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,628 @@
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.Widget
19
+ *
20
+ * The base xooie widget. This widget contains all common functionality for Xooie widgets but does not provide
21
+ * specific functionality.
22
+ **/
23
+
24
+ define('xooie/widgets/base', ['jquery', 'xooie/xooie', 'xooie/helpers', 'xooie/shared', 'xooie/keyboard_navigation'], function($, $X, helpers, shared, keyboardNavigation) {
25
+
26
+ var Widget;
27
+
28
+ /**
29
+ * Xooie.Widget@xooie-init(event)
30
+ * - event (Event): A jQuery event object
31
+ *
32
+ * A jQuery special event triggered when the widget is successfully initialized. Triggers on the `root` element
33
+ * of the widget. This event will fire if bound after the widget is instantiated.
34
+ **/
35
+
36
+ $.event.special['xooie-init'] = {
37
+ add: function(handleObj) {
38
+ var id = $(this).data('xooieInstance');
39
+ if (typeof id !== 'undefined') {
40
+ var event = $.Event('xooie-init');
41
+ event.namespace = handleObj.namespace;
42
+ event.data = handleObj.data;
43
+
44
+ handleObj.handler.call(this, event);
45
+ }
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Xooie.Widget@xooie-refresh(event)
51
+ * - event (Event): A jQuery event object
52
+ *
53
+ * A jQuery special event triggered when the widget is refreshed. Refresh events occur when the `root` element
54
+ * is passed to [[$X]]. Triggers on the `root` element of the widget.
55
+ **/
56
+
57
+ /** internal
58
+ * Xooie.Widget.roleDetails(name) -> Object
59
+ *
60
+ * TODO: Test and document.
61
+ **/
62
+ function roleDetails (name) {
63
+ return {
64
+ processor: '_process_role_' + name,
65
+ renderer: '_render_role_' + name,
66
+ getter: '_get_role_' + name,
67
+ pluralName: name + 's',
68
+ selector: '[data-x-role=' + name + ']'
69
+ };
70
+ }
71
+
72
+ /** internal
73
+ * Xooie.Widget.roleDispatcher(name, prototype)
74
+ *
75
+ * TODO: Test and document.
76
+ **/
77
+ function roleDispatcher(name, prototype) {
78
+ var role = roleDetails(name);
79
+
80
+ if (helpers.isUndefined(prototype[role.pluralName])) {
81
+ prototype._definedRoles.push(name);
82
+
83
+ prototype[role.pluralName] = function() {
84
+ return this[role.getter]();
85
+ };
86
+ }
87
+ }
88
+
89
+ /** internal
90
+ * Xooie.Widget.cacheInstance(instance) -> Integer
91
+ * - instance (Widget): An instance of a Xooie widget to be cached
92
+ *
93
+ * Recursively checks for the next available index in [[$X._instanceCache]] using [[$X._instanceIndex]]
94
+ * as a reference point. Returns the index.
95
+ **/
96
+ function cacheInstance (instance) {
97
+ if (typeof instance !== 'undefined') {
98
+ var index = $X._instanceIndex;
99
+
100
+ $X._instanceIndex += 1;
101
+
102
+ if (typeof $X._instanceCache[index] === 'undefined') {
103
+ $X._instanceCache[index] = instance;
104
+
105
+ return index;
106
+ } else {
107
+ return cacheInstance(instance);
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * new Xooie.Widget(element[, addons])
114
+ * - element (Element | String): A jQuery-selected element or string selector for the root element of this widget
115
+ * - addons (Array): An optional collection of [[Xooie.Addon]] classes to be instantiated with this widget
116
+ *
117
+ * Instantiates a new Xooie widget, or returns an existing widget if it is already associated with the element passed.
118
+ * Any addons passed into the constructor will be instantiated and added to the [[Xooie.Widget#addons]] collection.
119
+ **/
120
+ Widget = function(element, addons) {
121
+ var self = this;
122
+
123
+ element = $(element);
124
+
125
+ //set the default options
126
+ shared.setData(this, element.data());
127
+
128
+ //do instance tracking
129
+ if (element.data('xooieInstance')) {
130
+ if (typeof $X._instanceCache[element.data('xooieInstance')] !== 'undefined'){
131
+ element.trigger(this.get('refreshEvent'));
132
+ return $X._instanceCache[element.data('xooieInstance')];
133
+ } else {
134
+ this.cleanup();
135
+ }
136
+ }
137
+
138
+ element.on(this.get('initEvent') + ' ' + this.get('refreshEvent'), function(){
139
+ self._applyRoles();
140
+ });
141
+
142
+ var id = cacheInstance(this);
143
+
144
+ this.set('id', id);
145
+
146
+ this.set('root', element);
147
+
148
+ element.addClass(this.get('className'))
149
+ .addClass(this.get('instanceClass'));
150
+
151
+ var initCheck = function(){
152
+ var i;
153
+
154
+ if (!self._extendCount || self._extendCount <= 0) {
155
+
156
+ if (typeof addons !== 'undefined') {
157
+ for (i = 0; i < addons.length; i+=1) {
158
+ new addons[i](self);
159
+ }
160
+ }
161
+
162
+ element.attr('data-xooie-instance', id);
163
+
164
+ element.trigger(self.get('initEvent'));
165
+ self._extendCount = null;
166
+ } else {
167
+ setTimeout(initCheck, 0);
168
+ }
169
+ };
170
+
171
+ if (this._extendCount > 0) {
172
+ setTimeout(initCheck, 0);
173
+ } else {
174
+ initCheck();
175
+ }
176
+
177
+ // new keyboardNavigation();
178
+ };
179
+
180
+ /** internal
181
+ * Xooie.Widget._renderMethods -> Object
182
+ *
183
+ * A dispatch table of all supported template render methods.
184
+ *
185
+ * ##### Supported Frameworks
186
+ *
187
+ * - **mustache**: [http://mustache.github.io/]
188
+ * - **jsrender**: [https://github.com/BorisMoore/jsrender]
189
+ * - **underscore**: [http://underscorejs.org/]
190
+ **/
191
+ Widget._renderMethods = {
192
+ //TODO: make this a default template
193
+ 'micro_template': function(template, view) {
194
+ if (typeof template.micro_render !== 'undefined') {
195
+ return $(template.micro_render(view));
196
+ } else {
197
+ return false;
198
+ }
199
+ },
200
+
201
+ 'mustache': function(template, view) {
202
+ if (typeof Mustache !== 'undefined' && typeof Mustache.render !== 'undefined') {
203
+ return $(Mustache.render(template.html(), view));
204
+ } else {
205
+ return false;
206
+ }
207
+ },
208
+
209
+ 'jsrender': function(template, view) {
210
+ if (typeof template.render !== 'undefined') {
211
+ return $(template.render(view));
212
+ } else {
213
+ return false;
214
+ }
215
+ },
216
+
217
+ 'underscore': function(template, view) {
218
+ if (typeof _ !== 'undefined' && typeof _.template !== 'undefined') {
219
+ return $(_.template(template.html())(view).trim());
220
+ } else {
221
+ return false;
222
+ }
223
+ }
224
+ };
225
+
226
+ //CLASS METHODS
227
+
228
+ /**
229
+ * Xooie.Widget.defineWriteOnly(name)
230
+ * - name (String): The name of the property to define as a write-only property
231
+ *
232
+ * See [[Xooie.shared.defineWriteOnly]].
233
+ **/
234
+ Widget.defineWriteOnly = function(name) {
235
+ shared.defineWriteOnly(this, name);
236
+ };
237
+
238
+ /**
239
+ * Xooie.Widget.defineReadOnly(name[, defaultValue])
240
+ * - name (String): The name of the property to define as a read-only property.
241
+ * - defaultValue (Object): An optional default value.
242
+ *
243
+ * See [[Xooie.shared.defineReadOnly]].
244
+ **/
245
+ Widget.defineReadOnly = function(name, defaultValue){
246
+ shared.defineReadOnly(this, name, defaultValue);
247
+ };
248
+
249
+ /**
250
+ * Xooie.Widget.define(name[, defaultValue])
251
+ * - name (String): The name of the property to define.
252
+ * - defaultValue: An optional default value.
253
+ *
254
+ * A method that defines a property as both readable and writable. In reality it calls both [[Xooie.Widget.defineReadOnly]]
255
+ * and [[Xooie.Widget.defineWriteOnly]].
256
+ **/
257
+ Widget.define = function(name, defaultValue){
258
+ this.defineReadOnly(name, defaultValue);
259
+ this.defineWriteOnly(name);
260
+ };
261
+
262
+ /**
263
+ * Xooie.Widget.defineRole(name)
264
+ *
265
+ * TODO: This needs tests and documentation
266
+ **/
267
+ Widget.defineRole = function(name) {
268
+ var role = roleDetails(name);
269
+
270
+ roleDispatcher(name, this.prototype);
271
+
272
+ if (!helpers.isFunction(this.prototype[role.getter])) {
273
+ this.prototype[role.getter] = function() {
274
+ return this.root().find(role.selector);
275
+ };
276
+ }
277
+ };
278
+
279
+ /**
280
+ * Xooie.Widget.extend(constr) -> Widget
281
+ * - constr (Function): The constructor for the new [[Xooie.Widget]] class.
282
+ *
283
+ * See [[Xooie.shared.extend]].
284
+ **/
285
+ Widget.extend = function(constr){
286
+ return shared.extend(constr, this);
287
+ };
288
+
289
+ /**
290
+ * Xooie.Widget.createStyleRule(selector, properties) -> cssRule | undefined
291
+ * - selector (String): The selector used to identify the rule.
292
+ * - properties (Object): A hash of key/value pairs of css properties and values.
293
+ *
294
+ * Creates a new css rule in the Xooie stylesheet. If the rule exists, it will overwrite said rule.
295
+ **/
296
+ // TODO: update so that if the rule exists the properties are added to the rule
297
+ Widget.createStyleRule = function(selector, properties) {
298
+ if (typeof $X._stylesheet.addRule !== 'undefined') {
299
+ return $X._stylesheet.addRule(selector, properties);
300
+ }
301
+ };
302
+
303
+ /**
304
+ * Xooie.Widget.getStyleRule(selector) -> cssRule | undefined
305
+ * - selector (String): The selector used to identify the rule.
306
+ *
307
+ * Retrieves the css rule from the Xooie stylesheet using the provided `selector`. If the rule is not
308
+ * present in [[$X._styleRules]] then the method will check in [[$X._stylesheet]].
309
+ **/
310
+ Widget.getStyleRule = function(selector) {
311
+ if ($X._styleRules.hasOwnProperty(selector)) {
312
+ return $X._styleRules[selector];
313
+ } else {
314
+ return $X._stylesheet.getRule(selector);
315
+ }
316
+ };
317
+
318
+ /** internal
319
+ * Xooie.Widget#_definedProps -> Array
320
+ *
321
+ * A collection of properties that have been defined for this class instance.
322
+ **/
323
+ Widget.prototype._definedProps = [];
324
+
325
+ /** internal
326
+ * Xooie.Widget#_definedRoles -> Array
327
+ *
328
+ * A collection of roles that have been defined for this class instance.
329
+ **/
330
+ Widget.prototype._definedRoles = [];
331
+
332
+ /** internal, read-only
333
+ * Xooie.Widget#_extendCount -> Integer | null
334
+ *
335
+ * Tracks the number of constructors that need to be called.
336
+ **/
337
+ Widget.prototype._extendCount = null;
338
+
339
+ //PROPERTY DEFINITIONS
340
+
341
+ /** internal
342
+ * Xooie.Widget#_id -> Integer
343
+ *
344
+ * The id of this widget. This value is used to keep track of the instance.
345
+ **/
346
+ /**
347
+ * Xooie.Widget#id([value]) -> Integer
348
+ * - value: an optional value to be set.
349
+ *
350
+ * The method for setting or getting [[Xooie.Widget#_id]]. Returns the current value of
351
+ * [[Xooie.Widget#_id]] if no value is passed or sets the value.
352
+ **/
353
+ Widget.define('id');
354
+
355
+ /** internal
356
+ * Xooie.Widget#_root -> Element
357
+ *
358
+ * The root DOM element associated with this widget. This is a jQuery-selected element.
359
+ **/
360
+ /**
361
+ * Xooie.Widget#root([value]) -> Element
362
+ * - value: an optional value to be set.
363
+ *
364
+ * The method for setting or getting [[Xooie.Widget#_root]]. Returns the current value of
365
+ * [[Xooie.Widget#_root]] if no value is passed or sets the value.
366
+ **/
367
+ Widget.define('root');
368
+
369
+ /** internal
370
+ * Xooie.Widget#_namespace -> String
371
+ *
372
+ * The namespace of the widget. This value is used for determining the value of [[Xooie.Widget#className]],
373
+ * [[Xooie.Widget#refreshEvent]], [[Xooie.Widget#initEvent]], and [[Xooie.Widget#instanceClass]].
374
+ **/
375
+ /**
376
+ * Xooie.Widget#namespace([value]) -> String
377
+ * - value: an optional value to be set.
378
+ *
379
+ * The method for setting or getting [[Xooie.Widget#_namespace]]. Returns the current value of
380
+ * [[Xooie.Widget#_namespace]] if no value is passed or sets the value.
381
+ **/
382
+ Widget.define('namespace', '');
383
+
384
+ /** internal
385
+ * Xooie.Widget#_templateLanguage -> String
386
+ *
387
+ * Determines the template framework to use.
388
+ * Default: `micro_template`.
389
+ **/
390
+ /**
391
+ * Xooie.Widget#templateLanguage([value]) -> String
392
+ * - value: an optional value to be set.
393
+ *
394
+ * The method for setting or getting [[Xooie.Widget#_templateLanguage]]. Returns the current value of
395
+ * [[Xooie.Widget#_templateLanguage]] if no value is passed or sets the value.
396
+ **/
397
+ Widget.define('templateLanguage', 'micro_template');
398
+
399
+ /** internal, read-only
400
+ * Xooie.Widget#_addons -> Object
401
+ *
402
+ * A collection of addons instantiated addons associated with this widget.
403
+ * Default: `{}`.
404
+ **/
405
+ /** read-only
406
+ * Xooie.Widget#addons([value]) -> Object
407
+ * - value: an optional value to be set.
408
+ *
409
+ * The method for setting or getting [[Xooie.Widget#_addons]]. Returns the current value of
410
+ * [[Xooie.Widget#_addons]] if no value is passed or sets the value.
411
+ **/
412
+ Widget.defineReadOnly('addons');
413
+
414
+ /** internal, read-only
415
+ * Xooie.Widget#_refreshEvent -> String
416
+ *
417
+ * The name of the event that is triggered when the module is refreshed. Refresh events are triggered
418
+ * when the root element of this widget is passed to [[$X]].
419
+ * Default: `xooie-refresh`.
420
+ **/
421
+ /** read-only
422
+ * Xooie.Widget#refreshEvent() -> String
423
+ *
424
+ * The method for getting [[Xooie.Widget#_refreshEvent]].
425
+ **/
426
+ Widget.defineReadOnly('refreshEvent', 'xooie-refresh');
427
+
428
+ /** internal, read-only
429
+ * Xooie.Widget#_initEvent -> String
430
+ *
431
+ * The name of the event that is triggered when the module is initialized. The initialization event
432
+ * is not triggered until all addons have been instantiated.
433
+ * Default: `xooie-init`.
434
+ **/
435
+ /** read-only
436
+ * Xooie.Widget#initEvent() -> String
437
+ *
438
+ * The method for getting [[Xooie.Widget#_initEvent]].
439
+ **/
440
+ Widget.defineReadOnly('initEvent', 'xooie-init');
441
+
442
+ /** internal, read-only
443
+ * Xooie.Widget#_className -> String
444
+ *
445
+ * The string class name that is applied to the root element of this widget when it is instantiated.
446
+ * Default: `is-instantiated`.
447
+ **/
448
+ /** read-only
449
+ * Xooie.Widget#className() -> String
450
+ *
451
+ * The method for getting [[Xooie.Widget#_className]].
452
+ **/
453
+ Widget.defineReadOnly('className', 'is-instantiated');
454
+
455
+ /** internal, read-only
456
+ * Xooie.Widget#_instanceClass -> String
457
+ *
458
+ * A class that is generated and applied to the root element of the widget.
459
+ * Default: `{{namespace}}-{{id}}`
460
+ **/
461
+ /** read-only
462
+ * Xooie.Widget#instanceClass() -> String
463
+ *
464
+ * The method for getting [[Xooie.Widget#_instanceClass]].
465
+ **/
466
+ Widget.defineReadOnly('instanceClass');
467
+
468
+
469
+ //PROTOTYPE DEFINITIONS
470
+
471
+ /**
472
+ * Xooie.Widget#get(name) -> object
473
+ * - name (String): The name of the property to be retrieved.
474
+ *
475
+ * See [[Xooie.shared.get]].
476
+ **/
477
+ Widget.prototype.get = function(name) {
478
+ return shared.get(this, name);
479
+ };
480
+
481
+ /**
482
+ * Xooie.Widget#set(name, value)
483
+ * - name (String): The name of the property to be set.
484
+ * - value: The value of the property to be set.
485
+ *
486
+ * See [[Xooie.shared.set]].
487
+ **/
488
+ Widget.prototype.set = function(name, value) {
489
+ return shared.set(this, name, value);
490
+ };
491
+
492
+ /**
493
+ * Xooie.Widget#cleanup()
494
+ *
495
+ * Removes the `className` and `instanceClass` classes and `data-xooie-instance` attribute from the root element.
496
+ * Calls [[Xooie.Addon.cleanup]] for each addon. This will permit the instance to be garbage collected.
497
+ **/
498
+ Widget.prototype.cleanup = function() {
499
+ var name;
500
+
501
+ for (name in this.addons()) {
502
+ if (this.addons().hasOwnProperty(name)) {
503
+ this.addons()[name].cleanup();
504
+ }
505
+ }
506
+
507
+ this.root().removeClass(this.className());
508
+ this.root().removeClass(this.instanceClass());
509
+ this.root().attr('data-xooie-instance', false);
510
+ };
511
+
512
+ /**
513
+ * Xooie.Widget#render(template, view) -> Element
514
+ * - template (Element): A jQuery-selected script element that contains the template to be rendered.
515
+ * - view (Object): The data to be passed to the template when it is rendered.
516
+ *
517
+ * Renders the template with the provided data by calling the method in [[Xooie.Widget.renderMethods]] based on the
518
+ * template language specified. Returns `$('<span>Error rendering template</span>')` when an error occurs
519
+ **/
520
+ Widget.prototype.render = function(template, view) {
521
+ var language = template.data('templateLanguage') || this.templateLanguage(),
522
+ result = Widget._renderMethods[language](template, view);
523
+
524
+ if (result === false) {
525
+ return $('<span>Error rendering template</span>');
526
+ } else {
527
+ return result;
528
+ }
529
+ };
530
+
531
+ /** internal
532
+ * Xooie.Widget#_getRoleId(role, index) -> String
533
+ * - role (String): The name of the role for which this id is being generated.
534
+ * - index (Integer): The index at which the particular element exists in the read order.
535
+ *
536
+ * Generates an id string to be applied to an element of the specified role. The format of
537
+ * this id string is `x-[[Xooie.Widget#id]]-{role}-{index}`.
538
+ **/
539
+ Widget.prototype._getRoleId = function(role, index) {
540
+ return 'x-' + this.id() + '-' + role + '-' + index;
541
+ };
542
+
543
+ /** internal
544
+ * Xooie.Widget#_applyRoles()
545
+ *
546
+ * TODO: Test and document.
547
+ **/
548
+ Widget.prototype._applyRoles = function() {
549
+ var i, j, role, elements;
550
+
551
+ for (i=0; i < this._definedRoles.length; i+=1) {
552
+ role = roleDetails(this._definedRoles[i]);
553
+ elements = this[role.getter]();
554
+
555
+ if (elements.length === 0 && helpers.isFunction(this[role.renderer])) {
556
+ elements = this[role.renderer]();
557
+ }
558
+
559
+ if (helpers.isUndefined(elements)) {
560
+ return;
561
+ }
562
+
563
+ for (j=0; j < elements.length; j+=1) {
564
+ $(elements[j]).attr('id', this._getRoleId(this._definedRoles[i], j));
565
+ }
566
+
567
+ if (helpers.isFunction(this[role.processor])) {
568
+ this[role.processor](elements);
569
+ }
570
+ }
571
+ };
572
+
573
+ /** internal
574
+ * Xooie.Widget#_process_addons(addons) -> Object
575
+ * - addons (Object): The collection of instantiated addons for this widget
576
+ *
577
+ * Checks to see if the addons object has been defined. We can't define objects as
578
+ * 'default' values for properties since the object will be the same for each instance.
579
+ **/
580
+ Widget.prototype._process_addons = function(addons){
581
+ if (typeof addons === 'undefined'){
582
+ addons = this._addons = {};
583
+ }
584
+
585
+ return addons;
586
+ };
587
+
588
+ /** internal
589
+ * Xooie.Widget#_process_refreshEvent(refreshEvent) -> String
590
+ * - refreshEvent (String): The unmodified refreshEvent string.
591
+ *
592
+ * Adds the [[Xooie.Widget#namespace]] to the `refreshEvent`
593
+ **/
594
+ Widget.prototype._process_refreshEvent = function(refreshEvent){
595
+ return this.namespace() === '' ? refreshEvent : refreshEvent + '.' + this.namespace();
596
+ };
597
+
598
+ /** internal
599
+ * Xooie.Widget#_process_initEvent(initEvent) -> String
600
+ * - initEvent (String): The unmodified initEvent string.
601
+ *
602
+ * Adds the [[Xooie.Widget#namespace]] to the `initEvent`
603
+ **/
604
+ Widget.prototype._process_initEvent = function(initEvent){
605
+ return this.namespace() === '' ? initEvent : initEvent + '.' + this.namespace();
606
+ };
607
+
608
+ /** internal
609
+ * Xooie.Widget#_process_className(className) -> String
610
+ * - className (String): The unmodified className string.
611
+ *
612
+ * Adds the [[Xooie.Widget#namespace]] to the `className`
613
+ **/
614
+ Widget.prototype._process_className = function(className) {
615
+ return this.namespace() === '' ? className : className + '-' + this.namespace();
616
+ };
617
+
618
+ /** internal
619
+ * Xooie.Widget#_process_instanceClass() -> String
620
+ *
621
+ * Creates an instanceClass string from the [[Xooie.Widget#namespace]] and [[Xooie.Widget#id]].
622
+ **/
623
+ Widget.prototype._process_instanceClass = function() {
624
+ return this.namespace() === '' ? 'widget-' + this.id() : this.namespace() + '-' + this.id();
625
+ };
626
+
627
+ return Widget;
628
+ });