Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('imageloader', function (Y, NAME) {
2
 
3
/**
4
 * The ImageLoader Utility is a framework to dynamically load images according to certain triggers,
5
 * enabling faster load times and a more responsive UI.
6
 *
7
 * @module imageloader
8
 */
9
 
10
 
11
	/**
12
	 * A group for images. A group can have one time limit and a series of triggers. Thus the images belonging to this group must share these constraints.
13
	 * @class ImgLoadGroup
14
	 * @extends Base
15
	 * @constructor
16
	 */
17
	Y.ImgLoadGroup = function() {
18
		// call init first, because it sets up local vars for storing attribute-related info
19
		this._init();
20
		Y.ImgLoadGroup.superclass.constructor.apply(this, arguments);
21
	};
22
 
23
	Y.ImgLoadGroup.NAME = 'imgLoadGroup';
24
 
25
	Y.ImgLoadGroup.ATTRS = {
26
 
27
		/**
28
		 * Name for the group. Only used to identify the group in logging statements.
29
		 * @attribute name
30
		 * @type String
31
		 */
32
		name: {
33
			value: ''
34
		},
35
 
36
		/**
37
		 * Time limit, in seconds, after which images are fetched regardless of trigger events.
38
		 * @attribute timeLimit
39
		 * @type Number
40
		 */
41
		timeLimit: {
42
			value: null
43
		},
44
 
45
		/**
46
		 * Distance below the fold for which images are loaded. Images are not loaded until they are at most this distance away from (or above) the fold.
47
		 * This check is performed at page load (domready) and after any window scroll or window resize event (until all images are loaded).
48
		 * @attribute foldDistance
49
		 * @type Number
50
		 */
51
		foldDistance: {
52
			validator: Y.Lang.isNumber,
53
			setter: function(val) { this._setFoldTriggers(); return val; },
54
			lazyAdd: false
55
		},
56
 
57
		/**
58
		 * Class name that will identify images belonging to the group. This class name will be removed from each element in order to fetch images.
59
		 * This class should have, in its CSS style definition, "<code>background:none !important;</code>".
60
		 * @attribute className
61
		 * @type String
62
		 */
63
		className: {
64
			value: null,
65
			setter: function(name) { this._className = name; return name; },
66
			lazyAdd: false
67
		},
68
 
69
        /**
70
         * Determines how to act when className is used as the way to delay load images. The "default" action is to just
71
         * remove the class name. The "enhanced" action is to remove the class name and also set the src attribute if
72
         * the element is an img.
73
         * @attribute classNameAction
74
         * @type String
75
         */
76
        classNameAction: {
77
            value: "default"
78
        }
79
 
80
	};
81
 
82
	var groupProto = {
83
 
84
		/**
85
		 * Initialize all private members needed for the group.
86
		 * @method _init
87
		 * @private
88
		 */
89
		_init: function() {
90
 
91
			/**
92
			 * Collection of triggers for this group.
93
			 * Keeps track of each trigger's event handle, as returned from <code>Y.on</code>.
94
			 * @property _triggers
95
			 * @private
96
			 * @type Array
97
			 */
98
			this._triggers = [];
99
 
100
			/**
101
			 * Collection of images (<code>Y.ImgLoadImgObj</code> objects) registered with this group, keyed by DOM id.
102
			 * @property _imgObjs
103
			 * @private
104
			 * @type Object
105
			 */
106
			this._imgObjs = {};
107
 
108
			/**
109
			 * Timeout object to keep a handle on the time limit.
110
			 * @property _timeout
111
			 * @private
112
			 * @type Object
113
			 */
114
			this._timeout = null;
115
 
116
			/**
117
			 * DOM elements having the class name that is associated with this group.
118
			 * Elements are stored during the <code>_foldCheck</code> function and reused later during any subsequent <code>_foldCheck</code> calls - gives a slight performance improvement when the page fold is repeatedly checked.
119
			 * @property _classImageEls
120
			 * @private
121
			 * @type Array
122
			 */
123
			this._classImageEls = null;
124
 
125
			/**
126
			 * Keep the CSS class name in a member variable for ease and speed.
127
			 * @property _className
128
			 * @private
129
			 * @type String
130
			 */
131
			this._className = null;
132
 
133
			/**
134
			 * Boolean tracking whether the window scroll and window resize triggers have been set if this is a fold group.
135
			 * @property _areFoldTriggersSet
136
			 * @private
137
			 * @type Boolean
138
			 */
139
			this._areFoldTriggersSet = false;
140
 
141
			/**
142
			 * The maximum pixel height of the document that has been made visible.
143
			 * During fold checks, if the user scrolls up then there's no need to check for newly exposed images.
144
			 * @property _maxKnownHLimit
145
			 * @private
146
			 * @type Int
147
			 */
148
			this._maxKnownHLimit = 0;
149
 
150
			// add a listener to domready that will start the time limit
151
			Y.on('domready', this._onloadTasks, this);
152
		},
153
 
154
		/**
155
		 * Adds a trigger to the group. Arguments are passed to <code>Y.on</code>.
156
		 * @method addTrigger
157
		 * @chainable
158
		 * @param {Object} obj  The DOM object to attach the trigger event to
159
		 * @param {String} type  The event type
160
		 */
161
		addTrigger: function(obj, type) {
162
			if (! obj || ! type) {
163
				return this;
164
			}
165
 
166
 
167
			/* Need to wrap the fetch function. Event Util can't distinguish prototyped functions of different instantiations.
168
			 *   Leads to this scenario: groupA and groupZ both have window-scroll triggers. groupZ also has a 2-sec timeout (groupA has no timeout).
169
			 *   groupZ's timeout fires; we remove the triggers. The detach call finds the first window-scroll event with Y.ILG.p.fetch, which is groupA's.
170
			 *   groupA's trigger is removed and never fires, leaving images unfetched.
171
			 */
172
			var wrappedFetch = function() {
173
				this.fetch();
174
			};
175
			this._triggers.push( Y.on(type, wrappedFetch, obj, this) );
176
 
177
			return this;
178
		},
179
 
180
		/**
181
		 * Adds a custom event trigger to the group.
182
		 * @method addCustomTrigger
183
		 * @chainable
184
		 * @param {String} name  The name of the event
185
		 * @param {Object} obj  The object on which to attach the event. <code>obj</code> is optional - by default the event is attached to the <code>Y</code> instance
186
		 */
187
		addCustomTrigger: function(name, obj) {
188
			if (! name) {
189
				return this;
190
			}
191
 
192
 
193
			// see comment in addTrigger()
194
			var wrappedFetch = function() {
195
				this.fetch();
196
			};
197
			if (Y.Lang.isUndefined(obj)) {
198
				this._triggers.push( Y.on(name, wrappedFetch, this) );
199
			}
200
			else {
201
				this._triggers.push( obj.on(name, wrappedFetch, this) );
202
			}
203
 
204
			return this;
205
		},
206
 
207
		/**
208
		 * Sets the window scroll and window resize triggers for any group that is fold-conditional (i.e., has a fold distance set).
209
		 * @method _setFoldTriggers
210
		 * @private
211
		 */
212
		_setFoldTriggers: function() {
213
			if (this._areFoldTriggersSet) {
214
				return;
215
			}
216
 
217
 
218
			var wrappedFoldCheck = function() {
219
				this._foldCheck();
220
			};
221
			this._triggers.push( Y.on('scroll', wrappedFoldCheck, window, this) );
222
			this._triggers.push( Y.on('resize', wrappedFoldCheck, window, this) );
223
			this._areFoldTriggersSet = true;
224
		},
225
 
226
		/**
227
		 * Performs necessary setup at domready time.
228
		 * Initiates time limit for group; executes the fold check for the images.
229
		 * @method _onloadTasks
230
		 * @private
231
		 */
232
		_onloadTasks: function() {
233
			var timeLim = this.get('timeLimit');
234
			if (timeLim && timeLim > 0) {
235
				this._timeout = setTimeout(this._getFetchTimeout(), timeLim * 1000);
236
			}
237
 
238
			if (! Y.Lang.isUndefined(this.get('foldDistance'))) {
239
				this._foldCheck();
240
			}
241
		},
242
 
243
		/**
244
		 * Returns the group's <code>fetch</code> method, with the proper closure, for use with <code>setTimeout</code>.
245
		 * @method _getFetchTimeout
246
		 * @return {Function}  group's <code>fetch</code> method
247
		 * @private
248
		 */
249
		_getFetchTimeout: function() {
250
			var self = this;
251
			return function() { self.fetch(); };
252
		},
253
 
254
		/**
255
		 * Registers an image with the group.
256
		 * Arguments are passed through to a <code>Y.ImgLoadImgObj</code> constructor; see that class' attribute documentation for detailed information. "<code>domId</code>" is a required attribute.
257
		 * @method registerImage
258
		 * @param {Object} config A configuration object literal with attribute name/value pairs  (passed through to a <code>Y.ImgLoadImgObj</code> constructor)
259
		 * @return {Object}  <code>Y.ImgLoadImgObj</code> that was registered
260
		 */
261
		registerImage: function() {
262
			var domId = arguments[0].domId;
263
			if (! domId) {
264
				return null;
265
			}
266
 
267
 
268
			this._imgObjs[domId] = new Y.ImgLoadImgObj(arguments[0]);
269
			return this._imgObjs[domId];
270
		},
271
 
272
		/**
273
		 * Displays the images in the group.
274
		 * This method is called when a trigger fires or the time limit expires; it shouldn't be called externally, but is not private in the rare event that it needs to be called immediately.
275
		 * @method fetch
276
		 */
277
		fetch: function() {
278
 
279
			// done with the triggers
280
			this._clearTriggers();
281
 
282
			// fetch whatever we need to by className
283
			this._fetchByClass();
284
 
285
			// fetch registered images
286
			for (var id in this._imgObjs) {
287
				if (this._imgObjs.hasOwnProperty(id)) {
288
					this._imgObjs[id].fetch();
289
				}
290
			}
291
		},
292
 
293
		/**
294
		 * Clears the timeout and all triggers associated with the group.
295
		 * @method _clearTriggers
296
		 * @private
297
		 */
298
		_clearTriggers: function() {
299
			clearTimeout(this._timeout);
300
			// detach all listeners
301
			for (var i=0, len = this._triggers.length; i < len; i++) {
302
				this._triggers[i].detach();
303
			}
304
		},
305
 
306
		/**
307
		 * Checks the position of each image in the group. If any part of the image is within the specified distance (<code>foldDistance</code>) of the client viewport, the image is fetched immediately.
308
		 * @method _foldCheck
309
		 * @private
310
		 */
311
		_foldCheck: function() {
312
 
313
			var allFetched = true,
314
			    viewReg = Y.DOM.viewportRegion(),
315
			    hLimit = viewReg.bottom + this.get('foldDistance'),
316
					id, imgFetched, els, i, len;
317
 
318
			// unless we've uncovered new frontiers, there's no need to continue
319
			if (hLimit <= this._maxKnownHLimit) {
320
				return;
321
			}
322
			this._maxKnownHLimit = hLimit;
323
 
324
			for (id in this._imgObjs) {
325
				if (this._imgObjs.hasOwnProperty(id)) {
326
					imgFetched = this._imgObjs[id].fetch(hLimit);
327
					allFetched = allFetched && imgFetched;
328
				}
329
			}
330
 
331
			// and by class
332
			if (this._className) {
333
				if (this._classImageEls === null) {
334
					// get all the relevant elements and store them
335
					this._classImageEls = [];
336
					els = Y.all('.' + this._className);
337
					els.each( function(node) { this._classImageEls.push( { el: node, y: node.getY(), fetched: false } ); }, this);
338
				}
339
				els = this._classImageEls;
340
				for (i=0, len = els.length; i < len; i++) {
341
					if (els[i].fetched) {
342
						continue;
343
					}
344
					if (els[i].y && els[i].y <= hLimit) {
345
						//els[i].el.removeClass(this._className);
346
						this._updateNodeClassName(els[i].el);
347
                        els[i].fetched = true;
348
					}
349
					else {
350
						allFetched = false;
351
					}
352
				}
353
			}
354
 
355
			// if allFetched, remove listeners
356
			if (allFetched) {
357
				this._clearTriggers();
358
			}
359
		},
360
 
361
        /**
362
         * Updates a given node, removing the ImageLoader class name. If the
363
         * node is an img and the classNameAction is "enhanced", then node
364
         * class name is removed and also the src attribute is set to the
365
         * image URL as well as clearing the style background image.
366
         * @method _updateNodeClassName
367
         * @param node {Node} The node to act on.
368
         * @private
369
         */
370
        _updateNodeClassName: function(node){
371
            var url;
372
 
373
            if (this.get("classNameAction") == "enhanced"){
374
 
375
                if (node.get("tagName").toLowerCase() == "img"){
376
                    url = node.getStyle("backgroundImage");
377
                    /url\(["']?(.*?)["']?\)/.test(url);
378
                    url = RegExp.$1;
379
                    node.set("src", url);
380
                    node.setStyle("backgroundImage", "");
381
                }
382
            }
383
 
384
            node.removeClass(this._className);
385
        },
386
 
387
		/**
388
		 * Finds all elements in the DOM with the class name specified in the group. Removes the class from the element in order to let the style definitions trigger the image fetching.
389
		 * @method _fetchByClass
390
		 * @private
391
		 */
392
		_fetchByClass: function() {
393
			if (! this._className) {
394
				return;
395
			}
396
 
397
 
398
			Y.all('.' + this._className).each(Y.bind(this._updateNodeClassName, this));
399
		}
400
 
401
	};
402
 
403
 
404
	Y.extend(Y.ImgLoadGroup, Y.Base, groupProto);
405
 
406
 
407
	//------------------------------------------------
408
 
409
 
410
	/**
411
	 * Image objects to be registered with the groups
412
	 * @class ImgLoadImgObj
413
	 * @extends Base
414
	 * @constructor
415
	 */
416
	Y.ImgLoadImgObj = function() {
417
		Y.ImgLoadImgObj.superclass.constructor.apply(this, arguments);
418
		this._init();
419
	};
420
 
421
	Y.ImgLoadImgObj.NAME = 'imgLoadImgObj';
422
 
423
	Y.ImgLoadImgObj.ATTRS = {
424
		/**
425
		 * HTML DOM id of the image element.
426
		 * @attribute domId
427
		 * @type String
428
		 */
429
		domId: {
430
			value: null,
431
			writeOnce: true
432
		},
433
 
434
		/**
435
		 * Background URL for the image.
436
		 * For an image whose URL is specified by "<code>background-image</code>" in the element's style.
437
		 * @attribute bgUrl
438
		 * @type String
439
		 */
440
		bgUrl: {
441
			value: null
442
		},
443
 
444
		/**
445
		 * Source URL for the image.
446
		 * For an image whose URL is specified by a "<code>src</code>" attribute in the DOM element.
447
		 * @attribute srcUrl
448
		 * @type String
449
		 */
450
		srcUrl: {
451
			value: null
452
		},
453
 
454
		/**
455
		 * Pixel width of the image. Will be set as a <code>width</code> attribute on the DOM element after the image is fetched.
456
		 * Defaults to the natural width of the image (no <code>width</code> attribute will be set).
457
		 * Usually only used with src images.
458
		 * @attribute width
459
		 * @type Int
460
		 */
461
		width: {
462
			value: null
463
		},
464
 
465
		/**
466
		 * Pixel height of the image. Will be set as a <code>height</code> attribute on the DOM element after the image is fetched.
467
		 * Defaults to the natural height of the image (no <code>height</code> attribute will be set).
468
		 * Usually only used with src images.
469
		 * @attribute height
470
		 * @type Int
471
		 */
472
		height: {
473
			value: null
474
		},
475
 
476
		/**
477
		 * Whether the image's <code>style.visibility</code> should be set to <code>visible</code> after the image is fetched.
478
		 * Used when setting images as <code>visibility:hidden</code> prior to image fetching.
479
		 * @attribute setVisible
480
		 * @type Boolean
481
		 */
482
		setVisible: {
483
			value: false
484
		},
485
 
486
		/**
487
		 * Whether the image is a PNG.
488
		 * PNG images get special treatment in that the URL is specified through AlphaImageLoader for IE, versions 6 and earlier.
489
		 * Only used with background images.
490
		 * @attribute isPng
491
		 * @type Boolean
492
		 */
493
		isPng: {
494
			value: false
495
		},
496
 
497
		/**
498
		 * AlphaImageLoader <code>sizingMethod</code> property to be set for the image.
499
		 * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
500
		 * Defaults to <code>scale</code>.
501
		 * @attribute sizingMethod
502
		 * @type String
503
		 */
504
		sizingMethod: {
505
			value: 'scale'
506
		},
507
 
508
		/**
509
		 * AlphaImageLoader <code>enabled</code> property to be set for the image.
510
		 * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
511
		 * Defaults to <code>true</code>.
512
		 * @attribute enabled
513
		 * @type String
514
		 */
515
		enabled: {
516
			value: 'true'
517
		}
518
 
519
	};
520
 
521
	var imgProto = {
522
 
523
		/**
524
		 * Initialize all private members needed for the group.
525
		 * @method _init
526
		 * @private
527
		 */
528
		_init: function() {
529
 
530
			/**
531
			 * Whether this image has already been fetched.
532
			 * In the case of fold-conditional groups, images won't be fetched twice.
533
			 * @property _fetched
534
			 * @private
535
			 * @type Boolean
536
			 */
537
			this._fetched = false;
538
 
539
			/**
540
			 * The Node object returned from <code>Y.one</code>, to avoid repeat calls to access the DOM.
541
			 * @property _imgEl
542
			 * @private
543
			 * @type Object
544
			 */
545
			this._imgEl = null;
546
 
547
			/**
548
			 * The vertical position returned from <code>getY</code>, to avoid repeat calls to access the DOM.
549
			 * The Y position is checked only for images registered with fold-conditional groups. The position is checked first at page load (domready)
550
			 *   and this caching enhancement assumes that the image's vertical position won't change after that first check.
551
			 * @property _yPos
552
			 * @private
553
			 * @type Int
554
			 */
555
			this._yPos = null;
556
		},
557
 
558
		/**
559
		 * Displays the image; puts the URL into the DOM.
560
		 * This method shouldn't be called externally, but is not private in the rare event that it needs to be called immediately.
561
		 * @method fetch
562
		 * @param {Number} withinY  The pixel distance from the top of the page, for which if the image lies within, it will be fetched. Undefined indicates that no check should be made, and the image should always be fetched
563
		 * @return {Boolean}  Whether the image has been fetched (either during this execution or previously)
564
		 */
565
		fetch: function(withinY) {
566
			if (this._fetched) {
567
				return true;
568
			}
569
 
570
			var el = this._getImgEl(),
571
			    yPos;
572
			if (! el) {
573
				return false;
574
			}
575
 
576
			if (withinY) {
577
				// need a distance check
578
				yPos = this._getYPos();
579
				if (! yPos || yPos > withinY) {
580
					return false;
581
				}
582
			}
583
 
584
 
585
			// apply url
586
			if (this.get('bgUrl') !== null) {
587
				// bg url
588
				if (this.get('isPng') && Y.UA.ie && Y.UA.ie <= 6) {
589
					// png for which to apply AlphaImageLoader
590
					el.setStyle('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + this.get('bgUrl') + '", sizingMethod="' + this.get('sizingMethod') + '", enabled="' + this.get('enabled') + '")');
591
				}
592
				else {
593
					// regular bg image
594
					el.setStyle('backgroundImage', "url('" + this.get('bgUrl') + "')");
595
				}
596
			}
597
			else if (this.get('srcUrl') !== null) {
598
				// regular src image
599
				el.setAttribute('src', this.get('srcUrl'));
600
			}
601
 
602
			// apply attributes
603
			if (this.get('setVisible')) {
604
				el.setStyle('visibility', 'visible');
605
			}
606
			if (this.get('width')) {
607
				el.setAttribute('width', this.get('width'));
608
			}
609
			if (this.get('height')) {
610
				el.setAttribute('height', this.get('height'));
611
			}
612
 
613
			this._fetched = true;
614
 
615
			return true;
616
		},
617
 
618
		/**
619
		 * Gets the object (as a <code>Y.Node</code>) of the DOM element indicated by "<code>domId</code>".
620
		 * @method _getImgEl
621
		 * @return {Object} DOM element of the image as a <code>Y.Node</code> object
622
		 * @private
623
		 */
624
		_getImgEl: function() {
625
			if (this._imgEl === null) {
626
				this._imgEl = Y.one('#' + this.get('domId'));
627
			}
628
			return this._imgEl;
629
		},
630
 
631
		/**
632
		 * Gets the Y position of the node in page coordinates.
633
		 * Expects that the page-coordinate position of the image won't change.
634
		 * @method _getYPos
635
		 * @return {Object} The Y position of the image
636
		 * @private
637
		 */
638
		_getYPos: function() {
639
			if (this._yPos === null) {
640
				this._yPos = this._getImgEl().getY();
641
			}
642
			return this._yPos;
643
		}
644
 
645
	};
646
 
647
 
648
	Y.extend(Y.ImgLoadImgObj, Y.Base, imgProto);
649
 
650
 
651
 
652
 
653
}, '3.18.1', {"requires": ["base-base", "node-style", "node-screen"]});