stormfront-rails 0.2.3 → 0.10.3

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.
@@ -1,5 +1,5 @@
1
1
  var stormfront = { };
2
- var Stormfront = { Mixin: { } };
2
+ var Stormfront = { };
3
3
 
4
4
  Stormfront.VERSION = '1.0.0';
5
5
 
@@ -8,36 +8,49 @@ Stormfront.Class = function(options) {
8
8
  this.initialize.apply(this, arguments);
9
9
  };
10
10
 
11
- Stormfront.Class.extend = function(protoProps, staticProps) {
12
- var parent = this;
13
- var child;
14
-
15
- if (protoProps && _.has(protoProps, 'constructor'))
16
- child = protoProps.constructor;
17
- else
18
- child = function(){ return parent.apply(this, arguments); };
19
-
20
- _.extend(child, parent, staticProps);
21
-
22
- var Surrogate = function(){ this.constructor = child; };
23
- Surrogate.prototype = parent.prototype;
24
- child.prototype = new Surrogate;
25
-
26
- if (protoProps) _.extend(child.prototype, protoProps);
27
- child.__super__ = parent.prototype;
11
+ Stormfront.Class.extend = function(protoProps) {
12
+ function copyConstructor(base){
13
+ return function(){ return base.apply(this, arguments); };
14
+ }
15
+ function copyBaseProperties(child, parent){
16
+ _.extend(child, parent);
17
+ }
18
+ function carryPrototypeChain(child, parent){
19
+ var Surrogate = function(){ this.constructor = child; };
20
+ Surrogate.prototype = parent.prototype;
21
+ child.prototype = new Surrogate;
22
+ }
23
+ function copyNewProperties(protoProps){
24
+ _.extend(child.prototype, protoProps);
25
+ }
26
+ function enableAccessToBaseClass(){
27
+ child.__super__ = parent.prototype;
28
+ }
28
29
 
30
+ var parent = this;
31
+ var child = copyConstructor(parent);
32
+ copyBaseProperties(child, parent);
33
+ carryPrototypeChain(child, parent);
34
+ copyNewProperties(protoProps);
35
+ enableAccessToBaseClass(child, parent);
29
36
  return child;
30
37
  };
31
38
 
32
-
33
39
  stormfront.arguments = function(args){
34
40
  return new Stormfront.Arguments(args);
35
41
  };
36
42
 
37
43
  Stormfront.Arguments = Stormfront.Class.extend({
38
44
  initialize: function(array){
45
+ function copy(array) {
46
+ var copy = new Array(array.length);
47
+ for(var i = 0; i < array.length; ++i) {
48
+ copy[i] = array[i];
49
+ }
50
+ return copy;
51
+ }
39
52
  if (array)
40
- this.array = [].slice.call(array);
53
+ this.array = copy(array);
41
54
  else
42
55
  this.array = [];
43
56
  },
@@ -57,11 +70,14 @@ Stormfront.Arguments = Stormfront.Class.extend({
57
70
  return this.array;
58
71
  }
59
72
  });
60
-
61
73
  stormfront.hash = function(hash){
62
74
  return new Stormfront.Hash(hash);
63
75
  };
64
76
 
77
+ Stormfront.Unchanged = {
78
+ unchanged: 'true'
79
+ };
80
+
65
81
  Stormfront.Hash = Stormfront.Class.extend({
66
82
  initialize: function(hash){
67
83
  this.hash = hash;
@@ -79,9 +95,68 @@ Stormfront.Hash = Stormfront.Class.extend({
79
95
  },
80
96
  subtract: function(values){
81
97
  return _.omit(this.hash, _.keys(values));
98
+ },
99
+ diff: function(newProperties){
100
+ function getProperties(oldProperties, newProperties){
101
+ var candidates = _.keys(newProperties).concat(_.keys(oldProperties));
102
+ var position = candidates.length;
103
+ var keys = [];
104
+
105
+ while (position--) {
106
+ var candidate = candidates[position];
107
+ if (keys.indexOf(candidate) === -1)
108
+ keys.unshift(candidate);
109
+ }
110
+ return keys;
111
+ }
112
+ function evaluateArray(previous, current) {
113
+ if (current.length !== previous.length)
114
+ return current;
115
+
116
+ var difference = _.any(previous, function(value, index){
117
+ return evaluateObject(value, current[index]);
118
+ });
119
+ return difference ? current : Stormfront.Unchanged;
120
+ }
121
+
122
+ function evaluateValue(previous, current){
123
+ if (_.isArray(current))
124
+ return evaluateArray(previous, current);
125
+ else if (_.isObject(current))
126
+ return evaluateObject(previous, current);
127
+ else if (previous !== current)
128
+ return current;
129
+ return Stormfront.Unchanged;
130
+ }
131
+
132
+ function evaluateObject(oldProperties, newProperties) {
133
+ var changes = { };
134
+ function addEntry(property, value) {
135
+ if (value)
136
+ changes[property] = value;
137
+ }
138
+
139
+ var properties = getProperties(oldProperties, newProperties);
140
+ _.each(properties, function(property){
141
+ var previous = oldProperties[property];
142
+ var current = newProperties[property];
143
+ if (previous && current)
144
+ addEntry(property, evaluateValue(previous, current));
145
+ else if (previous)
146
+ addEntry(property, previous);
147
+ else
148
+ addEntry(property, current);
149
+ });
150
+ return _.isEmpty(changes) ? Stormfront.Unchanged : changes;
151
+ }
152
+ if (!this.hash)
153
+ return newProperties;
154
+ if (!newProperties)
155
+ return this.hash;
156
+
157
+ return evaluateObject(this.hash, newProperties);
82
158
  }
83
159
  });
84
-
85
160
  stormfront.number = function(number){
86
161
  return new Stormfront.Number(number);
87
162
  };
@@ -97,7 +172,6 @@ Stormfront.Number = Stormfront.Class.extend({
97
172
  return this.number < value ? -1 : this.number > value ? 1 : 0;
98
173
  }
99
174
  });
100
-
101
175
  Stormfront.Response = Stormfront.Class.extend({
102
176
  initialize: function(xhr){
103
177
  this.xhr = xhr;
@@ -129,7 +203,6 @@ Stormfront.Response = Stormfront.Class.extend({
129
203
  }
130
204
  }
131
205
  });
132
-
133
206
  stormfront.string = function(text){
134
207
  return new Stormfront.String(text);
135
208
  };
@@ -138,23 +211,6 @@ Stormfront.String = Stormfront.Class.extend({
138
211
  initialize: function(text){
139
212
  this.text = text;
140
213
  },
141
- title: function(){
142
- var sentence = _.str.capitalize(_.str.camelize(this.text)).split(/(?=[A-Z\d])/);
143
- var displaySentence = "";
144
- function processCharacter(character, i){
145
- function whenPreviousWordIsNotAnAcronym() {
146
- return i > 0 && sentence[i - 1].length > 1;
147
- }
148
- function whenPreviousWordISAnAcronymAndCurrentWordIsNotAnAcronym() {
149
- return i > 0 && sentence[i - 1].length == 1 && sentence[i].length > 1;
150
- }
151
- if (whenPreviousWordIsNotAnAcronym() || whenPreviousWordISAnAcronymAndCurrentWordIsNotAnAcronym())
152
- displaySentence += " ";
153
- displaySentence += character;
154
- }
155
- _.each(sentence, processCharacter);
156
- return displaySentence;
157
- },
158
214
  decimal: function(places, placeholder){
159
215
  placeholder = placeholder || '';
160
216
  places = places || 3;
@@ -164,17 +220,22 @@ Stormfront.String = Stormfront.Class.extend({
164
220
  scientific: function(){
165
221
  return this.text === null || $.trim(this.text) === '' ? '' : parseFloat(this.text).toExponential(3);
166
222
  },
167
- decimal_format: function(){
168
- return this.decimal(this.text);
169
- },
170
- scientific_notation: function(){
171
- return this.scientific(this.text);
172
- },
173
223
  firstWord: function(){
174
224
  return this.text ? this.text.split(' ')[0] : '';
225
+ },
226
+ title: function(){
227
+ return this.camelCase(' ', ' ');
228
+ },
229
+ camelCase: function(delimiter, alimiter){
230
+ delimiter = delimiter || ' ';
231
+ alimiter = alimiter || '';
232
+ function capitalize(word){
233
+ return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
234
+ }
235
+ var words = this.text.split(delimiter);
236
+ return _.map(words, capitalize).join(alimiter)
175
237
  }
176
238
  });
177
-
178
239
  stormfront.time = function(datetime){
179
240
  return new Stormfront.Time(datetime);
180
241
  };
@@ -205,201 +266,456 @@ Stormfront.Time = Stormfront.Class.extend({
205
266
 
206
267
 
207
268
 
269
+ stormfront.type = function(name){
270
+ return (new Stormfront.Type(name)).get();
271
+ };
272
+ Stormfront.Type = Stormfront.Class.extend({
273
+ initialize: function (name) {
274
+ this.name = name;
275
+ },
276
+ get: function () {
277
+ var name = this.name.split('.');
278
+ return stormfront.hash(window).findNestedValue(name);
279
+ }
280
+ });
208
281
 
209
-
210
- Stormfront.Chaperone = Stormfront.Class.extend({
211
- initialize: function(){
212
- this.children = {};
213
- _.extend(this, Backbone.Events);
214
- this.throttle('create', 'created', Stormfront.Errors.INITIALIZING);
215
- this.throttle('render', 'rendered', Stormfront.Errors.RENDERING);
216
- this.throttle('attach', 'attached', Stormfront.Errors.ATTACHING);
217
- this.wrap('add', 'added', Stormfront.Errors.ADDING);
218
- this.wrap('remove', 'removed', Stormfront.Errors.CLOSING);
219
- },
220
-
221
- create: function(childClass, options, context){
222
- return new window[childClass](options.call(context));
223
- },
224
- find: function(identity){
225
- return this.children[identity];
226
- },
227
- add: function(child){
228
- var identity = child.getIdentity();
229
- this.remove(this.find(identity));
230
- this.children[identity] = child;
231
- return child;
232
- },
233
- remove: function(child){
234
- if (child)
235
- this.children = _.omit(this.children, child.getIdentity());
236
- return child;
237
- },
238
- render: function(child){
239
- return child.render();
240
- },
241
- attach: function(child, location, method){
242
- if (location.size() === 0)
243
- throw Stormfront.Errors.NO_LOCATION(child);
244
- else if (location.size() > 1)
245
- throw Stormfront.Errors.MULTIPLE_LOCATIONS(child);
246
-
247
- location[method](child.$el);
248
- return child;
249
- },
250
-
251
- wrap: function(field, event, error){
252
- var implementation = this[field];
253
- this[field] = function(){
254
- try {
255
- var result = implementation.apply(this, arguments);
256
- result && this.trigger(event, result);
257
- } catch (e) {
258
- this.trigger('error', error(arguments[0].toString()), e);
259
- }
282
+ Stormfront.Patterns = {
283
+ Dispatcher: function (subject, dispatcher) {
284
+ if (dispatcher) {
285
+ subject.dispatch = function (event) {
286
+ dispatcher.dispatch(event);
287
+ };
288
+ subject.getDispatcher = function(){
289
+ return dispatcher;
290
+ };
260
291
  }
261
292
  },
262
- throttle: function(field, event, error){
263
- this.wrap(field, event, error);
264
- var implementation = this[field];
265
- this[field] = function(){
266
- var self = this;
267
- var capturedArguments = arguments;
268
- function func(){
269
- implementation.apply(self, capturedArguments);
293
+ Events: function (subject) {
294
+ if (!subject.listenTo)
295
+ $.extend(subject, Backbone.Events);
296
+ $.extend(subject, {
297
+ forward: function (other, type) {
298
+ function propagate(){
299
+ subject.trigger.apply(subject, arguments);
300
+ }
301
+ subject.listenTo(other, type || 'all', propagate);
270
302
  }
271
- setTimeout(func, 5);
272
- }
303
+ })
304
+ }
305
+ };
306
+ Stormfront.Errors = {
307
+ FEEDBACK: 'An unexpected error was encountered'
308
+ };
309
+
310
+ Stormfront.Dispatches = {
311
+ RECEIVED: 'Dispatcher:Action:Received',
312
+ QUEUED: 'Dispatcher:Action:Queued',
313
+ READY: 'Dispatcher:Action:Ready',
314
+ EXECUTING: 'Store:Action:Executing',
315
+ EXECUTED: 'Store:Action:Executed',
316
+ MISSING: 'Store:Action:Missing',
317
+ ERROR: 'Store:Action:Error',
318
+ CHANGE: 'Store:Changed',
319
+ UNCHANGED: 'Store:Unchanged',
320
+ NO_RETURN: 'Store:NoReturnValue'
321
+ };
322
+
323
+ Stormfront.NO_CHANGE = 'Store:Nothing_Changed';
324
+ Stormfront.Reducer = Stormfront.Class.extend({
325
+ type: 'NOT:IMPLEMENTED',
326
+ dependencies: [],
327
+ initialize: function(dispatcher){
328
+ Stormfront.Patterns.Dispatcher(this, dispatcher);
273
329
  },
330
+ execute: function(store, event){
331
+ throw 'The reducer does not implement a function body'
332
+ }
333
+ });
334
+ Stormfront.Ajax = {
335
+ START: 'AJAX:START',
336
+ SUCCESS: 'AJAX:SUCCESS',
337
+ ERROR: 'AJAX:ERROR',
338
+ ABORTED: 'AJAX:ABORTED',
339
+ PENDING: 'AJAX:PENDING',
340
+ Support: {
341
+ generateKey: function (event) {
342
+ return event.type;
343
+ },
344
+ createGenericRequest: function (type) {
345
+ return function (info) {
346
+ return _.extend(info, {type: type});
347
+ }
348
+ },
349
+ createGenericReducer: function (type) {
350
+ return Stormfront.Ajax.RequestReducer.extend({
351
+ type: type,
352
+ execute: function (store, event) {
353
+ try {
354
+ event.callbacks[type].call(event.callbacks.context, store, event);
355
+ } catch (e) {
356
+ console.warn('Ajax Handler encountered an error', e);
357
+ }
358
+ return this.updateEntry(store, event, type);
359
+ }
360
+ });
361
+ }
362
+ }
363
+ };
274
364
 
275
- close: function(){
276
- _.each(this.children, this.remove, this);
365
+ Stormfront.Ajax.RequestReducer = Stormfront.Reducer.extend({
366
+ getKey: function(event){
367
+ return Stormfront.Ajax.Support.generateKey(event);
368
+ },
369
+ getEntries: function(store){
370
+ return store.ajax || (store.ajax = { });
371
+ },
372
+ getEntry: function(store, event){
373
+ var key = this.getKey(event);
374
+ var entries = this.getEntries(store);
375
+ return entries[key] || (entries[key] = { });
376
+ },
377
+ updateEntry: function(store, event, state){
378
+ var ajax = this.getEntry(store, event);
379
+ ajax.state = state;
380
+ ajax.request = event.xhr;
381
+ ajax.exception = event.exception;
382
+ return store;
383
+ },
384
+ dropEntry: function(store, event){
385
+ var key = this.getKey(event);
386
+ var entries = this.getEntries(store);
387
+ delete entries[key];
277
388
  }
278
389
  });
279
390
 
280
- Stormfront.Template = Stormfront.Class.extend({
281
- initialize: function(view){
282
- this.view = view;
283
- },
284
- render: function(){
285
- function renderTemplate(view){
286
- function getTemplate(view){
287
- var selector = view.getIdentity();
288
- return $('#' + selector + '_template');
289
- }
290
- function compileTemplate(selection){
291
- return Handlebars.compile(selection.html());
292
- }
293
- function expandTemplate(template){
294
- return template(view.getViewModel());
295
- }
296
- return expandTemplate(compileTemplate(getTemplate(view)))
297
- }
298
- function findError(view){
299
- function getTemplate(view){
300
- if (!view.className)
301
- throw Stormfront.Errors.NO_SELECTOR();
302
- var selector = view.getIdentity();
303
- return $('#' + selector + '_template');
304
- }
305
- function compileTemplate(template){
306
- if (template.size() === 1)
307
- return template;
308
- if (template.size() === 0)
309
- throw Stormfront.Errors.NO_TEMPLATE(view.getIdentity());
391
+ Stormfront.Ajax.Request = Stormfront.Ajax.RequestReducer.extend({
392
+ type: 'NOT:IMPLEMENTED',
393
+ execute: function(store, event){
394
+ event.callbacks = { };
395
+ event.callbacks.context = this;
396
+ event.callbacks[Stormfront.Ajax.START] = this.onStart;
397
+ event.callbacks[Stormfront.Ajax.SUCCESS] = this.onSuccess;
398
+ event.callbacks[Stormfront.Ajax.ERROR] = this.onError;
310
399
 
311
- return Handlebars.compile(template.html());
312
- }
313
- function expandTemplate(template, vm){
314
- try {
315
- return template(vm);
316
- }
317
- catch(e) {
318
- throw Stormfront.Errors.INCOMPATIBLE_TEMPLATE(view.getIdentity());
319
- }
320
- }
321
- function getViewModel(view){
322
- try {
323
- return view.getViewModel();
324
- }
325
- catch (e){
326
- throw Stormfront.Errors.VIEW_MODEL(view.getIdentity());
327
- }
328
- }
329
- try {
330
- return expandTemplate(compileTemplate(getTemplate(view)), getViewModel(view));
331
- }
332
- catch(e) {
333
- return e;
334
- }
400
+ var self = this;
401
+ function onStart(xhr, settings){
402
+ _.extend(event, { xhr: xhr });
403
+ self.dispatch(new Stormfront.Ajax.StartRequest(event));
335
404
  }
336
- try {
337
- return renderTemplate(this.view);
405
+ function onSuccess(data, status, xhr){
406
+ _.extend(event, {
407
+ data: data,
408
+ status: status,
409
+ xhr: xhr
410
+ });
411
+ self.dispatch(new Stormfront.Ajax.SuccessRequest(event));
338
412
  }
339
- catch(e) {
340
- throw new TemplateError(findError(this.view), e.message);
413
+ function onError(xhr, status, exception){
414
+ _.extend(event, {
415
+ status: status,
416
+ xhr: xhr,
417
+ exception: exception
418
+ });
419
+ self.dispatch(new Stormfront.Ajax.ErrorRequest(event));
341
420
  }
342
- }
421
+ var request = this.getParameters(store, event);
422
+ var settings = _.extend(request, {
423
+ beforeSend: onStart,
424
+ error: onError,
425
+ success: onSuccess
426
+ });
427
+ this.updateEntry(store, event, Stormfront.Ajax.PENDING);
428
+ this.send(settings);
429
+ return store;
430
+ },
431
+ send: function(settings){
432
+ // TODO: Remaining Concerns AJAX:
433
+ // TODO: Aborting similar request
434
+ // TODO: Avoiding equal request
435
+ $.ajax(settings);
436
+ },
437
+ getParameters: function(store, event){
438
+ return { url: '', method: 'get', data: {} }
439
+ },
440
+ onStart: function(store, event) { },
441
+ onSuccess: function(store, event) { },
442
+ onError: function(store, event) { }
343
443
  });
344
444
 
345
- function TemplateError(message, innerException) {
346
- var error = Error.call(this, message);
347
- this.name = 'TemplateError';
348
- this.message = error.message;
349
- this.stack = error.stack;
350
- this.innerException = innerException;
351
- }
445
+ Stormfront.Ajax.StartRequest = Stormfront.Ajax.Support.createGenericRequest(Stormfront.Ajax.START);
446
+ Stormfront.Ajax.Start = Stormfront.Ajax.Support.createGenericReducer(Stormfront.Ajax.START);
352
447
 
353
- TemplateError.prototype = Object.create(Error.prototype);
354
- TemplateError.prototype.constructor = TemplateError;
448
+ Stormfront.Ajax.SuccessRequest = Stormfront.Ajax.Support.createGenericRequest(Stormfront.Ajax.SUCCESS);
449
+ Stormfront.Ajax.Success = Stormfront.Ajax.Support.createGenericReducer(Stormfront.Ajax.SUCCESS);
355
450
 
451
+ Stormfront.Ajax.ErrorRequest = Stormfront.Ajax.Support.createGenericRequest(Stormfront.Ajax.ERROR);
452
+ Stormfront.Ajax.Error = Stormfront.Ajax.Support.createGenericReducer(Stormfront.Ajax.ERROR);
356
453
 
357
- Stormfront.Mixin.Dispatch = {
358
- hasEvents: function(){
359
- return this.trigger;
360
- },
361
- addEvents: function(){
454
+ Stormfront.Ajax.Reducers = [
455
+ Stormfront.Ajax.Start,
456
+ Stormfront.Ajax.Success,
457
+ Stormfront.Ajax.Error
458
+ ];
459
+ Stormfront.Store = Stormfront.Class.extend({
460
+ initialize: function (options) {
362
461
  $.extend(this, Backbone.Events);
462
+
463
+ this._state = new Stormfront.Store.State();
464
+ this._reducers = new Stormfront.Store.Reducers(options);
363
465
  },
364
- streamlineDispatch: function(event){
365
- this.dispatch = function(){
366
- this.trigger('dispatch', event)
367
- };
368
- this.dispatch(event);
466
+ executeAction: function (action) {
467
+ var reducer = this._reducers.find(action);
468
+ if (reducer)
469
+ this.executeDispatch(action, reducer);
470
+ else
471
+ this.trigger(Stormfront.Dispatches.MISSING, action, reducer);
472
+ },
473
+ executeDispatch: function (action, reducer) {
474
+ try {
475
+ this.executeExternalCode(action, reducer);
476
+ } catch (e) {
477
+ this.trigger(Stormfront.Dispatches.ERROR, action, reducer);
478
+ }
369
479
  },
370
- dispatch: function(event){
371
- if (!this.hasEvents())
372
- this.addEvents();
373
- this.streamlineDispatch(event);
480
+ executeExternalCode: function(action, reducer){
481
+ var current = this._state.getState();
482
+ this.trigger(Stormfront.Dispatches.EXECUTING, action, reducer, current);
483
+
484
+ var updated = reducer.execute(current, action);
485
+ this.trigger(Stormfront.Dispatches.EXECUTED, action, reducer, updated);
486
+
487
+ if (updated === Stormfront.NO_CHANGE)
488
+ this.trigger(Stormfront.Dispatches.UNCHANGED, action, reducer);
489
+ if (this._state.setState(updated))
490
+ this.trigger(Stormfront.Dispatches.CHANGE, action, reducer, updated, current);
491
+ else
492
+ this.trigger(Stormfront.Dispatches.NO_RETURN, action, reducer);
374
493
  }
375
- };
494
+ });
376
495
 
377
- Stormfront.Mixin.Other = {
378
- unauthorized: function(entity){
379
- function propagateUnauthorized(source, response, options){
380
- if (response.status === 403)
381
- this.trigger('unauthorized', source, response, options);
382
- }
383
- entity.on('error', propagateUnauthorized);
496
+ Stormfront.Store.State = Stormfront.Class.extend({
497
+ setState: function (state) {
498
+ return state ? this.updateState(state) : false;
499
+ },
500
+ updateState: function (state) {
501
+ this._state = this.copyState(state);
502
+ return true;
503
+ },
504
+ getState: function () {
505
+ return this.copyState(this._state);
384
506
  },
385
- invalid: function(entity){
386
- function propagateInvalid(source, response, options){
387
- if (response.status === 400)
388
- this.trigger('invalid', source, response, options);
507
+ copyState: function (state) {
508
+ return $.extend(true, {}, state);
509
+ }
510
+ });
511
+
512
+ Stormfront.Store.Reducers = Stormfront.Class.extend({
513
+ reducers: { },
514
+ initialize: function (options) {
515
+ function createReducer(type) {
516
+ var reducer = new type(options.dispatcher);
517
+ this.reducers[reducer.type] = reducer;
518
+ _.each(reducer.dependencies, createReducer, this);
389
519
  }
390
- entity.on('error', propagateInvalid);
520
+
521
+ _.each(options.reducers, createReducer, this);
522
+ _.each(Stormfront.Ajax.Reducers, createReducer, this);
523
+ _.each(Stormfront.Store.Operations, createReducer, this);
391
524
  },
392
- empty: function(collection){
393
- function determineEmpty(collection, response, options){
394
- if (collection.isEmpty())
395
- this.trigger('empty', collection, response, options);
525
+ find: function(event){
526
+ return this.reducers[event.type];
527
+ }
528
+ });
529
+
530
+ SetStateAction = function(state){
531
+ return {
532
+ type: 'Stormfront:Store:State:Set',
533
+ state: state
534
+ }
535
+ };
536
+
537
+ SetState = Stormfront.Reducer.extend({
538
+ type: 'Stormfront:Store:State:Set',
539
+ execute: function (store, action) {
540
+ return action.state;
541
+ }
542
+ });
543
+
544
+ Stormfront.Store.Operations = [SetState];
545
+ Stormfront.Dispatcher = Stormfront.Class.extend({
546
+ initialize: function () {
547
+ $.extend(this, Backbone.Events);
548
+ var queue = [];
549
+ var root = this;
550
+
551
+ this.dispatch = function (event) {
552
+ this.trigger(Stormfront.Dispatches.RECEIVED, event);
553
+ if (_.isEmpty(queue))
554
+ process(event);
396
555
  else
397
- this.trigger('populated', collection, response, options);
556
+ enqueue(event);
557
+ };
558
+
559
+ function process(event) {
560
+ enqueue(event);
561
+ processQueue();
562
+ }
563
+
564
+ function enqueue(event) {
565
+ queue.push(event);
566
+ root.trigger(Stormfront.Dispatches.QUEUED, event);
567
+ }
568
+
569
+
570
+ function processQueue() {
571
+ if (_.isEmpty(queue))
572
+ return;
573
+ processNext();
574
+ processQueue();
575
+ }
576
+
577
+ function processNext() {
578
+ root.trigger(Stormfront.Dispatches.READY, queue[0]);
579
+ queue.shift();
580
+ }
581
+ }
582
+ });
583
+ Stormfront.Container = Stormfront.Class.extend({
584
+ page: null,
585
+ initial: null,
586
+ reducers: [],
587
+ initialize: function(location) {
588
+ Stormfront.Patterns.Events(this);
589
+ var dispatcher = new Stormfront.Dispatcher();
590
+ var page = new this.page(dispatcher);
591
+ var store = new Stormfront.Store({
592
+ reducers: this.reducers,
593
+ dispatcher: dispatcher
594
+ });
595
+
596
+ function executeAction(action){
597
+ store.executeAction(action);
598
+ }
599
+ function updateView(action, reducer, newProperties, oldProperties){
600
+ page.updateProperties(newProperties, oldProperties, action, reducer);
398
601
  }
399
- collection.on('sync', determineEmpty);
602
+ this.listenTo(dispatcher, Stormfront.Dispatches.READY, executeAction);
603
+ this.listenTo(store, Stormfront.Dispatches.CHANGE, updateView);
604
+
605
+ Stormfront.Patterns.Dispatcher(this, dispatcher);
606
+ Stormfront.Patterns.Events(this);
607
+
608
+ this.forward(dispatcher);
609
+ this.forward(store);
610
+
611
+ dispatcher.dispatch(SetStateAction(this.initial));
612
+
613
+ location.html(page.render().$el);
614
+
615
+ page.transition('attached');
400
616
  }
617
+ });
618
+
619
+ var homefront = { };
620
+ var Homefront = {
621
+ Patterns: {}
401
622
  };
402
623
 
624
+ Homefront.Template = Stormfront.Class.extend({
625
+ initialize: function (selector) {
626
+ this.selector = selector;
627
+ },
628
+ render: function (vm) {
629
+ function getTemplate(selector) {
630
+ return $('#' + selector + '_template');
631
+ }
632
+ function compileTemplate(selection) {
633
+ return Handlebars.compile(selection.html());
634
+ }
635
+ function expandTemplate(template, vm) {
636
+ return template(vm);
637
+ }
638
+
639
+ var template = getTemplate(this.selector);
640
+ var compiled = compileTemplate(template);
641
+ return expandTemplate(compiled, vm)
642
+ }
643
+ });
644
+
645
+ Homefront.Entity = Stormfront.Class.extend({
646
+ defaults: null,
647
+ initialize: function (properties) {
648
+ this.properties = _.extend(this, this.defaults, properties);
649
+ },
650
+ state: function(){
651
+ return this.getEntity().state;
652
+ },
653
+ getEntity: function(){
654
+ return this.properties;
655
+ },
656
+ isBusy: function(){
657
+ var state = this.state();
658
+ return state === Stormfront.Ajax.START ||
659
+ state === Stormfront.Ajax.PENDING;
660
+ }
661
+ });
662
+ Homefront.Collection = Homefront.Entity.extend({
663
+ entity: null,
664
+ state: '',
665
+ initialize: function (items, state) {
666
+ state = state || this.state;
667
+ var entity = this.entity;
668
+ this.properties = {
669
+ state: state,
670
+ data: _.map(items, function (option) {
671
+ return new entity(option);
672
+ })
673
+ };
674
+ this.state = state;
675
+ },
676
+ list: function () {
677
+ return this.getEntity().data;
678
+ },
679
+ isEmpty: function () {
680
+ return this.list().length === 0;
681
+ },
682
+ find: function (parameters) {
683
+ return _.findWhere(this.list(), parameters);
684
+ },
685
+ groupBy: function (field) {
686
+ return _.groupBy(this.list(), field);
687
+ },
688
+ pluck: function (field) {
689
+ return _.pluck(this.list(), field);
690
+ },
691
+ first: function () {
692
+ return this.list()[0];
693
+ },
694
+ onlyOne: function () {
695
+ return this.list().length === 1;
696
+ }
697
+ });
698
+ Homefront.Request = Stormfront.Ajax.Request.extend({
699
+ getEntity: function (store, action) {
700
+ return {};
701
+ },
702
+ onStart: function (store, action) {
703
+ this.getEntity(store, action).state = Stormfront.Ajax.START;
704
+ return store;
705
+ },
706
+ onSuccess: function (store, action) {
707
+ var entity = this.getEntity(store, action);
708
+ entity.state = Stormfront.Ajax.SUCCESS;
709
+ entity.data = action.data;
710
+ return store;
711
+ },
712
+ onError: function (store, action) {
713
+ var entity = this.getEntity(store);
714
+ entity.state = Stormfront.Ajax.ERROR;
715
+ entity.error = action.error;
716
+ return store;
717
+ }
718
+ });
403
719
 
404
720
  Keys = {
405
721
  Enter: 13,
@@ -453,7 +769,6 @@ KeyboardEvents = Stormfront.Class.extend({
453
769
  this.off('all');
454
770
  }
455
771
  });
456
-
457
772
  MouseEvents = Stormfront.Class.extend({
458
773
  initialize: function(options){
459
774
  options = _.extend({delay: 175}, options);
@@ -600,123 +915,7 @@ MouseEvents = Stormfront.Class.extend({
600
915
  this.$el.global && this.$el.global.off('.me');
601
916
  }
602
917
  });
603
-
604
-
605
- Stormfront.ViewBase = Backbone.View.extend({
606
- initialize: function(){
607
- this.errors = [];
608
- },
609
- render: function(){
610
- try {
611
- this.$el.html(new Stormfront.Template(this).render());
612
- }
613
- catch(e) {
614
- this.handleException(e);
615
- }
616
- return this;
617
- },
618
- getViewModel: function(){
619
- return this.model ? this.model.toJSON ? this.model.toJSON() : this.model : {};
620
- },
621
- handleException: function(exception){
622
- if (exception.innerException)
623
- console.warn(exception.message, exception.innerException);
624
- else
625
- console.warn(exception.message);
626
-
627
- this.handleError(exception.message);
628
- },
629
- handleError: function(message){
630
- if (_.contains(this.errors, message))
631
- return;
632
- this.notifyUser(message);
633
- },
634
- notifyUser: function(message){
635
- this.alert && this.alert.close();
636
-
637
- this.errors.push(message);
638
- this.alert = new Stormfront.Alert({
639
- model: {
640
- summary: Stormfront.Errors.FEEDBACK,
641
- details: this.errors.join(', ')
642
- }
643
- });
644
-
645
- this.listenTo(this.alert, 'closing', this.removeNotification);
646
- this.$el.append(this.alert.render().$el);
647
- },
648
- removeNotification: function(){
649
- this.errors = [];
650
- this.stopListening(this.alert);
651
- },
652
- getIdentity: function(){
653
- return stormfront.string(this.className).firstWord();
654
- },
655
- toString: function(){
656
- return this.getIdentity();
657
- },
658
- close: function(){
659
- this.remove();
660
- }
661
- });
662
-
663
- Stormfront.View = Stormfront.ViewBase.extend({
664
- initialize: function(options){
665
- Stormfront.ViewBase.prototype.initialize.call(this, options);
666
- this.when = this.when || { };
667
- this.addPublisher(this);
668
- this.addSubscriber(this.when);
669
- this.transition.apply(this, stormfront.arguments(arguments).prepend('initializing').get());
670
- },
671
- render: function(){
672
- Stormfront.ViewBase.prototype.render.call(this);
673
- this.transition.apply(this, stormfront.arguments(arguments).prepend('rendering').get());
674
- return this;
675
- },
676
- addPublisher: function(publisher, identity){
677
- function notify(event){
678
- var args = stormfront.arguments(arguments).skip(1).get();
679
- function executeHandler(subscriber){
680
- var handler = stormfront.hash(subscriber).findNestedValue(event.split(':'));
681
- if (_.isFunction(handler))
682
- handler.apply(this, args);
683
- }
684
- _.each(this._subscribers, executeHandler, this);
685
- }
686
- function prepend(identity){
687
- return function(event){
688
- var newEvent = identity + ':' + event;
689
- var args = stormfront.arguments(arguments).skip(1).prepend(newEvent).get();
690
- notify.apply(this, args);
691
- }
692
- }
693
- if (publisher === this)
694
- this.listenTo(publisher, 'transition', notify);
695
- else if (this.model && publisher === this.model)
696
- this.listenTo(publisher, 'all', prepend('model'));
697
- else if (this.collection && publisher === this.collection)
698
- this.listenTo(publisher, 'all', prepend('collection'));
699
- else if (publisher.className)
700
- this.listenTo(publisher, 'transition', prepend(publisher.getIdentity()));
701
- else
702
- this.listenTo(publisher, 'all', prepend(identity));
703
- },
704
- addSubscriber: function(whenBlock){
705
- this._subscribers = this._subscribers || [];
706
- if (whenBlock)
707
- this._subscribers.push(whenBlock);
708
- },
709
- transition: function(){
710
- var args = stormfront.arguments(arguments).prepend('transition').get();
711
- this.trigger.apply(this, args);
712
- },
713
- close: function(){
714
- this.transition.apply(this, stormfront.arguments(arguments).prepend('closing').get());
715
- Stormfront.ViewBase.prototype.close.call(this);
716
- }
717
- });
718
-
719
- Stormfront.Errors = {
918
+ Homefront.Errors = {
720
919
  FEEDBACK: 'An unexpected error was encountered',
721
920
  /** @return {string} */
722
921
  NO_SELECTOR: function() { return 'A template selector was not provided for your view' },
@@ -725,7 +924,7 @@ Stormfront.Errors = {
725
924
  /** @return {string} */
726
925
  INCOMPATIBLE_TEMPLATE: function(name) { return name +'\'s template and view model for your view are not compatible'; },
727
926
  /** @return {string} */
728
- VIEW_MODEL: function(name) { return name + '\'s view model for the child generated an error'; },
927
+ VIEW_MODEL: function(name) { return name + '\'s context generated an error'; },
729
928
  /** @return {string} */
730
929
  NO_LOCATION: function(name) { return name + ' has no locations available to render'; },
731
930
  /** @return {string} */
@@ -742,181 +941,167 @@ Stormfront.Errors = {
742
941
  ADDING: function(name) { return name + ' failed to add'; }
743
942
  };
744
943
 
745
- //TODO: This whole class depends on Bootstrap
746
- Stormfront.Alert = Stormfront.View.extend({
747
- className: 'alert',
748
- when: {
749
- rendering: function(){
750
- this.$el.attr('role', 'alert').
751
- addClass('alert-warning').
752
- addClass('alert-dismissible');
753
-
754
- var self = this;
755
- this.$el.on('closed.bs.alert', function(){
756
- self.trigger('closing');
757
- self.close();
758
- });
759
- }
760
- }
761
- });
762
-
763
- Stormfront.Container = Stormfront.View.extend({
764
- reducers: [],
765
- store: null,
766
- root: null,
767
- when: {
768
- initializing: function(){
769
- this._reducers = _.map(this.reducers, this.createReducer, this);
770
- },
771
- rendering: function(){
772
- this.store = new this.store();
773
- this.root = new this.root({model: this.store});
774
- this.listenToDispatches(this.root);
775
- this.$el.append(this.root.render().$el);
944
+ Homefront.ViewBase = Backbone.View.extend({
945
+ location: null,
946
+ template: null,
947
+ render: function () {
948
+ try {
949
+ var context = this.getContext();
950
+ if (this.template)
951
+ this.$el.html(new Homefront.Template(this.template).render(context));
952
+ } catch (e) {
953
+ this.$el.html('An error was encountered');
954
+ console.error('Failed to render view');
955
+ console.error(e);
776
956
  }
957
+ return this;
777
958
  },
778
- createReducer: function(interaction){
779
- var useCase = new interaction();
780
- this.listenToDispatches(useCase);
781
- return this.addReducer(useCase);
782
- },
783
- addReducer: function(useCase){
784
- this._reducers[useCase.type] = useCase;
785
- return useCase;
786
- },
787
- executeUseCase: function(event){
788
- this._reducers[event.type].execute(this.store, event);
959
+ getContext: function(){
960
+ return this.properties ? this.properties : { };
789
961
  },
790
- listenToDispatches: function(publisher){
791
- this.listenTo(publisher, 'dispatch', this.executeUseCase);
962
+ close: function () {
963
+ this.remove();
792
964
  }
793
965
  });
794
-
795
- Stormfront.Entity = Stormfront.View.extend({
796
- exceptions: {
797
- model: {
798
- error: function(collection, xhr){
799
- var response = new Stormfront.Response(xhr);
800
- if (response.isActionable()) {
801
- this.handleException({error: response.getError()});
802
- response.markAsHandled();
966
+ Homefront.View = Homefront.ViewBase.extend({
967
+ when: {},
968
+ initialize: function (dispatcher) {
969
+ _.each(this.when, this.addSubscriber, this);
970
+ this.addPublisher(this);
971
+ this.transition('before:initializing');
972
+ Stormfront.Patterns.Dispatcher(this, dispatcher);
973
+ this.transition('initializing');
974
+ },
975
+ render: function () {
976
+ this.transition('before:rendering');
977
+ Homefront.ViewBase.prototype.render.call(this);
978
+ this.transition('rendering');
979
+ return this;
980
+ },
981
+ renderAgain: function(){
982
+ this.render();
983
+ this.transition('attached');
984
+ },
985
+ addPublisher: function (publisher, identity) {
986
+ function notify(event) {
987
+ var args = stormfront.arguments(arguments).skip(1).get();
988
+ function executeHandler(subscriber) {
989
+ var handler = stormfront.hash(subscriber).findNestedValue(event.split(':'));
990
+ try {
991
+ if (_.isFunction(handler))
992
+ handler.apply(this, args);
993
+ } catch (e) {
994
+ console.error('Error encountered within the When block: ('+event+')');
995
+ console.error(e);
803
996
  }
804
- },
805
- invalid: function(model, error){
806
- this.handleException({error: error});
807
997
  }
998
+
999
+ _.each(this._subscribers, executeHandler, this);
808
1000
  }
809
- },
810
- overlays: {
811
- model: {
812
- request: function(){
813
- this.addOverlay();
814
- },
815
- sync: function(){
816
- this.removeOverlay();
817
- },
818
- error: function(){
819
- this.removeOverlay();
1001
+
1002
+ function prepend(identity) {
1003
+ return function (event) {
1004
+ var newEvent = identity + ':' + event;
1005
+ var args = stormfront.arguments(arguments).skip(1).prepend(newEvent).get();
1006
+ notify.apply(this, args);
820
1007
  }
821
1008
  }
1009
+
1010
+ if (publisher === this)
1011
+ this.listenTo(publisher, 'transition', notify);
1012
+ else
1013
+ this.listenTo(publisher, 'transition', prepend(identity));
822
1014
  },
823
- change: {
824
- model: {
825
- change: function(){
826
- this.render();
827
- }
828
- }
1015
+ addSubscriber: function (whenBlock) {
1016
+ this._subscribers = this._subscribers || [];
1017
+ if (whenBlock && !_.isEmpty(whenBlock))
1018
+ this._subscribers.push(whenBlock);
829
1019
  },
830
- initialize: function(){
831
- this.addSubscriber(this.overlays);
832
- this.addSubscriber(this.exceptions);
833
- this.addSubscriber(this.change);
834
- this.addPublisher(this.model);
835
- Stormfront.View.prototype.initialize.apply(this, arguments);
1020
+ transition: function () {
1021
+ this.trigger.apply(this, stormfront.arguments(arguments).prepend('transition').get());
836
1022
  },
837
- addOverlay: function(){
838
- this.removeOverlay();
839
- this.overlay = new Stormfront.Overlay({selector: this.$el});
1023
+ updateProperties: function (properties) {
1024
+ var previous = this.properties;
1025
+ this.properties = this.selectProperties(properties);
1026
+ this.transition('updating', this.properties, previous);
840
1027
  },
841
- removeOverlay: function(){
842
- if (this.overlay) {
843
- this.overlay.close();
844
- this.overlay = null;
845
- }
1028
+ selectProperties: function (properties) {
1029
+ return properties;
1030
+ },
1031
+ close: function () {
1032
+ this.transition('before:closing');
1033
+ this.transition('closing');
1034
+ Homefront.ViewBase.prototype.close.call(this);
846
1035
  }
847
1036
  });
848
1037
 
849
- Stormfront.Layout = Stormfront.View.extend({
850
- base: {
851
- initializing: function(){
852
- this._children = new Stormfront.Chaperone();
853
- this.addPublisher(this._children, 'child');
854
- },
855
- rendering: function(){
856
- _.each(this.children, this.composeChild, this);
857
- },
858
- closing: function(){
859
- this._children.close();
860
- },
861
- child: {
862
- created: function(child){
863
- this._children.add(child);
864
- },
865
- added: function(child){
866
- this.addPublisher(child);
867
- this._children.render(child);
868
- },
869
- rendered: function(child){
870
- var location = this.$('.' + child.getIdentity());
871
- this._children.attach(child, location, 'replaceWith');
872
- },
873
- attached: function(child){
874
- child.transition('rendered', child);
875
- },
876
- removed: function(child){
877
- child.close();
878
- this.stopListening(child);
879
- },
880
- error: function(error, innerException){
881
- this.handleException(
882
- new LayoutError(error, innerException)
883
- );
884
- }
885
- }
886
- },
887
- initialize: function(){
888
- this.addSubscriber(this.base);
889
- Stormfront.View.prototype.initialize.apply(this, arguments);
890
- },
891
- composeChild: function(childOptions, childClass){
892
- this._children.create(childClass, childOptions, this);
893
- },
894
- findChild: function(identity){
895
- return this._children.find(identity);
1038
+ Homefront.View.extend = function(protoProps){
1039
+ function copyConstructor(base){
1040
+ return function(){ return base.apply(this, arguments); };
896
1041
  }
897
- });
1042
+ function copyBaseProperties(child, parent){
1043
+ _.extend(child, parent);
1044
+ }
1045
+ function carryPrototypeChain(child, parent){
1046
+ var Surrogate = function(){ this.constructor = child; };
1047
+ Surrogate.prototype = parent.prototype;
1048
+ child.prototype = new Surrogate;
1049
+ }
1050
+ function copyNewProperties(child, protoProps){
1051
+ var newWhen = protoProps['when'];
1052
+ var oldWhen = child.prototype['when'];
898
1053
 
899
- function LayoutError(message, innerException) {
900
- var error = Error(message);
901
- this.name = 'LayoutError';
902
- this.message = error.message;
903
- this.stack = error.stack;
904
- this.innerException = innerException;
905
- }
1054
+ _.extend(child.prototype, protoProps);
906
1055
 
907
- LayoutError.prototype = Object.create(Error.prototype);
908
- LayoutError.prototype.constructor = LayoutError;
1056
+ var when = [];
1057
+ oldWhen && when.push(oldWhen);
1058
+ newWhen && when.push(newWhen);
1059
+ child.prototype['when'] = _.flatten(when);
1060
+ }
1061
+ function enableAccessToBaseClass(){
1062
+ child.__super__ = parent.prototype;
1063
+ }
1064
+
1065
+ var parent = this;
1066
+ var child = copyConstructor(parent);
1067
+ copyBaseProperties(child, parent);
1068
+ carryPrototypeChain(child, parent);
1069
+ copyNewProperties(child, protoProps);
1070
+ enableAccessToBaseClass(child, parent);
1071
+ return child;
1072
+ };
909
1073
 
910
- Stormfront.Overlay = Stormfront.Class.extend({
1074
+ Homefront.Alert = Homefront.View.extend({
1075
+ className: 'alert',
1076
+ events: {
1077
+ 'click .close': 'dismiss'
1078
+ },
1079
+ render: function(){
1080
+ var errors = this.properties.errors || [];
1081
+ var content = $('<div>');
1082
+ var summary = $('<p>').addClass('summary').text(Stormfront.Errors.FEEDBACK);
1083
+ var details = $('<p>').addClass('errors').text(errors.join(', '));
1084
+ var close = $('<div>').addClass('close').attr('title', 'click to close');
1085
+ content.append(summary).append(details).append(close);
1086
+ this.$el.html(content);
1087
+ this.transitionAs('rendering', arguments);
1088
+ return this;
1089
+ },
1090
+ dismiss: function(){
1091
+ this.trigger('dismissed');
1092
+ this.close();
1093
+ }
1094
+ });
1095
+ Homefront.Overlay = Stormfront.Class.extend({
911
1096
  initialize: function(options){
912
1097
  var selector = options.selector;
913
1098
 
914
1099
  if (!selector)
915
- this.overlay = new Stormfront.FullScreenOverlay({model: options});
1100
+ this.overlay = new Homefront.FullScreenOverlay({model: options});
916
1101
  else if (options.saveRequest)
917
- this.overlay = new Stormfront.InlineOverlay();
1102
+ this.overlay = new Homefront.InlineOverlay();
918
1103
  else
919
- this.overlay = new Stormfront.AnonymousOverlay();
1104
+ this.overlay = new Homefront.AnonymousOverlay();
920
1105
 
921
1106
  selector = selector ? selector : document.body;
922
1107
  this.location = selector.size ? selector : $(selector);
@@ -936,7 +1121,7 @@ Stormfront.Overlay = Stormfront.Class.extend({
936
1121
  }
937
1122
  });
938
1123
 
939
- Stormfront.OverlayBase = Stormfront.View.extend({
1124
+ Homefront.OverlayBase = Homefront.View.extend({
940
1125
  className: 'overlay',
941
1126
  activated: false,
942
1127
  PAUSE_DURATION: 0,
@@ -1010,7 +1195,7 @@ Stormfront.OverlayBase = Stormfront.View.extend({
1010
1195
  }
1011
1196
  });
1012
1197
 
1013
- Stormfront.FullScreenOverlay = Stormfront.OverlayBase.extend({
1198
+ Homefront.FullScreenOverlay = Homefront.OverlayBase.extend({
1014
1199
  MINIMUM_DURATION: 900,
1015
1200
  css: {
1016
1201
  overlay_background: {
@@ -1028,7 +1213,7 @@ Stormfront.FullScreenOverlay = Stormfront.OverlayBase.extend({
1028
1213
  }
1029
1214
  });
1030
1215
 
1031
- Stormfront.AnonymousOverlay = Stormfront.OverlayBase.extend({
1216
+ Homefront.AnonymousOverlay = Homefront.OverlayBase.extend({
1032
1217
  PAUSE_DURATION: 200,
1033
1218
  MINIMUM_DURATION: 450,
1034
1219
  css: {
@@ -1047,7 +1232,7 @@ Stormfront.AnonymousOverlay = Stormfront.OverlayBase.extend({
1047
1232
  }
1048
1233
  });
1049
1234
 
1050
- Stormfront.InlineOverlay = Stormfront.OverlayBase.extend({
1235
+ Homefront.InlineOverlay = Homefront.OverlayBase.extend({
1051
1236
  className: 'input_overlay',
1052
1237
  MINIMUM_DURATION: 1500,
1053
1238
  MAXIMUM_WIDTH: 1200,
@@ -1085,53 +1270,546 @@ Stormfront.InlineOverlay = Stormfront.OverlayBase.extend({
1085
1270
  }
1086
1271
  }
1087
1272
  });
1273
+ Homefront.Guidance = {
1274
+ Generic: function (type, text) {
1275
+ return Homefront.Text.extend({
1276
+ tagName: type,
1277
+ className: 'guidance',
1278
+ text: text,
1279
+ selectProperties: function () {
1280
+ return {value: this.text}
1281
+ }
1282
+ });
1283
+ },
1284
+ Paragraph: function (text) {
1285
+ return Homefront.Guidance.Generic('p', text);
1286
+ },
1287
+ Table: function (text) {
1288
+ text = '<td colspan="99">' + text + '</td>';
1289
+ return Homefront.Guidance.Generic('tr', text);
1290
+ },
1291
+ List: function (text) {
1292
+ return Homefront.Guidance.Generic('li', text);
1293
+ }
1294
+ };
1088
1295
 
1089
- Stormfront.Reducer = Stormfront.Class.extend({
1090
- type: Stormfront.Errors.NOT_IMPLEMENTED,
1091
- initialize: function(){
1092
- _.extend(this, Stormfront.Mixin.Dispatch);
1296
+ Homefront.Chaperone = Homefront.View.extend({
1297
+ method: 'append',
1298
+ when: {
1299
+ initializing: function () {
1300
+ this._children = [];
1301
+ },
1302
+ updating: function (properties) {
1303
+ this.updateChildren(this._children, properties);
1304
+ },
1305
+ attached: function(){
1306
+ var previous = this._children;
1307
+ this._children = [];
1308
+ this.createChildren();
1309
+ this.renderChildren(this._children);
1310
+ this.attachChildren(this._children);
1311
+ this.closeChildren(previous);
1312
+ },
1313
+ closing: function () {
1314
+ this.closeChildren(this._children);
1315
+ this._children = [];
1316
+ },
1317
+ chaperone: {
1318
+ create: function (type, properties) {
1319
+ type = _.isString(type) ? stormfront.type(type) : type;
1320
+ properties = _.isObject(properties) ? properties : this.properties;
1321
+ if (type) {
1322
+ var child = new type(this.getDispatcher());
1323
+ this.transition('chaperone:add', child, properties);
1324
+ } else {
1325
+ console.error('Child type does not exist: ', type);
1326
+ }
1327
+ },
1328
+ add: function (child, properties) {
1329
+ this._children.push(child);
1330
+ child.transition && this.addPublisher(child, 'child');
1331
+ child.updateProperties(properties);
1332
+ },
1333
+ attach: function (child, location, method) {
1334
+ child.transition('attaching', child);
1335
+ location[method](child.$el);
1336
+ child.transition('attached', child);
1337
+ }
1338
+ },
1339
+ child: {
1340
+ closing: function (child) {
1341
+ this._children.splice(this._children.indexOf(child), 1);
1342
+ this.stopListening(child);
1343
+ }
1344
+ }
1345
+ },
1346
+ createChild: function (type, properties) {
1347
+ this.transition('chaperone:create', type, properties);
1348
+ },
1349
+ attachChild: function (child, location, method) {
1350
+ this.transition('chaperone:attach', child, location, method || this.method);
1351
+ },
1352
+ createChildren: function () {
1353
+
1354
+ },
1355
+ renderChildren: function (children) {
1356
+ _.invoke(children, 'render');
1357
+ },
1358
+ attachChildren: function (children) {
1359
+ function attachChild(child) {
1360
+ if (child.location)
1361
+ this.attachChild(child, this.$('.' + child.location));
1362
+ else
1363
+ this.attachChild(child, this.$el, 'append');
1364
+ }
1365
+ _.each(children, attachChild, this);
1366
+ },
1367
+ updateChildren: function (children, properties) {
1368
+ _.invoke(children, 'updateProperties', properties);
1369
+ },
1370
+ closeChildren: function (children) {
1371
+ function stop(child) {
1372
+ this.stopListening(child);
1373
+ }
1374
+ //TODO: This bit is odd...
1375
+ _.each(children, stop, this);
1376
+ _.invoke(children, 'close');
1377
+ },
1378
+ at: function (index) {
1379
+ return this._children[index];
1380
+ }
1381
+ });
1382
+ Homefront.Layout = Homefront.Chaperone.extend({
1383
+ method: 'replaceWith',
1384
+ createChildren: function () {
1385
+ _.each(this.children, this.createChild, this);
1386
+ }
1387
+ });
1388
+ Homefront.Switch = Homefront.Chaperone.extend({
1389
+ when: {
1390
+ updating: function () {
1391
+ if (this._state !== undefined && this.stateChanged())
1392
+ this.renderAgain();
1393
+ }
1394
+ },
1395
+ getCases: function () {
1396
+ var feedback = '"getState" and "getCases" are not implemented';
1397
+ var guidance = Homefront.Guidance.Paragraph(feedback);
1398
+ return {
1399
+ not_implemented: guidance
1400
+ };
1401
+ },
1402
+ getState: function () {
1403
+ return 'not_implemented'
1404
+ },
1405
+ stateChanged: function(){
1406
+ return this._state !== this.getState(this.properties);
1407
+ },
1408
+ updateState: function () {
1409
+ var oldState = this._state;
1410
+ var newState = this.getState(this.properties);
1411
+ this._state = newState;
1412
+
1413
+ if (oldState !== newState) {
1414
+ oldState && this.transition('exiting:' + oldState, oldState);
1415
+ this.transition('entering:' + newState, newState);
1416
+ } else {
1417
+ oldState && this.transition('staying:' + oldState, oldState);
1418
+ }
1419
+ return newState;
1420
+ },
1421
+ createChildren: function () {
1422
+ var cases = this.getCases();
1423
+ var state = this.updateState();
1424
+ var candidate = cases[state];
1425
+ candidate && this.createChild(candidate);
1426
+ },
1427
+ attachChildren: function (children) {
1428
+ if (children.length === 1) {
1429
+ var candidate = children[0];
1430
+ candidate.$el.addClass(this.className);
1431
+ candidate.$el.addClass(this._state);
1432
+ this.attachChild(candidate, this.$el, 'replaceWith');
1433
+ this.setElement(candidate.el);
1434
+ }
1435
+ }
1436
+ });
1437
+
1438
+ Homefront.Expandable = Homefront.Switch.extend({
1439
+ summary: null,
1440
+ details: null,
1441
+ expanded: false,
1442
+ getCases: function () {
1443
+ return {
1444
+ summary: this.summary,
1445
+ details: this.details
1446
+ };
1447
+ },
1448
+ getState: function () {
1449
+ return this.expanded ? 'details' : 'summary';
1450
+ },
1451
+ expand: function () {
1452
+ this.expanded = true;
1453
+ this.renderAgain();
1454
+ },
1455
+ collapse: function () {
1456
+ this.expanded = false;
1457
+ this.renderAgain();
1458
+ }
1459
+ });
1460
+ Homefront.Enumerator = Homefront.Chaperone.extend({
1461
+ item: null,
1462
+ when: {
1463
+ chaperone: {
1464
+ add: function(child){
1465
+ child.location = this.item.location;
1466
+ }
1467
+ }
1468
+ },
1469
+ createChildren: function () {
1470
+ var items = this.selectItems(this.properties);
1471
+ function createChild(item) {
1472
+ this.createChild(this.item.view, item);
1473
+ }
1474
+ _.each(items, createChild, this);
1475
+ },
1476
+ updateChildren: function(children, properties){
1477
+ var items = this.selectItems(properties);
1478
+ function updateChild(child, index) {
1479
+ var item = items[index];
1480
+ child.updateProperties(item);
1481
+ }
1482
+ _.each(children, updateChild);
1093
1483
  },
1094
- execute: function(model, event){
1095
- throw Stormfront.Errors.UseCases.NOT_IMPLEMENTED;
1484
+ composeChild: function(item){
1485
+ return this.createChild(this.item, item);
1486
+ },
1487
+ selectItems: function (properties) {
1488
+ return properties;
1489
+ },
1490
+ at: function (index) {
1491
+ return this._children[index];
1492
+ }
1493
+ });
1494
+ Homefront.List = Homefront.Switch.extend({
1495
+ item: null,
1496
+ when: {
1497
+ initializing: function () {
1498
+ this.enumerator = Homefront.Enumerator.extend({
1499
+ tagName: this.tagName,
1500
+ template: this.template,
1501
+ className: this.className + ' stormfront.enumerator',
1502
+ item: this.item,
1503
+ getContext: this.getContext,
1504
+ selectItems: this.selectItems
1505
+ });
1506
+ }
1507
+ },
1508
+ getCases: function () {
1509
+ return {
1510
+ empty: this.empty,
1511
+ populated: this.enumerator
1512
+ }
1513
+ },
1514
+ getState: function (properties) {
1515
+ var items = this.selectItems(properties);
1516
+ return items && items.length > 0 ? 'populated' : 'empty';
1517
+ },
1518
+ selectItems: function (properties) {
1519
+ return properties.list ? properties.list() : properties;
1520
+ }
1521
+ });
1522
+ //TODO: This should have a better name I suppose
1523
+ Homefront.ActiveList = Homefront.List.extend({
1524
+ getCases: function () {
1525
+ //lazy shortcut
1526
+ this.populated = this.enumerator;
1527
+ return this;
1528
+ },
1529
+ getState: function (properties) {
1530
+ var state = properties.state;
1531
+ if (state === Stormfront.Ajax.SUCCESS)
1532
+ //TODO: Fetching inventory can return an error in the data
1533
+ return this.selectItems(properties).length === 0 ? 'empty' : properties.data.error ? 'error' : 'populated';
1534
+ else if (state === Stormfront.Ajax.START)
1535
+ return 'requesting';
1536
+ else if (state === Stormfront.Ajax.ERROR)
1537
+ return 'error';
1538
+ return state;
1539
+ },
1540
+ selectItems: function(properties) {
1541
+ return properties.data
1096
1542
  }
1097
1543
  });
1098
1544
 
1099
- Stormfront.Request = function(model, request, options){
1100
- var startTime = stormfront.time.now();
1101
- var overlay = new Stormfront.Overlay(options);
1102
- function tryRequest(){
1103
- return shouldWait() ? delayRequest() : sendRequest();
1545
+ //TODO: Got two of them now?
1546
+ Homefront.Series = Homefront.List.extend({
1547
+ getState: function (properties) {
1548
+ var state = properties.state();
1549
+ if (state === Stormfront.Ajax.SUCCESS) {
1550
+ var items = this.selectItems(properties);
1551
+ return items && items.length > 0 ? 'populated' : 'empty';
1552
+ }
1553
+ return state;
1554
+ },
1555
+ selectItems: function (properties) {
1556
+ return properties.list();
1104
1557
  }
1105
- function shouldWait(){
1106
- //TODO: This is a dependency upon the Indigo behavior
1107
- return options.wait && $.xhrPool.length > 0;
1558
+ });
1559
+ Homefront.Patterns.Selectable = function(method, action){
1560
+ var events = {};
1561
+ events[method] = 'select';
1562
+ return {
1563
+ events: events,
1564
+ action: action,
1565
+ select: function(){
1566
+ var action = stormfront.type(this.action);
1567
+ this.dispatch(new action(this.properties));
1568
+ }
1569
+ };
1570
+ };
1571
+
1572
+ Homefront.SelectableList = Homefront.Enumerator.extend({
1573
+ className: 'options',
1574
+ item: null,
1575
+ select: null,
1576
+ method: 'click',
1577
+ when: {
1578
+ initializing: function(){
1579
+ var item = stormfront.type(this.item);
1580
+ this.item = {
1581
+ view: item.extend(Homefront.Patterns.Selectable(this.method, this.select))
1582
+ };
1583
+ }
1584
+ },
1585
+ selectItems: function (properties) {
1586
+ return properties.list();
1108
1587
  }
1109
- function delayRequest(){
1110
- if (stormfront.time.now() - startTime < 5000)
1111
- setTimeout(tryRequest, 1000);
1112
- else
1113
- overlay.error('Request timed out. Please try again soon.');
1114
- return null;
1588
+ });
1589
+
1590
+ Homefront.SelectableDisplay = Homefront.Switch.extend({
1591
+ className: 'selected',
1592
+ select: null,
1593
+ guidance: '',
1594
+ display: null,
1595
+ getCases: function() {
1596
+ return {
1597
+ none: Homefront.Guidance.Paragraph(this.guidance),
1598
+ selected: this.display
1599
+ };
1600
+ },
1601
+ when: {
1602
+ entering: {
1603
+ none: function(){
1604
+ var entity = this.properties;
1605
+ if (entity.onlyOne()) {
1606
+ var action = stormfront.type(this.select);
1607
+ this.dispatch(new action(entity.first()));
1608
+ }
1609
+ }
1610
+ },
1611
+ updating: function(){
1612
+ this.render();
1613
+ }
1614
+ },
1615
+ getState: function(properties){
1616
+ return properties.selected() ? 'selected' : 'none';
1115
1617
  }
1116
- function sendRequest(){
1117
- function onComplete(){
1118
- removeListeners();
1119
- overlay.close();
1618
+ });
1619
+
1620
+ //TODO: Could be a list or a series.
1621
+ Homefront.Selection = Homefront.Switch.extend({
1622
+ className: 'selection',
1623
+ item: null,
1624
+ select: null,
1625
+ method: 'click',
1626
+ display: null,
1627
+ none: '',
1628
+ initial: '',
1629
+ empty: '',
1630
+ list: null,
1631
+ adapter: null,
1632
+ when: {
1633
+ initializing: function(){
1634
+ this.selectList = Homefront.SelectableList.extend({
1635
+ item: this.item,
1636
+ select: this.select,
1637
+ method: this.method || 'click'
1638
+ });
1639
+ this.selectDisplay = Homefront.SelectableDisplay.extend({
1640
+ select: this.select,
1641
+ display: this.display,
1642
+ guidance: this.none
1643
+ });
1120
1644
  }
1121
- function onError(model, xhr){
1122
- removeListeners();
1123
- overlay.error(xhr);
1645
+ },
1646
+ getCases: function(){
1647
+ return {
1648
+ initial: Homefront.Guidance.Paragraph(this.initial),
1649
+ empty: Homefront.Guidance.Paragraph(this.empty),
1650
+ populated: Homefront.Layout.extend({
1651
+ children: [this.selectList, this.selectDisplay]
1652
+ })
1124
1653
  }
1125
- function removeListeners(){
1126
- model.off('error', onError);
1127
- model.off('sync', onComplete);
1128
- model.off('invalid', onComplete);
1654
+ },
1655
+ getState: function (properties) {
1656
+ var state = properties.state();
1657
+ if (state === Stormfront.Ajax.SUCCESS)
1658
+ return properties.isEmpty() ? 'empty' : 'populated';
1659
+ return state;
1660
+ },
1661
+ selectProperties: function(properties){
1662
+ var adapter = stormfront.type(this.adapter);
1663
+ return new adapter(properties);
1664
+ }
1665
+ });
1666
+ Homefront.Search = Homefront.Layout.extend({
1667
+ className: 'search',
1668
+ adapter: null,
1669
+ when: {
1670
+ initializing: function () {
1671
+ var results = Homefront.Selection.extend({
1672
+ select: this.select,
1673
+ display: this.details,
1674
+ none: this.none,
1675
+ empty: this.empty,
1676
+ initial: this.initial,
1677
+ item: this.item,
1678
+ method: this.method || 'click'
1679
+ });
1680
+ this.children = [this.search, results]
1681
+ }
1682
+ },
1683
+ selectProperties: function (properties) {
1684
+ var adapter = stormfront.type(this.adapter);
1685
+ return new adapter(properties);
1686
+ }
1687
+ });
1688
+
1689
+ Homefront.Text = Homefront.View.extend({
1690
+ when: {
1691
+ updating: function(){
1692
+ this.updateValue();
1693
+ },
1694
+ rendering: function(){
1695
+ this.updateValue();
1696
+ }
1697
+ },
1698
+ updateValue: function(){
1699
+ this.$el.text(this.getValue(this.properties));
1700
+ },
1701
+ getValue: function(properties){
1702
+ return this.value || properties.value;
1703
+ }
1704
+ });
1705
+
1706
+ Homefront.Label = Homefront.Text.extend({
1707
+ tagName: 'label',
1708
+ when: {
1709
+ rendering: function() {
1710
+ this.$el.attr('for', this.properties.for);
1711
+ }
1712
+ }
1713
+ });
1714
+
1715
+ Homefront.Paragraph = Homefront.Text.extend({
1716
+ tagName: 'p'
1717
+ });
1718
+
1719
+ Homefront.Input = Homefront.Text.extend({
1720
+ tagName: 'input',
1721
+ enable: function(){
1722
+ this.$el.prop('disabled', false);
1723
+ },
1724
+ disable: function(){
1725
+ this.$el.prop('disabled', true);
1726
+ },
1727
+ updateValue: function(){
1728
+ this.$el.val(this.getValue(this.properties));
1729
+ },
1730
+ readValue: function(){
1731
+ return this.$el.val();
1732
+ }
1733
+ });
1734
+
1735
+ Homefront.Select = Homefront.Input.extend({
1736
+ tagName: 'select',
1737
+ events: {
1738
+ 'change': 'select'
1739
+ },
1740
+ when: {
1741
+ rendering: function(){
1742
+ this.updateSelect();
1743
+ },
1744
+ updating: function(){
1745
+ this.updateSelect();
1746
+ }
1747
+ },
1748
+ updateSelect: function(){
1749
+ this.updateOptions();
1750
+ this.updateValue();
1751
+ },
1752
+ updateOptions: function(){
1753
+ var options = this.getOptions(this.properties);
1754
+ function createOption(value){
1755
+ return $('<option>').val(value).text(value);
1129
1756
  }
1130
- model.once('invalid', onError);
1131
- model.once('error', onError);
1132
- model.once('sync', onComplete);
1757
+ this.$el.append(_.map(options, createOption));
1758
+ },
1759
+ getOptions: function(properties){
1760
+ return properties.options;
1761
+ },
1762
+ getValue: function(properties) {
1763
+ return properties.selected;
1764
+ },
1765
+ select: function(){
1766
+ this.transition('change', this.$el.val());
1767
+ }
1768
+ });
1133
1769
 
1134
- return request.call(model, options.args, options.options);
1770
+ Homefront.TextBox = Homefront.Input.extend({
1771
+ events: {
1772
+ 'change': 'change',
1773
+ 'blur': 'blur',
1774
+ 'keyup': 'special'
1775
+ },
1776
+ when: {
1777
+ rendering: function(){
1778
+ this.$el.attr('type', 'text');
1779
+ }
1780
+ },
1781
+ change: function(){
1782
+ this.transition('change', this.readValue());
1783
+ },
1784
+ blur: function(){
1785
+ this.transition('blur', this.readValue());
1786
+ },
1787
+ special: function(event){
1788
+ if (event.which === Keys.Enter)
1789
+ this.transition('enter', this.readValue());
1790
+ }
1791
+ });
1792
+
1793
+ Homefront.Clicker = Homefront.Input.extend({
1794
+ events: {
1795
+ 'click': 'click'
1796
+ },
1797
+ click: function(){
1798
+ this.transition('click');
1799
+ }
1800
+ });
1801
+
1802
+ Homefront.Button = Homefront.Clicker.extend({
1803
+ when: {
1804
+ rendering: function(){
1805
+ this.$el.attr('type', 'button');
1806
+ }
1807
+ }
1808
+ });
1809
+ Homefront.Leaf = Homefront.View.extend({
1810
+ when: {
1811
+ initializing: function() {
1812
+ this.template = stormfront.string(this.className).firstWord() || null;
1813
+ }
1135
1814
  }
1136
- return tryRequest();
1137
- };
1815
+ });