t3-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +74 -0
  4. data/Rakefile +41 -0
  5. data/lib/generators/t3/behavior_generator.rb +17 -0
  6. data/lib/generators/t3/bootstrap_generator.rb +42 -0
  7. data/lib/generators/t3/generator_helper.rb +19 -0
  8. data/lib/generators/t3/module_generator.rb +17 -0
  9. data/lib/generators/t3/service_generator.rb +17 -0
  10. data/lib/generators/templates/behavior.js +45 -0
  11. data/lib/generators/templates/module.js +45 -0
  12. data/lib/generators/templates/service.js +45 -0
  13. data/lib/t3/rails.rb +8 -0
  14. data/lib/t3/rails/version.rb +6 -0
  15. data/test/dummy/README.rdoc +28 -0
  16. data/test/dummy/Rakefile +6 -0
  17. data/test/dummy/app/assets/javascripts/application.js +13 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  19. data/test/dummy/app/controllers/application_controller.rb +5 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/bin/bundle +3 -0
  23. data/test/dummy/bin/rails +4 -0
  24. data/test/dummy/bin/rake +4 -0
  25. data/test/dummy/bin/setup +29 -0
  26. data/test/dummy/config.ru +4 -0
  27. data/test/dummy/config/application.rb +27 -0
  28. data/test/dummy/config/boot.rb +5 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +41 -0
  31. data/test/dummy/config/environments/production.rb +79 -0
  32. data/test/dummy/config/environments/test.rb +42 -0
  33. data/test/dummy/config/initializers/assets.rb +11 -0
  34. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  36. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  37. data/test/dummy/config/initializers/inflections.rb +16 -0
  38. data/test/dummy/config/initializers/mime_types.rb +4 -0
  39. data/test/dummy/config/initializers/session_store.rb +3 -0
  40. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/test/dummy/config/locales/en.yml +23 -0
  42. data/test/dummy/config/routes.rb +56 -0
  43. data/test/dummy/config/secrets.yml +22 -0
  44. data/test/dummy/log/development.log +4 -0
  45. data/test/dummy/log/test.log +3962 -0
  46. data/test/dummy/public/404.html +67 -0
  47. data/test/dummy/public/422.html +67 -0
  48. data/test/dummy/public/500.html +66 -0
  49. data/test/dummy/public/favicon.ico +0 -0
  50. data/test/dummy/tmp/app/assets/javascripts/behaviors/validate.js +45 -0
  51. data/test/generators/behavior_generator_test.rb +21 -0
  52. data/test/generators/bootstrap_generator_test.rb +75 -0
  53. data/test/generators/module_generator_test.rb +21 -0
  54. data/test/generators/service_generator_test.rb +21 -0
  55. data/test/t3_rails_test.rb +13 -0
  56. data/test/test_helper.rb +18 -0
  57. data/vendor/assets/javascripts/t3-testing.js +459 -0
  58. data/vendor/assets/javascripts/t3.js +1233 -0
  59. data/vendor/assets/javascripts/t3.min.js +18 -0
  60. metadata +172 -0
@@ -0,0 +1,1233 @@
1
+ /*! t3 v 1.4.0*/
2
+ /*!
3
+ Copyright 2015 Box, Inc. All rights reserved.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+ // Start wrapper
18
+ // We use this to make sure we don't assign globals unless we actually want to
19
+ (function(window) {
20
+
21
+ /**
22
+ * @fileoverview Base namespaces for Box JavaScript.
23
+ * @author Box
24
+ */
25
+
26
+ /* eslint-disable no-unused-vars */
27
+
28
+ /**
29
+ * The one global object for Box JavaScript.
30
+ * @namespace
31
+ */
32
+ var Box = {};
33
+ /* eslint-enable no-unused-vars */
34
+
35
+ /**
36
+ * @fileoverview Definition of a custom event type. This is used as a utility
37
+ * throughout the framework whenever custom events are used. It is intended to
38
+ * be inherited from, either through the prototype or via mixin.
39
+ * @author Box
40
+ */
41
+
42
+ Box.EventTarget = (function() {
43
+
44
+ 'use strict';
45
+
46
+ /**
47
+ * An object that is capable of generating custom events and also
48
+ * executing handlers for events when they occur.
49
+ * @constructor
50
+ */
51
+ function EventTarget() {
52
+
53
+ /**
54
+ * Map of events to handlers. The keys in the object are the event names.
55
+ * The values in the object are arrays of event handler functions.
56
+ * @type {Object}
57
+ * @private
58
+ */
59
+ this._handlers = {};
60
+ }
61
+
62
+ EventTarget.prototype = {
63
+
64
+ // restore constructor
65
+ constructor: EventTarget,
66
+
67
+ /**
68
+ * Adds a new event handler for a particular type of event.
69
+ * @param {string} type The name of the event to listen for.
70
+ * @param {Function} handler The function to call when the event occurs.
71
+ * @returns {void}
72
+ */
73
+ on: function(type, handler) {
74
+
75
+ var handlers = this._handlers[type],
76
+ i,
77
+ len;
78
+
79
+ if (typeof handlers === 'undefined') {
80
+ handlers = this._handlers[type] = [];
81
+ }
82
+
83
+ for (i = 0, len = handlers.length; i < len; i++) {
84
+ if (handlers[i] === handler) {
85
+ // prevent duplicate handlers
86
+ return;
87
+ }
88
+ }
89
+
90
+ handlers.push(handler);
91
+ },
92
+
93
+ /**
94
+ * Fires an event with the given name and data.
95
+ * @param {string} type The type of event to fire.
96
+ * @param {Object} [data] An object with properties that should end up on
97
+ * the event object for the given event.
98
+ * @returns {void}
99
+ */
100
+ fire: function(type, data) {
101
+
102
+ var handlers,
103
+ i,
104
+ len,
105
+ event = {
106
+ type: type,
107
+ data: data
108
+ };
109
+
110
+ // if there are handlers for the event, call them in order
111
+ handlers = this._handlers[event.type];
112
+ if (handlers instanceof Array) {
113
+ // @NOTE: do a concat() here to create a copy of the handlers array,
114
+ // so that if another handler is removed of the same type, it doesn't
115
+ // interfere with the handlers array during this loop
116
+ handlers = handlers.concat();
117
+ for (i = 0, len = handlers.length; i < len; i++) {
118
+ handlers[i].call(this, event);
119
+ }
120
+ }
121
+ },
122
+
123
+ /**
124
+ * Removes an event handler from a given event.
125
+ * @param {string} type The name of the event to remove from.
126
+ * @param {Function} handler The function to remove as a handler.
127
+ * @returns {void}
128
+ */
129
+ off: function(type, handler) {
130
+
131
+ var handlers = this._handlers[type],
132
+ i,
133
+ len;
134
+
135
+ if (handlers instanceof Array) {
136
+ for (i = 0, len = handlers.length; i < len; i++) {
137
+ if (handlers[i] === handler) {
138
+ handlers.splice(i, 1);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ }
144
+ };
145
+
146
+ return EventTarget;
147
+
148
+ }());
149
+
150
+ /**
151
+ * @fileoverview DOM abstraction to use jquery to add and remove event listeners
152
+ * in T3
153
+ * @author jdivock
154
+ */
155
+
156
+ Box.JQueryDOM = (function() {
157
+ 'use strict';
158
+
159
+ return {
160
+
161
+ type: 'jquery',
162
+
163
+ /**
164
+ * Returns the first element that is a descendant of the element
165
+ * on which it is invoked that matches the specified group of selectors.
166
+ * @param {HTMLElement} root parent element to query off of
167
+ * @param {string} selector query string to match on
168
+ *
169
+ * @returns {HTMLElement} first element found matching query
170
+ */
171
+ query: function(root, selector) {
172
+ // Aligning with native which returns null if not found
173
+ return $(root).find(selector)[0] || null;
174
+ },
175
+
176
+ /**
177
+ * Returns a non-live NodeList of all elements descended from the
178
+ * element on which it is invoked that match the specified group of CSS selectors.
179
+ * @param {HTMLElement} root parent element to query off of
180
+ * @param {string} selector query string to match on
181
+ *
182
+ * @returns {Array} elements found matching query
183
+ */
184
+ queryAll: function(root, selector) {
185
+ return $.makeArray($(root).find(selector));
186
+ },
187
+
188
+ /**
189
+ * Adds event listener to element via jquery
190
+ * @param {HTMLElement} element Target to attach listener to
191
+ * @param {string} type Name of the action to listen for
192
+ * @param {function} listener Function to be executed on action
193
+ *
194
+ * @returns {void}
195
+ */
196
+ on: function(element, type, listener) {
197
+ $(element).on(type, listener);
198
+ },
199
+
200
+ /**
201
+ * Removes event listener to element via jquery
202
+ * @param {HTMLElement} element Target to remove listener from
203
+ * @param {string} type Name of the action remove listener from
204
+ * @param {function} listener Function to be removed from action
205
+ *
206
+ * @returns {void}
207
+ */
208
+ off: function(element, type, listener) {
209
+ $(element).off(type, listener);
210
+ }
211
+ };
212
+ }());
213
+
214
+ Box.DOM = Box.JQueryDOM;
215
+
216
+ /**
217
+ * @fileoverview Contains the Context type which is used by modules to interact
218
+ * with the environment.
219
+ * @author Box
220
+ */
221
+
222
+ Box.Context = (function() {
223
+
224
+ 'use strict';
225
+
226
+ /**
227
+ * The object type that modules use to interact with the environment. Used
228
+ * exclusively within Box.Application, but exposed publicly for easy testing.
229
+ * @param {Box.Application} application The application object to wrap.
230
+ * @param {HTMLElement} element Module's DOM element
231
+ * @constructor
232
+ */
233
+ function Context(application, element) {
234
+ this.application = application;
235
+ this.element = element;
236
+ }
237
+
238
+ //-------------------------------------------------------------------------
239
+ // Passthrough Methods
240
+ //-------------------------------------------------------------------------
241
+
242
+ Context.prototype = {
243
+
244
+ /**
245
+ * Passthrough method to application that broadcasts messages.
246
+ * @param {string} name Name of the message event
247
+ * @param {*} [data] Custom parameters for the message
248
+ * @returns {void}
249
+ */
250
+ broadcast: function(name, data) {
251
+ this.application.broadcast(name, data);
252
+ },
253
+
254
+ /**
255
+ * Passthrough method to application that retrieves services.
256
+ * @param {string} serviceName The name of the service to retrieve.
257
+ * @returns {Object|null} An object if the service is found or null if not.
258
+ */
259
+ getService: function(serviceName) {
260
+ return this.application.getService(serviceName);
261
+ },
262
+
263
+ /**
264
+ * Returns any configuration information that was output into the page
265
+ * for this instance of the module.
266
+ * @param {string} [name] Specific config parameter
267
+ * @returns {*} config value or the entire configuration JSON object
268
+ * if no name is specified (null if either not found)
269
+ */
270
+ getConfig: function(name) {
271
+ return this.application.getModuleConfig(this.element, name);
272
+ },
273
+
274
+ /**
275
+ * Returns a global variable
276
+ * @param {string} name Specific global var name
277
+ * @returns {*} returns the window-scope variable matching the name, null otherwise
278
+ */
279
+ getGlobal: function(name) {
280
+ return this.application.getGlobal(name);
281
+ },
282
+
283
+ /**
284
+ * Returns global configuration data
285
+ * @param {string} [name] Specific config parameter
286
+ * @returns {*} config value or the entire configuration JSON object
287
+ * if no name is specified (null if either not found)
288
+ */
289
+ getGlobalConfig: function(name) {
290
+ return this.application.getGlobalConfig(name);
291
+ },
292
+
293
+ /**
294
+ * Passthrough method that signals that an error has occurred. If in development mode, an error
295
+ * is thrown. If in production mode, an event is fired.
296
+ * @param {Error} [exception] The exception object to use.
297
+ * @returns {void}
298
+ */
299
+ reportError: function(exception) {
300
+ this.application.reportError(exception);
301
+ },
302
+
303
+ //-------------------------------------------------------------------------
304
+ // Service Shortcuts
305
+ //-------------------------------------------------------------------------
306
+
307
+ /**
308
+ * Returns the element that represents the module.
309
+ * @returns {HTMLElement} The element representing the module.
310
+ */
311
+ getElement: function() {
312
+ return this.element;
313
+ }
314
+
315
+ };
316
+
317
+ return Context;
318
+
319
+ }());
320
+
321
+ /**
322
+ * @fileoverview Contains the main application object that is the heart of the
323
+ * JavaScript architecture.
324
+ * @author Box
325
+ */
326
+
327
+ /**
328
+ * The core application object where components are registered and managed
329
+ * @mixes Box.EventTarget
330
+ * @namespace
331
+ */
332
+ Box.Application = (function() {
333
+
334
+ 'use strict';
335
+
336
+ //--------------------------------------------------------------------------
337
+ // Virtual Types
338
+ //--------------------------------------------------------------------------
339
+
340
+ /**
341
+ * An object representing information about a module.
342
+ * @typedef {Object} Box.Application~ModuleData
343
+ * @property {Function} creator The function that creates an instance of this module.
344
+ * @property {int} counter The number of module instances.
345
+ */
346
+
347
+ /**
348
+ * An object representing information about a module instance.
349
+ * @typedef {Object} Box.Application~ModuleInstanceData
350
+ * @property {string} moduleName The name of the module.
351
+ * @property {Box.Application~ModuleInstance} instance The module instance.
352
+ * @property {Box.Context} context The context object for the module.
353
+ * @property {HTMLElement} element The DOM element associated with the module.
354
+ * @property {Object} eventHandlers Handler callback functions by event type.
355
+ */
356
+
357
+ /**
358
+ * A module object.
359
+ * @typedef {Object} Box.Application~Module
360
+ */
361
+
362
+ //--------------------------------------------------------------------------
363
+ // Private
364
+ //--------------------------------------------------------------------------
365
+
366
+ var MODULE_SELECTOR = '[data-module]';
367
+
368
+ var globalConfig = {}, // Global configuration
369
+ modules = {}, // Information about each registered module by moduleName
370
+ serviceStack = [], // Track circular dependencies while loading services
371
+ services = {}, // Information about each registered service by serviceName
372
+ behaviors = {}, // Information about each registered behavior by behaviorName
373
+ instances = {}, // Module instances keyed by DOM element id
374
+ exports = [], // Method names that were added to application/context by services
375
+ initialized = false, // Flag whether the application has been initialized
376
+
377
+ application = new Box.EventTarget(); // base object for application
378
+
379
+ // Supported events for modules. Only events that bubble properly can be used in T3.
380
+ var eventTypes = ['click', 'mouseover', 'mouseout', 'mousedown', 'mouseup',
381
+ 'mouseenter', 'mouseleave', 'keydown', 'keyup', 'submit', 'change',
382
+ 'contextmenu', 'dblclick', 'input', 'focusin', 'focusout'];
383
+
384
+ /**
385
+ * Simple implementation of ES6 Object.assign() with just two parameters.
386
+ * @param {Object} receiver The object to receive properties
387
+ * @param {Object} supplier The object whose properties should be copied.
388
+ * @returns {Object} The receiver object.
389
+ * @private
390
+ */
391
+ function assign(receiver, supplier) {
392
+
393
+ for (var prop in supplier) {
394
+ if (supplier.hasOwnProperty(prop)) {
395
+ receiver[prop] = supplier[prop];
396
+ }
397
+ }
398
+
399
+ return receiver;
400
+ }
401
+
402
+ /**
403
+ * Creates a new version of a function whose this-value is bound to a specific
404
+ * object.
405
+ * @param {Function} method The function to bind.
406
+ * @param {Object} thisValue The this-value to set for the function.
407
+ * @returns {Function} A bound version of the function.
408
+ * @private
409
+ */
410
+ function bind(method, thisValue) {
411
+ return function() {
412
+ return method.apply(thisValue, arguments);
413
+ };
414
+ }
415
+
416
+ /**
417
+ * Simple implementation of Array.prototype.indexOf().
418
+ * @param {*[]} items An array of items to search.
419
+ * @param {*} item The item to search for in the array.
420
+ * @returns {int} The index of the item in the array if found, -1 if not found.
421
+ * @private
422
+ */
423
+ function indexOf(items, item) {
424
+ for (var i = 0, len = items.length; i < len; i++) {
425
+ if (items[i] === item) {
426
+ return i;
427
+ }
428
+ }
429
+
430
+ return -1;
431
+ }
432
+
433
+ /**
434
+ * Reset all state to its default values
435
+ * @returns {void}
436
+ * @private
437
+ */
438
+ function reset() {
439
+ globalConfig = {};
440
+ modules = {};
441
+ services = {};
442
+ serviceStack = [];
443
+ behaviors = {};
444
+ instances = {};
445
+ initialized = false;
446
+
447
+ for (var i = 0; i < exports.length; i++) {
448
+ delete application[exports[i]];
449
+ delete Box.Context.prototype[exports[i]];
450
+ }
451
+ exports = [];
452
+ }
453
+
454
+
455
+ /**
456
+ * Indicates if a given service is being instantiated. This is used to check
457
+ * for circular dependencies in service instantiation. If two services
458
+ * reference each other, it causes a stack overflow and is really hard to
459
+ * track down, so we provide an extra check to make finding this issue
460
+ * easier.
461
+ * @param {string} serviceName The name of the service to check.
462
+ * @returns {boolean} True if the service is already being instantiated,
463
+ * false if not.
464
+ * @private
465
+ */
466
+ function isServiceBeingInstantiated(serviceName) {
467
+ for (var i = 0, len = serviceStack.length; i < len; i++) {
468
+ if (serviceStack[i] === serviceName) {
469
+ return true;
470
+ }
471
+ }
472
+
473
+ return false;
474
+ }
475
+
476
+ /**
477
+ * Signals that an error has occurred. If in development mode, an error
478
+ * is thrown. If in production mode, an event is fired.
479
+ * @param {Error} [exception] The exception object to use.
480
+ * @returns {void}
481
+ * @private
482
+ */
483
+ function error(exception) {
484
+
485
+ if (globalConfig.debug) {
486
+ throw exception;
487
+ } else {
488
+ application.fire('error', {
489
+ exception: exception
490
+ });
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Wraps all methods on an object with try-catch so that objects don't need
496
+ * to worry about trapping their own errors. When an error occurs, the
497
+ * error event is fired with the error information.
498
+ * @see http://www.nczonline.net/blog/2009/04/28/javascript-error-handling-anti-pattern/
499
+ * @param {Object} object Any object whose public methods should be wrapped.
500
+ * @param {string} objectName The name that should be reported for the object
501
+ * when an error occurs.
502
+ * @returns {void}
503
+ * @private
504
+ */
505
+ function captureObjectErrors(object, objectName) {
506
+
507
+ var propertyName,
508
+ propertyValue;
509
+
510
+ /* eslint-disable guard-for-in, no-loop-func */
511
+ for (propertyName in object) {
512
+ propertyValue = object[propertyName];
513
+
514
+ // only do this for methods, be sure to check before making changes!
515
+ if (typeof propertyValue === 'function') {
516
+ /*
517
+ * This creates a new function that wraps the original function
518
+ * in a try-catch. The outer function executes immediately with
519
+ * the name and actual method passed in as values. This allows
520
+ * us to create a function with specific information even though
521
+ * it's inside of a loop.
522
+ */
523
+ object[propertyName] = (function(methodName, method) {
524
+ return function() {
525
+ var errorPrefix = objectName + '.' + methodName + '() - ';
526
+ try {
527
+ return method.apply(this, arguments);
528
+ } catch (ex) {
529
+ ex.name = errorPrefix + ex.name;
530
+ ex.message = errorPrefix + ex.message;
531
+ error(ex);
532
+ }
533
+ };
534
+
535
+ }(propertyName, propertyValue));
536
+ }
537
+ }
538
+ /* eslint-enable guard-for-in, no-loop-func */
539
+ }
540
+
541
+ /**
542
+ * Returns the name of the module associated with a DOM element
543
+ * @param {HTMLElement} element DOM element associated with the module
544
+ * @returns {string} Name of the module (empty if not a module)
545
+ * @private
546
+ */
547
+ function getModuleName(element) {
548
+ var moduleAttribute = element.getAttribute('data-module');
549
+
550
+ if (moduleAttribute) {
551
+ return moduleAttribute.split(' ')[0];
552
+ }
553
+ return '';
554
+ }
555
+
556
+ /**
557
+ * Determines if a given element represents a module.
558
+ * @param {HTMLElement} element The element to check.
559
+ * @returns {boolean} True if the element represents a module, false if not.
560
+ * @private
561
+ */
562
+ function isModuleElement(element) {
563
+ return element && element.hasAttribute('data-module');
564
+ }
565
+
566
+ /**
567
+ * Determines if a given element represents a T3 type.
568
+ * @param {HTMLElement} element The element to check.
569
+ * @returns {boolean} True if the element represents a T3 type, false if not.
570
+ * @private
571
+ */
572
+ function isTypeElement(element) {
573
+ return element && element.hasAttribute('data-type');
574
+ }
575
+
576
+ /**
577
+ * Calls a method on an object if it exists
578
+ * @param {Box.Application~ModuleInstance} instance Module object to call the method on.
579
+ * @param {string} method Name of method
580
+ * @param {...*} [args] Any additional arguments are passed as function parameters (Optional)
581
+ * @returns {void}
582
+ * @private
583
+ */
584
+ function callModuleMethod(instance, method) {
585
+ if (typeof instance[method] === 'function') {
586
+ // Getting the rest of the parameters (the ones other than instance and method)
587
+ instance[method].apply(instance, Array.prototype.slice.call(arguments, 2));
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Returns the requested service
593
+ * @param {string} serviceName The name of the service to retrieve.
594
+ * @returns {!Object} An object if the service is found or null if not.
595
+ * @private
596
+ */
597
+ function getService(serviceName) {
598
+
599
+ var serviceData = services[serviceName];
600
+
601
+ if (serviceData) {
602
+
603
+ // check for circular dependencies
604
+ if (isServiceBeingInstantiated(serviceName)) {
605
+ error(new ReferenceError('Circular service dependency: ' + serviceStack.join(' -> ') + ' -> ' + serviceName));
606
+ return null;
607
+ }
608
+
609
+ // flag that this service is being initialized just in case there's a circular dependency issue
610
+ serviceStack.push(serviceName);
611
+
612
+ if (!serviceData.instance) {
613
+ serviceData.instance = serviceData.creator(application);
614
+ }
615
+
616
+ // no error was thrown for circular dependencies, so we're done
617
+ serviceStack.pop();
618
+
619
+ return serviceData.instance;
620
+ }
621
+
622
+ return null;
623
+ }
624
+
625
+ /**
626
+ * Gets the behaviors associated with a particular module
627
+ * @param {Box.Application~ModuleInstanceData} instanceData Module with behaviors
628
+ * @returns {Array} Array of behavior instances
629
+ * @private
630
+ */
631
+ function getBehaviors(instanceData) {
632
+ var i,
633
+ behaviorNames,
634
+ behaviorData,
635
+ behaviorInstances = [],
636
+ moduleBehaviorInstances;
637
+
638
+ behaviorNames = instanceData.instance.behaviors || [];
639
+ for (i = 0; i < behaviorNames.length; i++) {
640
+ if (!('behaviorInstances' in instanceData)) {
641
+ instanceData.behaviorInstances = {};
642
+ }
643
+ moduleBehaviorInstances = instanceData.behaviorInstances;
644
+ behaviorData = behaviors[behaviorNames[i]];
645
+
646
+ if (behaviorData) {
647
+ if (!moduleBehaviorInstances[behaviorNames[i]]) {
648
+ moduleBehaviorInstances[behaviorNames[i]] = behaviorData.creator(instanceData.context);
649
+ }
650
+ behaviorInstances.push(moduleBehaviorInstances[behaviorNames[i]]);
651
+ } else {
652
+ error(new Error('Behavior "' + behaviorNames[i] + '" not found'));
653
+ }
654
+ }
655
+
656
+ return behaviorInstances;
657
+ }
658
+
659
+ /**
660
+ * Finds the closest ancestor that of an element that has a data-type
661
+ * attribute.
662
+ * @param {HTMLElement} element The element to start searching from.
663
+ * @returns {HTMLElement} The matching element or null if not found.
664
+ */
665
+ function getNearestTypeElement(element) {
666
+ var found = isTypeElement(element);
667
+
668
+
669
+ // We need to check for the existence of 'element' since occasionally we call this on a detached element node.
670
+ // For example:
671
+ // 1. event handlers like mouseout may sometimes detach nodes from the DOM
672
+ // 2. event handlers like mouseleave will still fire on the detached node
673
+ // Without checking the existence of a parentNode and returning null, we would throw errors
674
+ while (!found && element && !isModuleElement(element)) {
675
+ element = element.parentNode;
676
+ found = isTypeElement(element);
677
+ }
678
+
679
+ return found ? element : null;
680
+ }
681
+
682
+ /**
683
+ * Binds a user event to a DOM element with the given handler
684
+ * @param {HTMLElement} element DOM element to bind the event to
685
+ * @param {string} type Event type (click, mouseover, ...)
686
+ * @param {Function[]} handlers Array of event callbacks to be called in that order
687
+ * @returns {Function} The event handler
688
+ * @private
689
+ */
690
+ function bindEventType(element, type, handlers) {
691
+
692
+ function eventHandler(event) {
693
+
694
+ var targetElement = getNearestTypeElement(event.target),
695
+ elementType = targetElement ? targetElement.getAttribute('data-type') : '';
696
+
697
+ for (var i = 0; i < handlers.length; i++) {
698
+ handlers[i](event, targetElement, elementType);
699
+ }
700
+
701
+ return true;
702
+ }
703
+
704
+ Box.DOM.on(element, type, eventHandler);
705
+
706
+ return eventHandler;
707
+ }
708
+
709
+ /**
710
+ * Binds the user events listed in the module to its toplevel element
711
+ * @param {Box.Application~ModuleInstanceData} instanceData Events will be bound to the module defined in the Instance object
712
+ * @returns {void}
713
+ * @private
714
+ */
715
+ function bindEventListeners(instanceData) {
716
+ var i,
717
+ j,
718
+ type,
719
+ eventHandlerName,
720
+ eventHandlerFunctions,
721
+ moduleBehaviors = getBehaviors(instanceData);
722
+
723
+ for (i = 0; i < eventTypes.length; i++) {
724
+ eventHandlerFunctions = [];
725
+
726
+ type = eventTypes[i];
727
+ eventHandlerName = 'on' + type;
728
+
729
+ // Module's event handler gets called first
730
+ if (instanceData.instance[eventHandlerName]) {
731
+ eventHandlerFunctions.push(bind(instanceData.instance[eventHandlerName], instanceData.instance));
732
+ }
733
+
734
+ // And then all of its behaviors in the order they were declared
735
+ for (j = 0; j < moduleBehaviors.length; j++) {
736
+ if (moduleBehaviors[j][eventHandlerName]) {
737
+ eventHandlerFunctions.push(bind(moduleBehaviors[j][eventHandlerName], moduleBehaviors[j]));
738
+ }
739
+ }
740
+
741
+ if (eventHandlerFunctions.length) {
742
+ instanceData.eventHandlers[type] = bindEventType(instanceData.element, type, eventHandlerFunctions);
743
+ }
744
+ }
745
+ }
746
+
747
+ /**
748
+ * Unbinds the user events listed in the module
749
+ * @param {Box.Application~ModuleInstanceData} instanceData Events will be unbound from the module defined in the Instance object
750
+ * @returns {void}
751
+ * @private
752
+ */
753
+ function unbindEventListeners(instanceData) {
754
+ for (var type in instanceData.eventHandlers) {
755
+ if (instanceData.eventHandlers.hasOwnProperty(type)) {
756
+ Box.DOM.off(instanceData.element, type, instanceData.eventHandlers[type]);
757
+ }
758
+ }
759
+
760
+ instanceData.eventHandlers = {};
761
+ }
762
+
763
+ /**
764
+ * Gets the module instance associated with a DOM element
765
+ * @param {HTMLElement} element DOM element associated with module
766
+ * @returns {Box.Application~ModuleInstance} Instance object of the module (undefined if not found)
767
+ * @private
768
+ */
769
+ function getInstanceDataByElement(element) {
770
+ return instances[element.id];
771
+ }
772
+
773
+ //--------------------------------------------------------------------------
774
+ // Public
775
+ //--------------------------------------------------------------------------
776
+
777
+ /** @lends Box.Application */
778
+ return assign(application, {
779
+
780
+ //----------------------------------------------------------------------
781
+ // Application Lifecycle
782
+ //----------------------------------------------------------------------
783
+
784
+ /**
785
+ * Initializes the application
786
+ * @param {Object} [params] Configuration object
787
+ * @returns {void}
788
+ */
789
+ init: function(params) {
790
+ assign(globalConfig, params || {});
791
+
792
+ this.startAll(document.documentElement);
793
+
794
+ this.fire('init');
795
+ initialized = true;
796
+ },
797
+
798
+ /**
799
+ * Stops all modules and clears all saved state
800
+ * @returns {void}
801
+ */
802
+ destroy: function() {
803
+ this.stopAll(document.documentElement);
804
+
805
+ reset();
806
+ },
807
+
808
+ //----------------------------------------------------------------------
809
+ // Module Lifecycle
810
+ //----------------------------------------------------------------------
811
+
812
+ /**
813
+ * Determines if a module represented by the HTML element is started.
814
+ * If the element doesn't have a data-module attribute, this method
815
+ * always returns false.
816
+ * @param {HTMLElement} element The element that represents a module.
817
+ * @returns {Boolean} True if the module is started, false if not.
818
+ */
819
+ isStarted: function(element) {
820
+ var instanceData = getInstanceDataByElement(element);
821
+ return (typeof instanceData === 'object');
822
+ },
823
+
824
+ /**
825
+ * Begins the lifecycle of a module (registers and binds listeners)
826
+ * @param {HTMLElement} element DOM element associated with module to be started
827
+ * @returns {void}
828
+ */
829
+ start: function(element) {
830
+ var moduleName = getModuleName(element),
831
+ moduleData = modules[moduleName],
832
+ instanceData,
833
+ context,
834
+ module;
835
+
836
+ if (!moduleData) {
837
+ error(new Error('Module type "' + moduleName + '" is not defined.'));
838
+ return;
839
+ }
840
+
841
+ if (!this.isStarted(element)) {
842
+ // Auto-assign module id to element
843
+ if (!element.id) {
844
+ element.id = 'mod-' + moduleName + '-' + moduleData.counter;
845
+ }
846
+
847
+ moduleData.counter++;
848
+
849
+ context = new Box.Context(this, element);
850
+
851
+ module = moduleData.creator(context);
852
+
853
+ // Prevent errors from showing the browser, fire event instead
854
+ if (!globalConfig.debug) {
855
+ captureObjectErrors(module, moduleName);
856
+ }
857
+
858
+ instanceData = {
859
+ moduleName: moduleName,
860
+ instance: module,
861
+ context: context,
862
+ element: element,
863
+ eventHandlers: {}
864
+ };
865
+
866
+ bindEventListeners(instanceData);
867
+
868
+ instances[element.id] = instanceData;
869
+
870
+ callModuleMethod(instanceData.instance, 'init');
871
+
872
+ var moduleBehaviors = getBehaviors(instanceData),
873
+ behaviorInstance;
874
+
875
+ for (var i = 0, len = moduleBehaviors.length; i < len; i++) {
876
+ behaviorInstance = moduleBehaviors[i];
877
+ callModuleMethod(behaviorInstance, 'init');
878
+ }
879
+
880
+ }
881
+ },
882
+
883
+ /**
884
+ * Ends the lifecycle of a module (unregisters and unbinds listeners)
885
+ * @param {HTMLElement} element DOM element associated with module to be stopped
886
+ * @returns {void}
887
+ */
888
+ stop: function(element) {
889
+ var instanceData = getInstanceDataByElement(element);
890
+
891
+ if (!instanceData) {
892
+
893
+ if (globalConfig.debug) {
894
+ error(new Error('Unable to stop module associated with element: ' + element.id));
895
+ return;
896
+ }
897
+
898
+ } else {
899
+
900
+ unbindEventListeners(instanceData);
901
+
902
+ // Call these in reverse order
903
+ var moduleBehaviors = getBehaviors(instanceData);
904
+ var behaviorInstance;
905
+ for (var i = moduleBehaviors.length - 1; i >= 0; i--) {
906
+ behaviorInstance = moduleBehaviors[i];
907
+ callModuleMethod(behaviorInstance, 'destroy');
908
+ }
909
+
910
+ callModuleMethod(instanceData.instance, 'destroy');
911
+
912
+ delete instances[element.id];
913
+ }
914
+ },
915
+
916
+ /**
917
+ * Starts all modules contained within an element
918
+ * @param {HTMLElement} root DOM element which contains modules
919
+ * @returns {void}
920
+ */
921
+ startAll: function(root) {
922
+ var moduleElements = Box.DOM.queryAll(root, MODULE_SELECTOR);
923
+
924
+ for (var i = 0, len = moduleElements.length; i < len; i++) {
925
+ this.start(moduleElements[i]);
926
+ }
927
+ },
928
+
929
+ /**
930
+ * Stops all modules contained within an element
931
+ * @param {HTMLElement} root DOM element which contains modules
932
+ * @returns {void}
933
+ */
934
+ stopAll: function(root) {
935
+ var moduleElements = Box.DOM.queryAll(root, MODULE_SELECTOR);
936
+
937
+ for (var i = 0, len = moduleElements.length; i < len; i++) {
938
+ this.stop(moduleElements[i]);
939
+ }
940
+ },
941
+
942
+ //----------------------------------------------------------------------
943
+ // Module-Related
944
+ //----------------------------------------------------------------------
945
+
946
+ /**
947
+ * Registers a new module
948
+ * @param {string} moduleName Unique module identifier
949
+ * @param {Function} creator Factory function used to generate the module
950
+ * @returns {void}
951
+ */
952
+ addModule: function(moduleName, creator) {
953
+ if (typeof modules[moduleName] !== 'undefined') {
954
+ error(new Error('Module ' + moduleName + ' has already been added.'));
955
+ return;
956
+ }
957
+
958
+ modules[moduleName] = {
959
+ creator: creator,
960
+ counter: 1 // increments for each new instance
961
+ };
962
+ },
963
+
964
+ /**
965
+ * Returns any configuration information that was output into the page
966
+ * for this instance of the module.
967
+ * @param {HTMLElement} element The HTML element associated with a module.
968
+ * @param {string} [name] Specific config parameter
969
+ * @returns {*} config value or the entire configuration JSON object
970
+ * if no name is specified (null if either not found)
971
+ */
972
+ getModuleConfig: function(element, name) {
973
+
974
+ var instanceData = getInstanceDataByElement(element),
975
+ configElement;
976
+
977
+ if (instanceData) {
978
+
979
+ if (!instanceData.config) {
980
+ // <script type="text/x-config"> is used to store JSON data
981
+ configElement = Box.DOM.query(element, 'script[type="text/x-config"]');
982
+
983
+ // <script> tag supports .text property
984
+ if (configElement) {
985
+ instanceData.config = JSON.parse(configElement.text);
986
+ }
987
+ }
988
+
989
+ if (!instanceData.config) {
990
+ return null;
991
+ } else if (typeof name === 'undefined') {
992
+ return instanceData.config;
993
+ } else if (name in instanceData.config) {
994
+ return instanceData.config[name];
995
+ } else {
996
+ return null;
997
+ }
998
+ }
999
+
1000
+ return null;
1001
+ },
1002
+
1003
+ //----------------------------------------------------------------------
1004
+ // Service-Related
1005
+ //----------------------------------------------------------------------
1006
+
1007
+ /**
1008
+ * Registers a new service
1009
+ * @param {string} serviceName Unique service identifier
1010
+ * @param {Function} creator Factory function used to generate the service
1011
+ * @param {Object} [options] Additional options
1012
+ * @param {string[]} [options.exports] Method names to expose on context and application
1013
+ * @returns {void}
1014
+ */
1015
+ addService: function(serviceName, creator, options) {
1016
+
1017
+ if (typeof services[serviceName] !== 'undefined') {
1018
+ error(new Error('Service ' + serviceName + ' has already been added.'));
1019
+ return;
1020
+ }
1021
+
1022
+ options = options || {};
1023
+
1024
+ services[serviceName] = {
1025
+ creator: creator,
1026
+ instance: null
1027
+ };
1028
+
1029
+ if (options.exports) {
1030
+ var i,
1031
+ length = options.exports.length;
1032
+
1033
+ for (i = 0; i < length; i++) {
1034
+
1035
+ var exportedMethodName = options.exports[i];
1036
+
1037
+ /* eslint-disable no-loop-func */
1038
+ var handler = (function(methodName) {
1039
+ return function() {
1040
+ var service = getService(serviceName);
1041
+ return service[methodName].apply(service, arguments);
1042
+ };
1043
+ }(exportedMethodName));
1044
+ /* eslint-enable no-loop-func */
1045
+
1046
+ if (exportedMethodName in this) {
1047
+ error(new Error(exportedMethodName + ' already exists on Application object'));
1048
+ return;
1049
+ } else {
1050
+ this[exportedMethodName] = handler;
1051
+ }
1052
+
1053
+ if (exportedMethodName in Box.Context.prototype) {
1054
+ error(new Error(exportedMethodName + ' already exists on Context prototype'));
1055
+ return;
1056
+ } else {
1057
+ Box.Context.prototype[exportedMethodName] = handler;
1058
+ }
1059
+
1060
+ exports.push(exportedMethodName);
1061
+ }
1062
+ }
1063
+ },
1064
+
1065
+ /**
1066
+ * Returns the requested service
1067
+ * @param {string} serviceName The name of the service to retrieve.
1068
+ * @returns {!Object} An object if the service is found or null if not.
1069
+ */
1070
+ getService: getService,
1071
+
1072
+
1073
+ //----------------------------------------------------------------------
1074
+ // Behavior-Related
1075
+ //----------------------------------------------------------------------
1076
+
1077
+ /**
1078
+ * Registers a new behavior
1079
+ * @param {string} behaviorName Unique behavior identifier
1080
+ * @param {Function} creator Factory function used to generate the behavior
1081
+ * @returns {void}
1082
+ */
1083
+ addBehavior: function(behaviorName, creator) {
1084
+ if (typeof behaviors[behaviorName] !== 'undefined') {
1085
+ error(new Error('Behavior ' + behaviorName + ' has already been added.'));
1086
+ return;
1087
+ }
1088
+
1089
+ behaviors[behaviorName] = {
1090
+ creator: creator,
1091
+ instance: null
1092
+ };
1093
+ },
1094
+
1095
+ //----------------------------------------------------------------------
1096
+ // Messaging
1097
+ //----------------------------------------------------------------------
1098
+
1099
+ /**
1100
+ * Broadcasts a message to all registered listeners
1101
+ * @param {string} name Name of the message
1102
+ * @param {*} [data] Custom parameters for the message
1103
+ * @returns {void}
1104
+ */
1105
+ broadcast: function(name, data) {
1106
+ var i,
1107
+ id,
1108
+ instanceData,
1109
+ behaviorInstance,
1110
+ moduleBehaviors,
1111
+ messageHandlers;
1112
+
1113
+ for (id in instances) {
1114
+
1115
+ if (instances.hasOwnProperty(id)) {
1116
+ messageHandlers = [];
1117
+ instanceData = instances[id];
1118
+
1119
+ // Module message handler is called first
1120
+ if (indexOf(instanceData.instance.messages || [], name) !== -1) {
1121
+ messageHandlers.push(bind(instanceData.instance.onmessage, instanceData.instance));
1122
+ }
1123
+
1124
+ // And then any message handlers defined in module's behaviors
1125
+ moduleBehaviors = getBehaviors(instanceData);
1126
+ for (i = 0; i < moduleBehaviors.length; i++) {
1127
+ behaviorInstance = moduleBehaviors[i];
1128
+
1129
+ if (indexOf(behaviorInstance.messages || [], name) !== -1) {
1130
+ messageHandlers.push(bind(behaviorInstance.onmessage, behaviorInstance));
1131
+ }
1132
+ }
1133
+
1134
+ for (i = 0; i < messageHandlers.length; i++) {
1135
+ messageHandlers[i](name, data);
1136
+ }
1137
+ }
1138
+
1139
+ }
1140
+
1141
+ // also fire an event so non-T3 code can listen for the message
1142
+ this.fire('message', {
1143
+ message: name,
1144
+ messageData: data
1145
+ });
1146
+ },
1147
+
1148
+ //----------------------------------------------------------------------
1149
+ // Global Configuration
1150
+ //----------------------------------------------------------------------
1151
+
1152
+ /**
1153
+ * Returns a global variable
1154
+ * @param {string} name Specific global var name
1155
+ * @returns {*} returns the window-scope variable matching the name, null otherwise
1156
+ */
1157
+ getGlobal: function(name) {
1158
+ if (name in window) {
1159
+ return window[name];
1160
+ } else {
1161
+ return null;
1162
+ }
1163
+ },
1164
+
1165
+ /**
1166
+ * Returns global configuration data
1167
+ * @param {string} [name] Specific config parameter
1168
+ * @returns {*} config value or the entire configuration JSON object
1169
+ * if no name is specified (null if neither not found)
1170
+ */
1171
+ getGlobalConfig: function(name) {
1172
+ if (typeof name === 'undefined') {
1173
+ return globalConfig;
1174
+ } else if (name in globalConfig) {
1175
+ return globalConfig[name];
1176
+ } else {
1177
+ return null;
1178
+ }
1179
+ },
1180
+
1181
+ /**
1182
+ * Sets the global configuration data
1183
+ * @param {Object} config Global configuration object
1184
+ * @returns {void}
1185
+ */
1186
+ setGlobalConfig: function(config) {
1187
+ if (initialized) {
1188
+ error(new Error('Cannot set global configuration after application initialization'));
1189
+ return;
1190
+ }
1191
+
1192
+ assign(globalConfig, config);
1193
+ },
1194
+
1195
+ //----------------------------------------------------------------------
1196
+ // Error reporting
1197
+ //----------------------------------------------------------------------
1198
+
1199
+ /**
1200
+ * Signals that an error has occurred. If in development mode, an error
1201
+ * is thrown. If in production mode, an event is fired.
1202
+ * @param {Error} [exception] The exception object to use.
1203
+ * @returns {void}
1204
+ */
1205
+ reportError: error
1206
+ });
1207
+
1208
+ }());
1209
+
1210
+ if (typeof define === 'function' && define.amd) {
1211
+ // AMD
1212
+ define('t3', [], function() {
1213
+ return Box;
1214
+ });
1215
+ } else if (typeof module === 'object' && typeof module.exports === 'object') {
1216
+ // CommonJS/npm, we want to export Box instead of assigning to global Window
1217
+ module.exports = Box;
1218
+ } else {
1219
+ // Make sure not to override Box namespace
1220
+ window.Box = window.Box || {};
1221
+
1222
+ // Copy all properties onto namespace (ES3 safe for loop)
1223
+ for (var key in Box) {
1224
+ if (Box.hasOwnProperty(key)) {
1225
+ window.Box[key] = Box[key];
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // Potentially window is not defined yet, so bind to 'this' instead
1231
+ }(typeof window !== 'undefined' ? window : this));
1232
+ // End Wrapper
1233
+