rails_jskit 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,543 @@
1
+ /**
2
+ * JSkit global namespace object
3
+ *
4
+ * @module JSkit
5
+ * @class JSkit
6
+ */
7
+ var JSkit = (function() {
8
+ if (!_) throw new Error("JSkit: lodash or underscore is required");
9
+ if (!$) throw new Error("JSkit: jQuery or equivalent is required");
10
+
11
+ return {
12
+ /**
13
+ * Returns a new Application object.
14
+ *
15
+ * @static
16
+ * @method createApplication
17
+ * @return {Application}
18
+ */
19
+ createApplication: function() {
20
+ return JSkit.Application.create();
21
+ }
22
+ };
23
+ })();
24
+
25
+ /**
26
+ * @module JSkit
27
+ * @class Dispatcher
28
+ */
29
+ JSkit.Dispatcher = (function() {
30
+ var contains = _.contains;
31
+ var pluck = _.pluck;
32
+
33
+ /**
34
+ Get all handler functions for a given dispatcher and event.
35
+
36
+ @method getEventHandlers
37
+ @param {Dispatcher} dispatcher
38
+ @param {String} eventName event name for which you wish to find handlers
39
+ @return {Array} handler functions for the given event
40
+ */
41
+ function getEventHandlers(dispatcher, eventName) {
42
+ dispatcher.__events__[eventName] = dispatcher.__events__[eventName] || [];
43
+ return dispatcher.__events__[eventName];
44
+ }
45
+
46
+ /**
47
+ Create a handler object that contains the context and handler function
48
+
49
+ @method createHandlerObject
50
+ @param {Object,Function} context `this` context for handler function (defaults to `null`)
51
+ @param {Function} handler function to handle event
52
+ @return {Object} handler object with `handler` and `context`
53
+ */
54
+ function createHandlerObject(context, handler) {
55
+ context = context || null;
56
+ return {
57
+ context: context,
58
+ handler: handler
59
+ };
60
+ }
61
+
62
+ /**
63
+ Add an event handler to the array of registered handlers.
64
+
65
+ @method registerHandler
66
+ @param {Array} registeredHandlers registered handlers for an event
67
+ @param {Function} eventHandler to handle event
68
+ @param {String} [method="push"] method to add the handler to the array
69
+ */
70
+ function registerHandler(registeredHandlers, eventHandler, method) {
71
+ method = method || "push";
72
+ if (!contains(pluck(registeredHandlers, "handler"), eventHandler.handler)) {
73
+ registeredHandlers[method](eventHandler);
74
+ }
75
+ }
76
+
77
+ return {
78
+ /**
79
+ Create a new Dispatcher object.
80
+
81
+ @static
82
+ @method create
83
+ @return {Dispatcher}
84
+ */
85
+ create: function() {
86
+ return {
87
+ __events__: {},
88
+
89
+ /**
90
+ Register a handler to a given event.
91
+
92
+ @method on
93
+ @param {String} eventName Name of the event
94
+ @param {Function} handler Function to handle the event
95
+ @param {Controller} [context] `this` context of the handler
96
+ */
97
+ on: function(eventName, handler, context) {
98
+ var eventHandler = createHandlerObject(context, handler);
99
+ var registeredHandlers = getEventHandlers(this, eventName);
100
+ registerHandler(registeredHandlers, eventHandler);
101
+ },
102
+
103
+ /**
104
+ Register a handler to a given event that will
105
+ fire before the handlers registerd with `on`.
106
+
107
+ @method before
108
+ @param {String} eventName Name of the event
109
+ @param {Function} handler Function to handle the event
110
+ @param {Controller} [context] `this` context of the handler
111
+ */
112
+ before: function(eventName, handler, context) {
113
+ var eventHandler = createHandlerObject(context, handler);
114
+ var registeredHandlers = getEventHandlers(this, eventName);
115
+ registerHandler(registeredHandlers, eventHandler, "unshift");
116
+ },
117
+
118
+ /**
119
+ Remove a handler from a given event.
120
+
121
+ @method off
122
+ @param {String} eventName Name of the event
123
+ @param {Function} handler Function to unbind from the `event`
124
+ */
125
+ off: function(eventName, handler) {
126
+ var registeredHandlers = this.__events__[eventName];
127
+ var retainedHandlers = [];
128
+
129
+ if (handler) {
130
+ this.__events__[eventName] = _.reject(registeredHandlers, function(eventHandler) {
131
+ return eventHandler.handler !== handler;
132
+ });
133
+ } else {
134
+ this.__events__[eventName] = [];
135
+ }
136
+ },
137
+
138
+ /**
139
+ Trigger a given event, causing all handlers
140
+ for that event to be fired.
141
+
142
+ @method trigger
143
+ @param {String} eventName Name of the event
144
+ */
145
+ trigger: function(eventName) {
146
+ var eventHhandlers = this.__events__[eventName] || [];
147
+ var args = _.rest(arguments);
148
+
149
+ _.each(eventHhandlers, function(eventHandler) {
150
+ var handler = eventHandler.handler;
151
+ var context = eventHandler.context;
152
+ handler.apply(context, args);
153
+ });
154
+ }
155
+ };
156
+ }
157
+ };
158
+ })();
159
+
160
+ /**
161
+ * @module JSkit
162
+ * @class Controller
163
+ */
164
+ JSkit.Controller = (function() {
165
+ var bindAll = _.bindAll;
166
+ var compact = _.compact;
167
+ var defaults = _.defaults;
168
+ var each = _.each;
169
+ var first = _.first;
170
+ var flatten = _.flatten;
171
+ var functions = _.functions;
172
+ var isFunc = _.isFunction;
173
+ var isObject = _.isObject;
174
+ var keys = _.keys;
175
+ var map = _.map;
176
+ var pairs = _.pairs;
177
+ var uniq = _.uniq;
178
+ var values = _.values;
179
+
180
+ /**
181
+ * Take a string and put underscores between names and delimiters
182
+ *
183
+ * @private
184
+ * @method constantize
185
+ * @param {String} string
186
+ * @return {String}
187
+ */
188
+ function underscore(string) {
189
+ string = string || "";
190
+ return string.replace(/([A-Z])/g, " $1").replace(/^\s?/, "").replace(/-|\s/g, "_").toLowerCase();
191
+ }
192
+
193
+ /**
194
+ * Register the controller's actions to the dispatcher
195
+ *
196
+ * @private
197
+ * @method registerActions
198
+ * @param {Controller} controller
199
+ */
200
+ function registerActions(controller) {
201
+ each(controller.actions, function(action) {
202
+ each(mapAction(action), function(actionMap) {
203
+ ensureActionIsDefined(controller, actionMap);
204
+ controller.dispatcher.on(actionEventName(controller, actionMap.name), controller[actionMap.method], controller);
205
+ }, controller);
206
+ }, controller);
207
+ }
208
+
209
+ /**
210
+ * Take an action string or mapped action and return
211
+ * an object containing the action name and method.
212
+ *
213
+ * @private
214
+ * @method mapAction
215
+ * @param {String,Object} action/mappedAction
216
+ * @return {Array} action/event maps
217
+ */
218
+ function mapAction(action) {
219
+ return isObject(action) ? map(action, createActionMap) : [createActionMap(action, action)];
220
+ }
221
+
222
+ /**
223
+ * Create a list of maps of action name/method pairs.
224
+ *
225
+ * @private
226
+ * @method createActionMap
227
+ * @param {String} method to map to action
228
+ * @param {String} action to map to method
229
+ */
230
+ function createActionMap(method, action) {
231
+ return { name: action, method: method };
232
+ }
233
+
234
+ /**
235
+ * Take a controller and an actionMap and determine if
236
+ * the action is defined on the controller. If not, throw
237
+ * an error.
238
+ *
239
+ * @private
240
+ * @method ensureActionIsDefined
241
+ * @param {Controller} controller
242
+ * @param {Object} actionMap
243
+ */
244
+ function ensureActionIsDefined(controller, actionMap) {
245
+ if (!isFunc(controller[actionMap.method])) {
246
+ throw new Error(controller.name + ' action "' + actionMap.name + ":" + actionMap.method + '" method is undefined');
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Return an event name based on the controller properties
252
+ * that make up an event name.
253
+ *
254
+ * @private
255
+ * @method actionEventName
256
+ * @param {Controller} controller
257
+ * @param {String} action
258
+ * @return String
259
+ */
260
+ function actionEventName(controller, action) {
261
+ return compact([
262
+ controller.namespace,
263
+ controller.channel,
264
+ controller.controllerEventName,
265
+ action
266
+ ]).join(controller.eventSeparator);
267
+ }
268
+
269
+ /**
270
+ * Iterate over the given controller's elements object
271
+ * and cache a reference to each selected element.
272
+ *
273
+ * @private
274
+ * @method cacheElements
275
+ * @param {Controller} controller
276
+ * @param {String} action
277
+ */
278
+ function cacheElements(controller, action) {
279
+ if (controller.elements[action]) {
280
+ each(controller.elements[action], function(selector, name) {
281
+ controller["$" + name] = $(selector);
282
+ }, controller);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Iterate over the given action's events and register
288
+ * the event handlers on each element.
289
+ *
290
+ * @private
291
+ * @method registerEvents
292
+ * @param {Controller} controller
293
+ * @param {String} action
294
+ */
295
+ function registerEvents(controller, action) {
296
+ if (controller.events[action]) {
297
+ each(controller.events[action], function(eventMap, element) {
298
+ each(eventMap, function(method, evnt) {
299
+ var handler = controller[method];
300
+ var $element = controller["$" + element];
301
+ $element.on(evnt, handler);
302
+ }, controller);
303
+ }, controller);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Iterate over the controller's elements
309
+ * and register to cache each action's events.
310
+ *
311
+ * @private
312
+ * @method registerElementCaching
313
+ * @param {Controller} controller
314
+ */
315
+ function registerElementCaching(controller) {
316
+ each(controller.elements, function(elements, action) {
317
+ controller.dispatcher.before(actionEventName(controller, action), function() {
318
+ cacheElements(controller, action);
319
+ }, controller);
320
+ }, controller);
321
+ }
322
+
323
+ /**
324
+ * Iterate over the controller's events
325
+ * and register to handle each action's element events.
326
+ *
327
+ * @private
328
+ * @method registerControllerEvents
329
+ * @param {Controller} controller
330
+ */
331
+ function registerControllerEvents(controller) {
332
+ each(controller.events, function(eventMap, action) {
333
+ controller.dispatcher.on(actionEventName(controller, action), function() {
334
+ registerEvents(controller, action);
335
+ }, controller);
336
+ }, controller);
337
+ }
338
+
339
+ return {
340
+ /**
341
+ * Factory function to create fresh controller objects
342
+ * with the given attributes.
343
+ *
344
+ * @method create
345
+ * @static
346
+ * @param {Object} [attrs={}]
347
+ *
348
+ * @return {Controller}
349
+ */
350
+ create: function(attrs) {
351
+ attrs = attrs || {};
352
+ if (!attrs.name) throw new Error("Controller.create(name): name is undefined");
353
+
354
+ var controller = defaults(attrs, {
355
+ /**
356
+ * Array of actions to be wired up.
357
+ *
358
+ * @property actions
359
+ * @type Array
360
+ * @default []
361
+ */
362
+ actions: [],
363
+ /**
364
+ * Namespace that controller events are namespaced under.
365
+ *
366
+ * @property namespace
367
+ * @type String
368
+ * @default ""
369
+ */
370
+ namespace: "",
371
+ /**
372
+ * Channel that controller events use under namespace.
373
+ *
374
+ * @property channel
375
+ * @type String
376
+ * @default "controller"
377
+ */
378
+ channel: "controller",
379
+ /**
380
+ * Underscored name of controller for use in events.
381
+ *
382
+ * @property controllerEventName
383
+ * @type String
384
+ * @default "controller"
385
+ */
386
+ controllerEventName: underscore(attrs.name),
387
+ /**
388
+ * Event dispatcher for registering events.
389
+ *
390
+ * @property dispatcher
391
+ * @type Dispatcher
392
+ * @default JSkit.Dispatcher.create()
393
+ */
394
+ dispatcher: JSkit.Dispatcher.create(),
395
+ /**
396
+ * Object of element names/selectors to
397
+ * cache per action.
398
+ *
399
+ * @property elements
400
+ * @type Object
401
+ * @default {}
402
+ */
403
+ elements: {},
404
+ /**
405
+ * Object of events for each action to
406
+ * register on given elements.
407
+ *
408
+ * @property events
409
+ * @type Object
410
+ * @default {}
411
+ */
412
+ events: {},
413
+ /**
414
+ * String to seperate event name segments
415
+ *
416
+ * @property eventSeparator
417
+ * @type String
418
+ * @default ":"
419
+ */
420
+ eventSeparator: ":",
421
+ /**
422
+ * Default implementation that commits no operation
423
+ * @method all
424
+ */
425
+ all: function() {},
426
+ /**
427
+ * Default implementation that commits no operation
428
+ * @method initialize
429
+ */
430
+ initialize: function() {}
431
+ });
432
+ bindAll(controller);
433
+ controller.actions.unshift("all");
434
+ registerElementCaching(controller);
435
+ registerControllerEvents(controller);
436
+ registerActions(controller);
437
+
438
+ controller.initialize();
439
+
440
+ return controller;
441
+ }
442
+ };
443
+ })();
444
+
445
+ /**
446
+ * @module JSkit
447
+ * @class Application
448
+ */
449
+ JSkit.Application = (function() {
450
+ var extend = _.extend;
451
+ var map = _.map;
452
+
453
+ /**
454
+ * Takes a string and creates a constant name
455
+ * by uppercasing and camel-casing each word
456
+ * delimited by a space.
457
+ *
458
+ * @private
459
+ * @method constantize
460
+ * @param {String} [string=""]
461
+ * @return {String}
462
+ */
463
+ function constantize(string) {
464
+ string = string || "";
465
+ if (string.match(/_|-|\s/)) {
466
+ var s = map(string.split(/_|-|\s/g), function(part, i) {
467
+ return (i > 0) ? part.charAt(0).toUpperCase() + part.slice(1) : part.toLowerCase();
468
+ }).join("");
469
+ string = s;
470
+ } else {
471
+ string = string.toString();
472
+ }
473
+ return string.charAt(0).toUpperCase() + string.slice(1);
474
+ }
475
+
476
+ return {
477
+ /**
478
+ * Creates a new application object.
479
+ *
480
+ * @method create
481
+ * @static
482
+ * @return {Object} Application object
483
+ */
484
+ create: function() {
485
+ var dispatcher = JSkit.Dispatcher.create();
486
+ return {
487
+ /**
488
+ * Controllers namespace to store Controller objects built at runtime
489
+ *
490
+ * @property Controllers
491
+ * @type Object
492
+ * @default {}
493
+ */
494
+ Controllers: {},
495
+ /**
496
+ * Dispatcher to subcribe and publish events
497
+ *
498
+ * @property Dispatcher
499
+ * @type Dispatcher
500
+ * @default Dispatcher
501
+ */
502
+ Dispatcher: dispatcher,
503
+ /**
504
+ * Creates a controller with the given name and attributes
505
+ * and returns it. It also saves a reference to the Controller
506
+ * factory used to create the controller for testing purposes.
507
+ *
508
+ * @method createController
509
+ * @param {String} name Name of the controller
510
+ * @param {Object} [attributes={}] Controller attributes
511
+ * @return {Controller}
512
+ */
513
+ createController: function(name, attrs) {
514
+ attrs = attrs || {};
515
+ if (!name) throw new Error("Application.createController(name, attrs): name is undefined");
516
+ attrs.name = name;
517
+ var controllerName = constantize(name) + "Controller";
518
+ /**
519
+ * @class ControllerFactory
520
+ */
521
+ var factory = this[controllerName] = {
522
+ /**
523
+ * Creates a fresh controller object with the original defaults
524
+ *
525
+ * @static
526
+ * @method create
527
+ * @param {Object} [attributes]
528
+ * @return {Controller}
529
+ */
530
+ create: function(attributes) {
531
+ attributes = attributes || { name: name };
532
+ return JSkit.Controller.create(extend({}, attrs, attributes));
533
+ }
534
+ };
535
+
536
+ this.Controllers[name] = factory.create({ dispatcher: dispatcher });
537
+
538
+ return this.Controllers[name];
539
+ }
540
+ };
541
+ }
542
+ };
543
+ })();
@@ -0,0 +1,3 @@
1
+ //= require jskit
2
+
3
+ var <%= RailsJskit.configuration.app_namespace %> = JSkit.createApplication();
@@ -0,0 +1,59 @@
1
+ ApplicationController.class_eval do
2
+ helper_method :jskit
3
+
4
+ def jskit(config = { namespace: nil })
5
+ events = [
6
+ application_event(config),
7
+ controller_event(config),
8
+ action_event(config)
9
+ ].join("\n")
10
+
11
+ view_context.javascript_tag(events)
12
+ end
13
+
14
+ def set_action_payload(*args)
15
+ @action_payload = payload_js(args)
16
+ end
17
+
18
+ def set_controller_payload(*args)
19
+ @controller_payload = payload_js(args)
20
+ end
21
+
22
+ def set_app_payload(*args)
23
+ @app_payload = payload_js(args)
24
+ end
25
+
26
+ private
27
+
28
+ def payload_js(payload)
29
+ comma = ", "
30
+ comma + payload.map(&:to_json).join(comma)
31
+ end
32
+
33
+ def action_event(config)
34
+ build_event_trigger(config, controller_name, action_name, @action_payload)
35
+ end
36
+
37
+ def controller_event(config)
38
+ build_event_trigger(config, controller_name, "all", @controller_payload)
39
+ end
40
+
41
+ def application_event(config)
42
+ build_event_trigger(config, "application", "all", @app_payload)
43
+ end
44
+
45
+ def build_event_trigger(config, middle_namespace, final_namespace, payload)
46
+ event = [
47
+ config[:namespace],
48
+ "controller",
49
+ middle_namespace,
50
+ final_namespace
51
+ ].compact.join(":")
52
+
53
+ [
54
+ RailsJskit.configuration.app_namespace,
55
+ "Dispatcher",
56
+ %Q(trigger("#{event}"#{payload});)
57
+ ].join(".")
58
+ end
59
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate jskit Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,8 @@
1
+ <%= RailsJskit.config.app_namespace %>.createController(<%= name %>, {
2
+ actions: [],
3
+
4
+ all: function() {
5
+ console.log("<%= name %>Controller");
6
+ }
7
+ });
8
+
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate jskit Thing
6
+
7
+ This will create:
8
+ what/will/it/create