xooie 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,214 @@
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
+ define('xooie/base', ['jquery', 'xooie', 'xooie/stylesheet'], function($, $X, Stylesheet) {
18
+ $.event.special['xooie-init'] = {
19
+ add: function(handleObj) {
20
+ var control = $(this).data(handleObj.namespace + '-instance');
21
+ if (control) {
22
+ var event = $.Event('xooie-init');
23
+ event.namespace = handleObj.namespace;
24
+ event.data = handleObj.data;
25
+
26
+ handleObj.handler.call(this, event);
27
+ }
28
+ }
29
+ };
30
+
31
+ var Base = function(name, constructor) {
32
+ var instances, defaultOptions, instanceCounter, initEvent, instanceName, cssRules, stylesInstance, className, Xooie;
33
+
34
+ instances = [];
35
+
36
+ defaultOptions = {};
37
+
38
+ name = name.toLowerCase();
39
+ initEvent = 'xooie-init.' + name;
40
+ refreshEvent = 'xooie-refresh.' + name;
41
+ instanceName = name + '-instance';
42
+ instanceCounter = 0;
43
+ className = 'is-' + name + '-instantiated';
44
+
45
+ cssRules = {};
46
+ stylesInstance = new Stylesheet('Xooie');
47
+
48
+ Xooie = function(root) {
49
+ this.root = $(root);
50
+ this._name = name;
51
+
52
+ if (this.root.data(instanceName)) {
53
+ this.root.trigger(refreshEvent);
54
+ return instances[this.root.data(instanceName)];
55
+ }
56
+ instanceCounter+=1;
57
+ instances[instanceCounter] = this;
58
+ this.root.data(instanceName, instanceCounter);
59
+
60
+ this.instanceClass = this._name + '-' + instanceCounter;
61
+ this.root.addClass(this.instanceClass);
62
+
63
+ this.options = $.extend({}, Xooie.getDefaultOptions(), this.root.data());
64
+
65
+ //expose the stylesheet for this widget to each instance
66
+ this.stylesheet = stylesInstance;
67
+
68
+ //expose the common css rules
69
+ this.cssRules = $.extend({}, cssRules);
70
+
71
+ var addons, i, self = this;
72
+
73
+ constructor.apply(this, arguments);
74
+
75
+ this.root.addClass(className);
76
+
77
+ if(this.options.addons) {
78
+ addons = this.options.addons.split(' ');
79
+
80
+ for (i = 0; i < addons.length; i += 1) {
81
+ this.loadAddon(addons[i]);
82
+ }
83
+ }
84
+
85
+ this.root.trigger(initEvent);
86
+ };
87
+
88
+ Xooie.prototype = {
89
+ loadAddon: function(addon){
90
+ var self = this,
91
+ addon_name = $X.mapName(addon, 'addons', 'xooie/addons/');
92
+
93
+ if (typeof this.addons === 'undefined') {
94
+ this.addons = {};
95
+ }
96
+
97
+ try {
98
+ $X._requireShim(addon_name, function(Addon){
99
+ if (typeof Addon === 'function') {
100
+ new Addon(self);
101
+ }
102
+ });
103
+ } catch (e) {
104
+ //need to determine how to handle missing addons
105
+ }
106
+ },
107
+
108
+ render: function(template, view) {
109
+ var language = template.data('templateLanguage') || Base.default_template_language,
110
+ result = Base.render[language](template, view);
111
+
112
+ if (result === false) {
113
+ return $('<span>Error rendering template</span>');
114
+ } else {
115
+ return result;
116
+ }
117
+ },
118
+
119
+ cleanup: function() {
120
+ var name;
121
+
122
+ for (name in this.addons) {
123
+ if (this.addons.hasOwnProperty(name)) {
124
+ this.addons[name].cleanup();
125
+ }
126
+ }
127
+
128
+ this.root.removeClass(className);
129
+ this.root.removeClass(this.instanceClass);
130
+ this.root.data(instanceName, false);
131
+ }
132
+
133
+ };
134
+
135
+ Xooie.setCSSRules = function(rules){
136
+ var rule;
137
+
138
+ if(typeof stylesInstance.addRule === 'undefined'){
139
+ return;
140
+ }
141
+
142
+ for (rule in rules){
143
+ cssRules[rule] = stylesInstance.addRule(rule, rules[rule]);
144
+ }
145
+ };
146
+
147
+ Xooie.getDefaultOptions = function(){
148
+ return defaultOptions || {};
149
+ };
150
+
151
+ Xooie.setDefaultOptions = function(options) {
152
+ if (typeof options !== 'undefined') {
153
+ $.extend(defaultOptions, options);
154
+ }
155
+ };
156
+
157
+ Xooie.garbageCollect = function() {
158
+ var id, instance;
159
+
160
+ for (id in instances) {
161
+ if (instances.hasOwnProperty(id)) {
162
+ instance = instances[id];
163
+
164
+ if (instance.root.parents('body').length === 0) {
165
+ instance.cleanup();
166
+ delete instances[id];
167
+ }
168
+ }
169
+ }
170
+ };
171
+
172
+ $X.registeredClasses.push(Xooie);
173
+
174
+ return Xooie;
175
+ };
176
+
177
+ Base.default_template_language = 'micro_template';
178
+
179
+ Base.render = {
180
+ 'micro_template': function(template, view) {
181
+ if (typeof template.micro_render !== 'undefined') {
182
+ return $(template.micro_render(view));
183
+ } else {
184
+ return false;
185
+ }
186
+ },
187
+
188
+ 'mustache': function(template, view) {
189
+ if (typeof Mustache !== 'undefined' && typeof Mustache.render !== 'undefined') {
190
+ return $(Mustache.render(template.html(), view));
191
+ } else {
192
+ return false;
193
+ }
194
+ },
195
+
196
+ 'jsrender': function(template, view) {
197
+ if (typeof template.render !== 'undefined') {
198
+ return $(template.render(view));
199
+ } else {
200
+ return false;
201
+ }
202
+ },
203
+
204
+ 'underscore': function(template, view) {
205
+ if (typeof _ !== 'undefined' && typeof _.template !== 'undefined') {
206
+ return $(_.template(template.html())(view).trim());
207
+ } else {
208
+ return false;
209
+ }
210
+ }
211
+ };
212
+
213
+ return Base;
214
+ });
@@ -0,0 +1,400 @@
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
+ define('xooie/carousel', ['jquery', 'xooie/base'], function($, Base) {
18
+
19
+ var resizeTimer = null,
20
+ carouselElements = $(),
21
+ clickQueue = [],
22
+ cssRules = {},
23
+ cache;
24
+
25
+ $(window).on('resize', function() {
26
+ if (resizeTimer) {
27
+ clearTimeout(resizeTimer);
28
+ resizeTimer = null;
29
+ }
30
+ if (carouselElements) {
31
+ resizeTimer = setTimeout(function() {
32
+ carouselElements.trigger('carouselResize');
33
+ }, 100);
34
+ }
35
+ });
36
+
37
+ var Carousel = Base('carousel', function() {
38
+ var self = this,
39
+ scrollTimer,
40
+ onClick, onScroll, onScrollComplete;
41
+
42
+ this.isScrolling = false;
43
+
44
+ //Define the dispatch tables for various functionality:
45
+ this.positionUpdaters = {
46
+
47
+ "item": function(quantity, direction) {
48
+ var items = self.content.children(),
49
+ bias, offset,
50
+ position = self.wrapper.scrollLeft(),
51
+ i;
52
+
53
+ if (typeof direction === 'undefined') {
54
+ if (quantity > 0 && quantity <= items.length) {
55
+ offset = Math.round(items.eq(quantity - 1).position().left);
56
+ }
57
+ } else {
58
+ direction = direction === -1 ? -1 : 1;
59
+
60
+ bias = -direction;
61
+
62
+ if (!quantity || typeof quantity !== 'number') {
63
+ quantity = 1;
64
+ }
65
+
66
+ i = self.currentItem(bias) + direction * quantity;
67
+ i = Math.max(0, Math.min(items.length - 1, i));
68
+ offset = Math.round(items.eq(i).position().left);
69
+ }
70
+
71
+ return position + offset;
72
+ },
73
+
74
+ "px": function(quantity, direction) {
75
+ var position;
76
+
77
+ if (typeof direction === 'undefined') {
78
+ position = 0;
79
+ direction = 1;
80
+ } else {
81
+ position = self.wrapper.scrollLeft();
82
+ }
83
+ return position + direction * quantity;
84
+ }
85
+
86
+ };
87
+
88
+ this.snapMethods = {
89
+
90
+ "item": function(){
91
+ var items = self.content.children(),
92
+ offset, p1, p2,
93
+ i = self.currentItem();
94
+
95
+ p1 = items.eq(i).position().left;
96
+ if (Math.abs(p1) < 1) {
97
+ p1 = p1 < 0 ? Math.ceil(p1) : Math.floor(p1);
98
+ } else {
99
+ p1 = Math.round(p1);
100
+ }
101
+
102
+ if (p1 !== 0 && i > 0) {
103
+ p2 = items.eq(i - 1).position().left;
104
+ if (Math.abs(p2) < 1) {
105
+ p2 = p2 < 0 ? Math.ceil(p2) : Math.floor(p2);
106
+ } else {
107
+ p2 = Math.round(p2);
108
+ }
109
+
110
+ if (Math.abs(p1) < Math.abs(p2)) {
111
+ offset = p1 + self.wrapper.scrollLeft();
112
+ } else {
113
+ offset = p2 + self.wrapper.scrollLeft();
114
+ }
115
+
116
+ self.wrapper.animate({ scrollLeft: offset });
117
+ }
118
+
119
+ }
120
+
121
+ };
122
+
123
+ this.displayMethods = {
124
+ "item": function(container, template){
125
+ var element, item, items, lastVisible, rightPosition, i;
126
+
127
+ items = self.content.children();
128
+ currentItem = self.currentItem();
129
+ rightPosition = items.eq(currentItem).position().left + self.wrapper.innerWidth();
130
+ lastVisible = items.length;
131
+
132
+ for (i = currentItem; i < items.length; i += 1) {
133
+ item = items.eq(i);
134
+ if (Math.floor(item.position().left) + item.outerWidth() * self.options.visibleThreshold >= rightPosition) {
135
+ lastVisible = i;
136
+ break;
137
+ }
138
+ }
139
+
140
+ element = self.render(template, {
141
+ current_item: currentItem + 1,
142
+ last_visible_item: lastVisible,
143
+ total_items: items.length
144
+ });
145
+
146
+ container.append(element);
147
+ }
148
+ };
149
+
150
+ //select the content area and wrap it in a container
151
+ this.content = this.root.find(this.options.contentSelector);
152
+ this.content.wrap('<div/>');
153
+
154
+ this.wrapper = this.content.parent();
155
+ this.wrapper.addClass('xooie-carousel-wrapper');
156
+
157
+ //setting the wrapper's parent to overflow-y=hidden allows us to hide the horizontal scrollbar
158
+ this.wrapper.parent().addClass('xooie-carousel-crop');
159
+
160
+ this.cssRules.heightAdjust = this.stylesheet.addRule('.carousel-' + this.root.data('carousel-instance') + ' .xooie-carousel-crop');
161
+
162
+ this.content.addClass('xooie-carousel-content');
163
+
164
+ this.content.children().addClass('xooie-carousel-item');
165
+
166
+ this.root.find(this.options.controlSelector)
167
+ .on('click', function(event){
168
+ event.preventDefault();
169
+
170
+ self.updatePosition($(this).data('scroll'));
171
+ });
172
+
173
+ onScrollComplete = function() {
174
+ self.snap();
175
+ self.root.trigger('carouselScrollComplete');
176
+ };
177
+
178
+ onScroll = function(){
179
+ if (scrollTimer) {
180
+ scrollTimer = clearTimeout(scrollTimer);
181
+ } else {
182
+ self.root.removeClass('is-carousel-leftmost is-carousel-rightmost');
183
+ }
184
+
185
+ scrollTimer = setTimeout(onScrollComplete, 250);
186
+ };
187
+
188
+ this.wrapper.on('scroll', onScroll);
189
+
190
+ this.root.on({
191
+ carouselScrollComplete: function(){
192
+ self.updateDisplay();
193
+ self.updateLimits();
194
+ },
195
+ carouselInit: this.updateDimensions.bind(this),
196
+ carouselResize: this.updateDimensions.bind(this)
197
+ });
198
+
199
+ //It is possible that images may load after the carousel has instantiated/
200
+ //Also, this can be used for lazy-loading images
201
+ //TODO: This can be problematic, since it is triggering update dimensions for each image load
202
+ this.content.find('img').on('load', this.updateDimensions.bind(this));
203
+
204
+ carouselElements = carouselElements.add(this.root);
205
+ });
206
+
207
+ Carousel.setDefaultOptions({
208
+ contentSelector: '[data-role="carousel-content"]',
209
+ controlSelector: '[data-role="carousel-control"]',
210
+
211
+ displayMode: 'none',
212
+ displaySelector: '[data-role="carousel-display"]',
213
+ displayTemplateSelector: '[data-role="carousel-display-template"]',
214
+
215
+ snapMode: 'none',
216
+ visibleThreshold: 0.50
217
+ });
218
+
219
+ //Set css rules for all carousels
220
+ Carousel.setCSSRules({
221
+ '.xooie-carousel-wrapper': {
222
+ 'overflow-x': 'scroll',
223
+ 'overflow-y': 'hidden'
224
+ },
225
+ '.xooie-carousel-crop': {
226
+ 'overflow-y': 'hidden'
227
+ },
228
+ '.xooie-carousel-content': {
229
+ display: 'table-cell',
230
+ 'white-space': 'nowrap',
231
+ 'font-size': '0px'
232
+ },
233
+ '.xooie-carousel-item': {
234
+ display: 'inline-block',
235
+ zoom: '1',
236
+ '*display': 'inline',
237
+ 'font-size': '1em'
238
+ }
239
+ });
240
+
241
+ cache = {
242
+ currentItem: 0,
243
+ lastItem: 0
244
+ };
245
+
246
+ Carousel.prototype.currentItem = function(bias) {
247
+ var i, items = this.content.children(),
248
+ position, itemWidth;
249
+
250
+ if (typeof bias === 'undefined') {
251
+ bias = 1;
252
+ }
253
+
254
+ if (bias === 1) {
255
+ position = this.content.position().left;
256
+
257
+ for (i = 0; i < items.length - 1; i++) {
258
+ itemWidth = items.eq(i).outerWidth(true);
259
+
260
+ if (position + this.options.visibleThreshold * itemWidth >= 0){
261
+ return i;
262
+ } else {
263
+ position += itemWidth;
264
+ }
265
+ }
266
+ return items.length - 1;
267
+ } else {
268
+ position = this.content.outerWidth(true) + this.content.position().left;
269
+
270
+ for (i = items.length - 1; i > 0; i -= 1) {
271
+ itemWidth = items.eq(i).outerWidth(true);
272
+ position -= itemWidth;
273
+
274
+ if (i > 0 && position <= this.options.visibleThreshold * itemWidth) {
275
+ return i;
276
+ }
277
+ }
278
+ return 0;
279
+ }
280
+ };
281
+
282
+ Carousel.prototype.getRightLimit = function(){
283
+ try {
284
+ var lastItem = this.content.children(':last'),
285
+ position = lastItem.position();
286
+
287
+ if (position && typeof position.left !== 'undefined') {
288
+ return Math.floor(position.left) + lastItem.outerWidth(true);
289
+ }
290
+ } catch (e) {
291
+ return;
292
+ }
293
+ };
294
+
295
+ Carousel.prototype.updateDimensions = function() {
296
+ var items = this.content.children(),
297
+ height = 0;
298
+
299
+ items.each(function() {
300
+ var node = $(this);
301
+ height = Math.max(height, node.outerHeight(true));
302
+ });
303
+
304
+ //set the height of the wrapper's parent (or cropping element) to ensure we hide the scrollbar
305
+ this.cssRules.heightAdjust.style.height = height + 'px';
306
+
307
+ this.updateLimits();
308
+ this.updateDisplay();
309
+ this.snap();
310
+
311
+ this.root.trigger('carouselUpdated');
312
+ };
313
+
314
+ Carousel.prototype.updateLimits = function() {
315
+ this.root.toggleClass('is-carousel-leftmost', this.wrapper.scrollLeft() === 0);
316
+ this.root.toggleClass('is-carousel-rightmost', this.getRightLimit() <= this.wrapper.innerWidth());
317
+ };
318
+
319
+ Carousel.prototype.updatePosition = function(amount, cb) {
320
+ var match = (amount + '').match(/^([+\-]?)(\d+)(.*)$/),
321
+ callback,
322
+ self = this;
323
+
324
+ if (!match) {
325
+ if (typeof cb === 'function') {
326
+ cb();
327
+ }
328
+
329
+ return;
330
+ }
331
+
332
+ callback = function(){
333
+ var direction, quantity, units, offset;
334
+
335
+ if (match[1] !== '') {
336
+ direction = (match[1] === '-') ? -1 : 1;
337
+ }
338
+
339
+ quantity = parseInt(match[2], 10);
340
+ units = match[3];
341
+
342
+ if (units === '') {
343
+ units = 'px';
344
+ }
345
+
346
+ if (typeof self.positionUpdaters[units] === 'function') {
347
+ offset = self.positionUpdaters[units](quantity, direction);
348
+ } else {
349
+ offset = 0;
350
+ }
351
+
352
+ self.isScrolling = true;
353
+
354
+ self.root.trigger('carouselMove', offset);
355
+
356
+ self.wrapper.animate({ scrollLeft: offset }, 200,
357
+ function(){
358
+ self.isScrolling = false;
359
+ if (typeof cb === 'function') {
360
+ cb();
361
+ }
362
+ }
363
+ );
364
+ };
365
+
366
+ if (this.isScrolling) {
367
+ self.wrapper.stop(true,true);
368
+ }
369
+
370
+ callback();
371
+
372
+ };
373
+
374
+ Carousel.prototype.updateDisplay = function(){
375
+ if (this.options.displayMode === 'none') {
376
+ return;
377
+ }
378
+
379
+ var container = this.root.find(this.options.displaySelector),
380
+ template = this.root.find(this.options.displayTemplateSelector);
381
+
382
+ if (container.length === 0 || template.length === 0) {
383
+ return;
384
+ }
385
+
386
+ container.html('');
387
+
388
+ if (typeof this.displayMethods[this.options.displayMode] === 'function') {
389
+ this.displayMethods[this.options.displayMode](container, template);
390
+ }
391
+ };
392
+
393
+ Carousel.prototype.snap = function(){
394
+ if (this.getRightLimit() > this.wrapper.innerWidth() && typeof this.snapMethods[this.options.snapMode] === 'function') {
395
+ this.snapMethods[this.options.snapMode]();
396
+ }
397
+ };
398
+
399
+ return Carousel;
400
+ });