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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +41 -0
- data/lib/generators/t3/behavior_generator.rb +17 -0
- data/lib/generators/t3/bootstrap_generator.rb +42 -0
- data/lib/generators/t3/generator_helper.rb +19 -0
- data/lib/generators/t3/module_generator.rb +17 -0
- data/lib/generators/t3/service_generator.rb +17 -0
- data/lib/generators/templates/behavior.js +45 -0
- data/lib/generators/templates/module.js +45 -0
- data/lib/generators/templates/service.js +45 -0
- data/lib/t3/rails.rb +8 -0
- data/lib/t3/rails/version.rb +6 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +27 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/log/development.log +4 -0
- data/test/dummy/log/test.log +3962 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/app/assets/javascripts/behaviors/validate.js +45 -0
- data/test/generators/behavior_generator_test.rb +21 -0
- data/test/generators/bootstrap_generator_test.rb +75 -0
- data/test/generators/module_generator_test.rb +21 -0
- data/test/generators/service_generator_test.rb +21 -0
- data/test/t3_rails_test.rb +13 -0
- data/test/test_helper.rb +18 -0
- data/vendor/assets/javascripts/t3-testing.js +459 -0
- data/vendor/assets/javascripts/t3.js +1233 -0
- data/vendor/assets/javascripts/t3.min.js +18 -0
- 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
|
+
|