xooie 0.0.17.pre → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,769 @@
1
+ /*
2
+ * Copyright 2012 Comcast
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * class Xooie.Carousel < Xooie.Widget
19
+ *
20
+ * A widget that allows users to horizontally scroll through a collection of elements. Carousels are
21
+ * commonly used to display a large amount of images or links in a small amount of space. The user can
22
+ * view more items by clicking the directional controls to scroll the content forward or backward. If
23
+ * the device recognizes swipe gestues (e.g. mobile or Mac OS) then swiping will also allow the user to
24
+ * scroll content.
25
+ * Keyboard-only users will also be able to navigate from item to item using the tab, left or right keys.
26
+ * Screen reader users will percieve the carousel as a [list](http://www.w3.org/TR/wai-aria/roles#list) of items.
27
+ * For most devices, the native scrollbar is hidden in favor of the directional controls and native scrolling.
28
+ **/
29
+ define('xooie/widgets/carousel', ['jquery', 'xooie/helpers', 'xooie/widgets/base', 'xooie/event_handler'], function($, helpers, Base, EventHandler) {
30
+ var Carousel, timers;
31
+
32
+ /**
33
+ * Xooie.Carousel@xooie-carousel-resize(event)
34
+ * - event (Event): A jQuery event object
35
+ *
36
+ * A jQuery special event triggered to indicate that the carousel instance should be resized. This
37
+ * by default is triggered when the window is resized.
38
+ **/
39
+
40
+ timers = {
41
+ resize: null
42
+ };
43
+
44
+ $(window).on('resize', function() {
45
+ if (timers.resize !== null) {
46
+ clearTimeout(timers.resize);
47
+ timers.resize = null;
48
+ }
49
+ if (Carousel._cache.length > 0) {
50
+ // TODO: make this delay adjustable
51
+ timers.resize = setTimeout(function() {
52
+ Carousel._cache.trigger(Carousel.prototype.resizeEvent());
53
+ }, 100);
54
+ }
55
+ });
56
+
57
+ /** internal
58
+ * Xooie.Carousel.parseCtrlStr(ctrlStr) -> Array | undefined
59
+ *
60
+ * Checks the data-x-role value of a control and matches it against expected patterns to determine
61
+ * the control commands, if any.
62
+ * Returns an array: [Direction, Amount, Mode].
63
+ * For example, control:right 1 item -> [right, 1, item], whereas control:right continuous returns
64
+ * [right, undefined, continuous].
65
+ **/
66
+ function parseCtrlStr(ctrlStr) {
67
+ ctrlStr = ctrlStr.toLowerCase();
68
+
69
+ var ptrnMatch = ctrlStr.match(/^control:(left|right|goto)\s(\d+)(?:st|nd|rd|th)?\s(.*)$/);
70
+
71
+ if(ptrnMatch === null) {
72
+ ptrnMatch = ctrlStr.match(/^control:(left|right)()\s(continuous)$/);
73
+ }
74
+
75
+ if (ptrnMatch !== null) {
76
+ return ptrnMatch.slice(1);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * new Xooie.Carousel(element[, addons])
82
+ * - element (Element | String): A jQuery-selected element or string selector for the root element of this widget
83
+ * - addons (Array): An optional collection of [[Xooie.Addon]] classes to be instantiated with this widget
84
+ *
85
+ * Instantiates a new instance of a [[Xooie.Carousel]] widget. Defines [[Xooie.Carousel#_timers]],
86
+ * [[Xooie.Carousel#_controlEvents]], [[Xooie.Carousel#_wrapperEvents]], and [[Xooie.Carousel#cropStyle]].
87
+ * Events are bound to the [[Xooie.Widget#root]] to call [[Xooie.Carousel#updateDimensions]] on [[Xooie.Widget@xooie-init]],
88
+ * [[Xooie.Widget@xooie-refresh]] and [[Xooie.Carousel@xooie-carousel-resize]].
89
+ * Carousel instances are tracked in the [[Xooie.Carousel._cache]] collection.
90
+ **/
91
+ Carousel = Base.extend(function() {
92
+ var self = this;
93
+
94
+ /** internal
95
+ * Xooie.Carousel#_timers -> Object
96
+ *
97
+ * A hash of all timers currently active. If no timer is active for a particular type then the value is
98
+ * set to undefined.
99
+ *
100
+ * ##### Timers
101
+ * - **scroll** (Integer | undefined): Active while the content is being scrolled. Prevents post-scroll functionality
102
+ * from triggering until the carousel has completely finished scrolling.
103
+ * - **continuous** (Integer | undefined): Active while the user is continuously scrolling using a [[Xooie.Carousel#controls]].
104
+ **/
105
+ this._timers = {
106
+ scroll: 0,
107
+ continuous: 0
108
+ };
109
+
110
+ /** internal
111
+ * Xooie.Carousel#_positioners -> Object
112
+ *
113
+ * A dispatch table containing the various methods for scrolling the carousel content.
114
+ *
115
+ * ##### Positioners
116
+ * - **item**(direction, quantity): Calls [[Xooie.Carousel#scrollTo]] with the position of the item designated by the quantity.
117
+ * - **items**(direction, quantity): alias of **item**
118
+ * - **pixel**(direction, quantity): Calls [[Xooie.Carousel#scrollTo]] with the pixel position designated by quantity.
119
+ * - **pixels**(direction, quantity): alias of **pixel**
120
+ * - **px**(direction, quantity): alias of **pixel**
121
+ **/
122
+ this._positioners = {
123
+
124
+ item: function(direction, quantity) {
125
+ var items, pos, i;
126
+
127
+ items = this.items();
128
+
129
+ quantity = helpers.toInt(quantity);
130
+
131
+ if (isNaN(quantity)) {
132
+ return;
133
+ }
134
+
135
+ if (direction === 'goto' && quantity < 1 && quantity <= items.length) {
136
+ pos = Math.round(items.eq(quantity - 1).position().left);
137
+ } else {
138
+ i = this.currentItem(direction === 'right');
139
+
140
+ direction = direction === 'left' ? -1 : 1;
141
+
142
+ i = Math.max(0, Math.min(items.length - 1, i + (direction * quantity)));
143
+
144
+ pos = this.wrappers().scrollLeft() + Math.round(items.eq(i).position().left);
145
+ }
146
+
147
+ this.scrollTo(pos);
148
+ },
149
+
150
+ items: function() {
151
+ return this._positioners.item.apply(this, arguments);
152
+ },
153
+
154
+ pixel: function(direction, quantity) {
155
+ var pos;
156
+
157
+ quantity = helpers.toInt(quantity);
158
+
159
+ if (isNaN(quantity)) {
160
+ return;
161
+ }
162
+
163
+ if (direction === 'goto' && quantity >= 0) {
164
+ pos = quantity;
165
+ } else {
166
+ direction = direction === 'left' ? -1 : 1;
167
+
168
+ pos = this.wrappers().scrollLeft() + (direction * quantity);
169
+ }
170
+
171
+ this.scrollTo(pos);
172
+ },
173
+
174
+ pixels: function() {
175
+ return this._positioners.pixel.apply(this, arguments);
176
+ },
177
+
178
+ px: function() {
179
+ return this._positioners.pixel.apply(this, arguments);
180
+ }
181
+ };
182
+
183
+ /** internal
184
+ * Xooie.Carousel#continuousScroll(ctrl, direction)
185
+ * - ctrl (Element): The control that was activated to initiate the scroll
186
+ * - direction (String): The direction of the scroll. Can be `left` or `right`.
187
+ **/
188
+ function continuousScroll(ctrl, direction) {
189
+ clearInterval(self._timers.continuous);
190
+
191
+ self._timers.continuous = setInterval(function(dir) {
192
+ if (ctrl.is(':disabled')) {
193
+ self._timers.continuous = clearInterval(self._timers.continuous);
194
+ }
195
+
196
+ //TODO: Need some way of setting rate
197
+ self.scrollTo(self.wrappers().scrollLeft() + (dir * 5));
198
+ }, 0, [direction === 'right' ? 1 : -1]);
199
+ }
200
+
201
+ /** internal
202
+ * Xooie.Carousel#_controlEvents -> Object
203
+ *
204
+ * An instance of [[Xooie.EventHandler]] that manages event handlers to be bound to the
205
+ * [[Xooie.Carousel#controls]].
206
+ **/
207
+ this._controlEvents = new EventHandler(this.namespace());
208
+
209
+ this._controlEvents.add({
210
+ keydown: function(event) {
211
+ var ctrl, args;
212
+
213
+ if ([13,32].indexOf(event.which) !== -1) {
214
+ ctrl = $(this);
215
+ args = parseCtrlStr(ctrl.attr('data-x-role'));
216
+
217
+ if (args[2] === 'continuous' && !ctrl.is(':disabled')) {
218
+ continuousScroll(ctrl, args[0]);
219
+
220
+ event.preventDefault();
221
+ }
222
+ }
223
+ },
224
+
225
+ mousedown: function(event) {
226
+ var ctrl, args;
227
+
228
+ ctrl = $(this);
229
+ args = parseCtrlStr(ctrl.attr('data-x-role'));
230
+
231
+ if (args[2] === 'continuous' && !ctrl.is(':disabled')) {
232
+ continuousScroll(ctrl, args[0]);
233
+
234
+ event.preventDefault();
235
+ }
236
+ },
237
+
238
+ keyup: function(event) {
239
+ self._timers.continuous = clearInterval(self._timers.continuous);
240
+
241
+ if ($(this).is(':disabled')) {
242
+ return;
243
+ }
244
+
245
+ if ([13,32].indexOf(event.which) !== -1) {
246
+ var args = parseCtrlStr($(this).attr('data-x-role'));
247
+
248
+ if (helpers.isFunction(self._positioners[args[2]])) {
249
+ self._positioners[args[2]].apply(self, args);
250
+ }
251
+
252
+ event.preventDefault();
253
+ }
254
+ },
255
+
256
+ mouseup: function(event) {
257
+ self._timers.continuous = clearInterval(self._timers.continuous);
258
+
259
+ if ($(this).is(':disabled')) {
260
+ return;
261
+ }
262
+
263
+ var args = parseCtrlStr($(this).attr('data-x-role'));
264
+
265
+ if (helpers.isFunction(self._positioners[args[2]])) {
266
+ self._positioners[args[2]].apply(self, args);
267
+ }
268
+
269
+ event.preventDefault();
270
+ },
271
+
272
+ mouseleave: function(event) {
273
+ self._timers.continuous = clearInterval(self._timers.continuous);
274
+ },
275
+
276
+ blur: function(event) {
277
+ self._timers.continuous = clearInterval(self._timers.continuous);
278
+ }
279
+ });
280
+
281
+ function scrollComplete() {
282
+ self._timers.scroll = clearTimeout(self._timers.scroll);
283
+
284
+ self.updateLimits();
285
+ }
286
+
287
+ /** internal
288
+ * Xooie.Carousel#_wrapperEvents -> Object
289
+ *
290
+ * An instance of [[Xooie.EventHandler]] that manages event handlers to be bound to the
291
+ * [[Xooie.Carousel#wrappers]].
292
+ **/
293
+ this._wrapperEvents = new EventHandler(this.namespace());
294
+
295
+ this._wrapperEvents.add('scroll', function(event){
296
+ if (self._timers.scroll) {
297
+ self._timers.scroll = clearTimeout(self._timers.scroll);
298
+ } else {
299
+ self.root().removeClass(self.leftClass() + ' ' + self.rightClass());
300
+
301
+ self.controls().prop('disabled', false);
302
+ }
303
+
304
+ // TODO: make this delay adjustable
305
+ self._timers.scroll = setTimeout(scrollComplete, 250);
306
+ });
307
+
308
+ this.cropStyle(Carousel.createStyleRule('.' + this.instanceClass() + ' .' + this.cropClass() + ', .' + this.instanceClass() + '.' + this.cropClass()));
309
+
310
+ // TODO: add functionality to remove from cache
311
+ Carousel._cache = Carousel._cache.add(this.root());
312
+
313
+ this.root().on([
314
+ this.get('initEvent'),
315
+ this.get('refreshEvent'),
316
+ this.get('resizeEvent')].join(' '),
317
+ function(){
318
+ self.updateDimensions();
319
+ });
320
+
321
+ });
322
+
323
+ /** internal
324
+ * Xooie.Carousel._cache -> jQuery
325
+ *
326
+ * A jQuery collection that keeps track of currently instantiated carousel instances. This collection
327
+ * is primarily used during a window resize event, where the limits and dimensions are recalculated.
328
+ **/
329
+ Carousel._cache = $();
330
+
331
+ /** internal
332
+ * Xooie.Carousel#_namespace -> String
333
+ *
334
+ * See [[Xooie.Widget#_namespace]]
335
+ * Default: `carousel`.
336
+ **/
337
+ /**
338
+ * Xooie.Carousel#namespace([value]) -> String
339
+ * - value: an optional value to be set.
340
+ *
341
+ * See [[Xooie.Widget#namespace]]
342
+ **/
343
+ Carousel.define('namespace', 'carousel');
344
+
345
+ /** internal
346
+ * Xooie.Carousel#_isScrolling -> Boolean
347
+ *
348
+ * A value that determines whether or not the carousel is currently scrolling
349
+ * TODO: Perhaps depricate this in favor of scroll timer detection
350
+ * Default: `false`.
351
+ **/
352
+ /**
353
+ * Xooie.Carousel#isScrolling([value]) -> String
354
+ * - value: an optional value to be set.
355
+ *
356
+ **/
357
+ Carousel.define('isScrolling', false);
358
+
359
+ /** internal
360
+ * Xooie.Carousel#_visibleThreshold -> Integer
361
+ *
362
+ * Default: `0.5`.
363
+ **/
364
+ /**
365
+ * Xooie.Carousel#visibleThreshold([value]) -> Integer
366
+ * - value: an optional value to be set.
367
+ *
368
+ **/
369
+ Carousel.define('visibleThreshold', 0.5);
370
+
371
+ /** internal
372
+ * Xooie.Carousel#_cropStyle -> cssRule
373
+ *
374
+ * Default: `carousel`.
375
+ **/
376
+ /**
377
+ * Xooie.Carousel#cropStyle([value]) -> cssRule
378
+ * - value: an optional value to be set.
379
+ *
380
+ **/
381
+ Carousel.define('cropStyle');
382
+
383
+ /** internal, read-only
384
+ * Xooie.Carousel#_resizeEvent -> String
385
+ *
386
+ * Default: `xooie-carousel-resize`.
387
+ **/
388
+ /**
389
+ * Xooie.Carousel#resizeEvent() -> String
390
+ *
391
+ **/
392
+ Carousel.defineReadOnly('resizeEvent', 'xooie-carousel-resize');
393
+
394
+ /** internal, read-only
395
+ * Xooie.Carousel#_wrapperClass -> String
396
+ *
397
+ * Default: `xooie-carousel-wrapper`.
398
+ **/
399
+ /**
400
+ * Xooie.Carousel#wrapperClass() -> String
401
+ *
402
+ **/
403
+ Carousel.defineReadOnly('wrapperClass', 'xooie-carousel-wrapper');
404
+
405
+ /** internal, read-only
406
+ * Xooie.Carousel#_cropClass -> String
407
+ *
408
+ * Default: `xooie-carousel-crop`.
409
+ **/
410
+ /**
411
+ * Xooie.Carousel#cropClass() -> String
412
+ *
413
+ **/
414
+ Carousel.defineReadOnly('cropClass', 'xooie-carousel-crop');
415
+
416
+ /** internal, read-only
417
+ * Xooie.Carousel#_contentClass -> String
418
+ *
419
+ * Default: `xooie-carousel-content`.
420
+ **/
421
+ /**
422
+ * Xooie.Carousel#contentClass() -> String
423
+ *
424
+ **/
425
+ Carousel.defineReadOnly('contentClass', 'xooie-carousel-content');
426
+
427
+ /** internal, read-only
428
+ * Xooie.Carousel#_controlClass -> String
429
+ *
430
+ * Default: `xooie-carousel-control`.
431
+ **/
432
+ /**
433
+ * Xooie.Carousel#controlClass() -> String
434
+ *
435
+ **/
436
+ Carousel.defineReadOnly('controlClass', 'xooie-carousel-control');
437
+
438
+ /** internal, read-only
439
+ * Xooie.Carousel#_leftClass -> String
440
+ *
441
+ * Default: `is-left-limit`.
442
+ **/
443
+ /**
444
+ * Xooie.Carousel#leftClass() -> String
445
+ *
446
+ **/
447
+ Carousel.defineReadOnly('leftClass', 'is-left-limit');
448
+
449
+ /** internal, read-only
450
+ * Xooie.Carousel#_rightClass -> String
451
+ *
452
+ * Default: `is-right-limit`.
453
+ **/
454
+ /**
455
+ * Xooie.Carousel#rightClass() -> String
456
+ *
457
+ **/
458
+ Carousel.defineReadOnly('rightClass', 'is-right-limit');
459
+
460
+ // ROLE DEFINITIONS
461
+
462
+ /**
463
+ * Xooie.Carousel#wrapper() -> Elements
464
+ *
465
+ *
466
+ **/
467
+ Carousel.defineRole('wrapper');
468
+
469
+ /**
470
+ * Xooie.Carousel#content() -> Elements
471
+ *
472
+ * This role maps to the ARIA [tab list](http://www.w3.org/TR/wai-aria/roles#list)
473
+ **/
474
+ Carousel.defineRole('content');
475
+
476
+ /**
477
+ * Xooie.Carousel#item() -> Elements
478
+ *
479
+ * This role maps to the ARIA [listitem role](http://www.w3.org/TR/wai-aria/roles#listitem)
480
+ **/
481
+ Carousel.defineRole('item');
482
+
483
+ /**
484
+ * Xooie.Carousel#control() -> Elements
485
+ *
486
+ * Controls allow the user to scroll the carousel. The behavior of this scrolling is determined by
487
+ * the role itself. Behavior is set using the `data-x-role` attribute: `data-x-role="control:<direction> <quantity> <mode>"`.
488
+ * The `direction` value indicates which direction the carousel should be moved: `right`, `left`, or `goto`.
489
+ * The special `goto` value signifies that the control should scroll to a fixed position.
490
+ * The control syntax is designed to accept somewhat natural language. Therefore, plurals and n-aries can be used to
491
+ * describe the behavior. For example, you can use the following strings: `control:right 2 items`, `control:left 30 pixels`,
492
+ * `control:goto 5th item`.
493
+ **/
494
+ Carousel.defineRole('control');
495
+
496
+ // STYLE DEFINITIONS
497
+
498
+ Carousel.createStyleRule('.' + Carousel.prototype.wrapperClass(), {
499
+ position: 'relative',
500
+ 'overflow-x': 'scroll',
501
+ 'overflow-y': 'hidden'
502
+ });
503
+
504
+ Carousel.createStyleRule('.' + Carousel.prototype.cropClass(), {
505
+ 'overflow-y': 'hidden'
506
+ });
507
+
508
+ Carousel.createStyleRule('.' + Carousel.prototype.contentClass(), {
509
+ display: 'table-cell',
510
+ 'white-space': 'nowrap',
511
+ 'font-size': '0px',
512
+ 'transition': 'left 0.5s'
513
+ });
514
+
515
+ Carousel.createStyleRule('ul.' + Carousel.prototype.contentClass(), {
516
+ 'list-style': 'none',
517
+ 'padding': 0,
518
+ 'margin': 0
519
+ });
520
+
521
+ Carousel.createStyleRule('.' + Carousel.prototype.contentClass() + ' > *', {
522
+ display: 'inline-block',
523
+ zoom: '1',
524
+ '*display': 'inline',
525
+ 'font-size': '1em'
526
+ });
527
+
528
+ Carousel.createStyleRule('.' + Carousel.prototype.leftClass() + '.' + Carousel.prototype.rightClass() + ' [data-x-role^="control:left"]' +
529
+ ', .' + Carousel.prototype.leftClass() + '.' + Carousel.prototype.rightClass() + ' [data-x-role^="control:right"]', {
530
+ display: 'none'
531
+ });
532
+
533
+ /**
534
+ * Xooie.Carousel#currentItem(biasRight) -> Integer
535
+ * - biasRight (Boolean): If true, calculates the current item from the right side of the carousel.
536
+ *
537
+ * Returns the index of the first visible item. The value of [[Xooie.Carousel#visibleThreshold]] determines what
538
+ * percentage of the item must be showing to be considered visible.
539
+ **/
540
+ Carousel.prototype.currentItem = function(biasRight) {
541
+ var content, items,
542
+ position, itemWidth,
543
+ i;
544
+
545
+ content = this.contents();
546
+ items = this.items();
547
+
548
+ if (biasRight) {
549
+ position = content.outerWidth(true) + content.position().left;
550
+
551
+ for (i = items.length - 1; i > 0; i -= 1) {
552
+ itemWidth = items.eq(i).outerWidth(true);
553
+ position -= itemWidth;
554
+
555
+ if (i > 0 && position <= this.visibleThreshold() * itemWidth) {
556
+ return i;
557
+ }
558
+ }
559
+ return 0;
560
+ } else {
561
+ position = content.position().left;
562
+
563
+ for (i = 0; i < items.length - 1; i++) {
564
+ itemWidth = items.eq(i).outerWidth(true);
565
+
566
+ if (position + this.visibleThreshold() * itemWidth >= 0){
567
+ return i;
568
+ } else {
569
+ position += itemWidth;
570
+ }
571
+ }
572
+
573
+ return items.length - 1;
574
+ }
575
+ };
576
+
577
+ /**
578
+ * Xooie.Carousel#isLeft() -> Boolean
579
+ *
580
+ * Indicates if the carousel is scrolled completely to the left.
581
+ **/
582
+ Carousel.prototype.isLeft = function() {
583
+ return this.wrappers().scrollLeft() === 0;
584
+ };
585
+
586
+ /**
587
+ * Xooie.Carousel#isRight() -> Boolean
588
+ *
589
+ * Indicates if the carousel is scrolled completely to the right.
590
+ **/
591
+ Carousel.prototype.isRight = function() {
592
+ var lastItem, position;
593
+
594
+ try {
595
+ lastItem = this.items().filter(':visible:last');
596
+ position = lastItem.position();
597
+
598
+ if (position && !helpers.isUndefined(position.left)) {
599
+ return Math.floor(position.left) + lastItem.outerWidth(true) <= this.wrappers().innerWidth();
600
+ }
601
+ } catch (e) {
602
+ return false;
603
+ }
604
+
605
+ return false;
606
+ };
607
+
608
+ /**
609
+ * Xooie.Carousel#updateDimensions()
610
+ *
611
+ * Updates the height of the carousel based on the height of the tallest visible item in the carousel.
612
+ * The new height is applied to the [[Xooie.Carousel#cropStyle]] rule rather than the cropping element
613
+ * itself. This allows developers to use cascade rules to override the height if they so choose.
614
+ **/
615
+ Carousel.prototype.updateDimensions = function() {
616
+ var height = 0;
617
+
618
+ this.items().each(function(){
619
+ height = Math.max(height, $(this).outerHeight(true));
620
+ });
621
+
622
+ //set the height of the wrapper's parent (or cropping element) to ensure we hide the scrollbar
623
+ this.cropStyle().style.height = height + 'px';
624
+
625
+ this.updateLimits();
626
+ };
627
+
628
+ /**
629
+ * Xooie.Carousel#updateLimits()
630
+ *
631
+ * Updates the state of the carousel based on whether or not it is scrolled completely to the left or the right.
632
+ * If the carousel is scrolled completely to the left then the [[Xooie.Carousel#leftClass]] is applied to the
633
+ * [[Xooie.Widget#root]] and the left [[Xooie.Carousel#controls]] is disabled. If the carousel is scrolled
634
+ * completely to the left then the [[Xooie.Carousel#rightClass]] is applied to the [[Xooie.Widget#root]] and the
635
+ * right [[Xooie.Carousel#controls]] is disabled.
636
+ **/
637
+ Carousel.prototype.updateLimits = function() {
638
+ var isLeft = this.isLeft(),
639
+ isRight = this.isRight();
640
+
641
+ this.root().toggleClass(this.leftClass(), isLeft);
642
+ this.controls().filter('[data-x-role^="control:left"]')
643
+ .prop('disabled', isLeft);
644
+
645
+ this.root().toggleClass(this.rightClass(), isRight);
646
+ this.controls().filter('[data-x-role^="control:right"]')
647
+ .prop('disabled', isRight);
648
+ };
649
+
650
+ /**
651
+ * Xooie.Carousel#scrollTo(pos, cb)
652
+ * - pos (Integer): The position to which the carousel will be scrolled.
653
+ * - cb (Function): A callback function that is called when the animation is complete.
654
+ *
655
+ * Uses the jQuery animate functionality to scroll the carousel to the designated position.
656
+ **/
657
+ Carousel.prototype.scrollTo = function(pos, cb) {
658
+ var self = this;
659
+
660
+ pos = Math.floor(pos);
661
+
662
+ if (this.isScrolling) {
663
+ this.wrappers().stop(true,true);
664
+ }
665
+
666
+ this.isScrolling = true;
667
+
668
+ // TODO: make the scroll timer configurable
669
+ this.wrappers().animate({ scrollLeft: pos }, 200,
670
+ function(){
671
+ self.isScrolling = false;
672
+ if (helpers.isFunction(cb)) {
673
+ cb();
674
+ }
675
+ }
676
+ );
677
+ };
678
+
679
+ /** internal
680
+ * Xooie.Carousel#_process_role_content(content) -> Element
681
+ * - content (Element): A jQuery-selected collection of [[Xooie.Carousel#contents]]
682
+ *
683
+ * This method processes the element that has been designated as a [[Xooie.Carousel#contents]].
684
+ * In addition to applying the [[Xooie.Carousel#contentClass]] the content is also given the
685
+ * aria role [list](http://www.w3.org/TR/wai-aria/roles#list) if it is neither a `ul` or `ol` element.
686
+ **/
687
+ Carousel.prototype._process_role_content = function(content) {
688
+ content.addClass(this.contentClass());
689
+
690
+ if (!content.is('ul,ol')) {
691
+ content.attr('role', 'list');
692
+ }
693
+
694
+ return content;
695
+ };
696
+
697
+ /** internal
698
+ * Xooie.Carousel#_render_role_wrapper() -> Element
699
+ *
700
+ * Renders a `div` tag that is wrapped around the [[Xooie.Carousel#contents]]. This element is
701
+ * rendered only if no other [[Xooie.Carousel#wrappers]] is present as a decendant of the root of this
702
+ * widget.
703
+ **/
704
+ Carousel.prototype._render_role_wrapper = function() {
705
+ var wrapper = $('<div data-x-role="wrapper" />');
706
+
707
+ this.contents().wrap(wrapper);
708
+
709
+ return this.contents().parent();
710
+ };
711
+
712
+ /** internal
713
+ * Xooie.Carousel#_process_role_wrapper(wrapper) -> Element
714
+ * - wrapper (Element): A jQuery-selected collection of [[Xooie.Carousel#wrappers]]
715
+ *
716
+ * This method processes the element that has been designated as a [[Xooie.Carousel#wrappers]].
717
+ * The [[Xooie.Carousel#wrapperClass]] is added and the [[Xooie.Carousel#_wrapperEvents]] handlers are
718
+ * bound. Also, the [[Xooie.Carousel#cropClass]] is added to this element's parent.
719
+ **/
720
+ Carousel.prototype._process_role_wrapper = function(wrapper) {
721
+ wrapper.addClass(this.wrapperClass())
722
+ .on(this._wrapperEvents.handlers)
723
+ .parent().addClass(this.cropClass());
724
+
725
+ return wrapper;
726
+ };
727
+
728
+ /** internal
729
+ * Xooie.Carousel#_get_role_item() -> Element
730
+ *
731
+ * Gets all children of [[Xooie.Carousel#contents]].
732
+ **/
733
+ Carousel.prototype._get_role_item = function() {
734
+ return this.contents().children();
735
+ };
736
+
737
+ /** internal
738
+ * Xooie.Carousel#_get_role_control() -> Element
739
+ *
740
+ * TODO: Test and document
741
+ **/
742
+ Carousel.prototype._get_role_control = function(){
743
+ return this.root().find('[data-x-role^="control"]');
744
+ };
745
+
746
+ /** internal
747
+ * Xooie.Carousel#_process_role_control() -> Element
748
+ *
749
+ **/
750
+ Carousel.prototype._process_role_control = function(controls) {
751
+ controls.on(this._controlEvents.handlers);
752
+
753
+ controls.attr('aria-hidden', true)
754
+ .addClass(this.controlClass());
755
+
756
+ return controls;
757
+ };
758
+
759
+ /** internal
760
+ * Xooie.Carousel#_process_resizeEvent() -> String
761
+ *
762
+ * Adds the [[Xooie.Widget#namespace]] to the `resizeEvent` string.
763
+ **/
764
+ Carousel.prototype._process_resizeEvent = function(resizeEvent) {
765
+ return this.namespace() === '' ? resizeEvent : resizeEvent + '.' + this.namespace();
766
+ };
767
+
768
+ return Carousel;
769
+ });