sudojs-rails 0.1.7 → 0.1.9

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.
@@ -11,19 +11,36 @@ var sudo = {
11
11
  //
12
12
  // `namespace`
13
13
  ext: {},
14
- // ###_inherit_
14
+ // ###getPath
15
+ // Extract a value located at `path` relative to the passed in object
16
+ //
17
+ // `param` {String} `path`. The key in the form of a dot-delimited path.
18
+ // `param` {object} `obj`. An object literal to operate on.
19
+ //
20
+ // `returns` {*|undefined}. The value at keypath or undefined if not found.
21
+ getPath: function getPath(path, obj) {
22
+ var key, p;
23
+ p = path.split('.');
24
+ for (key; p.length && (key = p.shift());) {
25
+ if(!p.length) {
26
+ return obj[key];
27
+ } else {
28
+ obj = obj[key] || {};
29
+ }
30
+ }
31
+ return obj;
32
+ },
33
+ // ###inherit
15
34
  // Inherit the prototype from a parent to a child.
16
35
  // Set the childs constructor for subclasses of child.
17
- // A _private_ method as subclasses of the library base classes will not
36
+ // Subclasses of the library base classes will not
18
37
  // want to use this function in *most* use-cases. Why? User Sudo Class Objects
19
38
  // possess their own constructors and any call back to a `superclass` constructor
20
39
  // will generally be looking for the library Object's constructor.
21
40
  //
22
41
  // `param` {function} `parent`
23
42
  // `param` {function} `child`
24
- //
25
- // `private`
26
- _inherit_: function _inherit_(parent, child) {
43
+ inherit: function inherit(parent, child) {
27
44
  child.prototype = Object.create(parent.prototype);
28
45
  child.prototype.constructor = child;
29
46
  },
@@ -34,13 +51,12 @@ var sudo = {
34
51
  // `returns` {string}
35
52
  makeMeASandwich: function makeMeASandwich() {return 'Okay.';},
36
53
  // ###namespace
37
- // Convenience method for assuring a Namespace is defined. Uses
38
- // the optional `obj` arg with the Base objects `setPath` method.
54
+ // Method for assuring a Namespace is defined.
39
55
  //
40
56
  // `param` {string} `path`. The path that leads to a blank Object.
41
57
  namespace: function namespace(path) {
42
- if (!sudo.Base.prototype.getPath.call(this, path, window)) {
43
- sudo.Base.prototype.setPath.call(this, path, {}, window);
58
+ if (!this.getPath(path, window)) {
59
+ this.setPath(path, {}, window);
44
60
  }
45
61
  },
46
62
  // ###premier
@@ -48,39 +64,69 @@ var sudo = {
48
64
  //
49
65
  // `type` {Object}
50
66
  premier: null,
51
- // ####_uid_
67
+ // ###setPath
68
+ // Traverse the keypath and get each object
69
+ // (or make blank ones) eventually setting the value
70
+ // at the end of the path
71
+ //
72
+ // `param` {string} `path`. The path to traverse when setting a value.
73
+ // `param` {*} `value`. What to set.
74
+ // `param` {Object} `obj`. The object literal to operate on.
75
+ setPath: function setPath(path, value, obj) {
76
+ var p = path.split('.'), key;
77
+ for (key; p.length && (key = p.shift());) {
78
+ if(!p.length) {
79
+ obj[key] = value;
80
+ } else if (obj[key]) {
81
+ obj = obj[key];
82
+ } else {
83
+ obj = obj[key] = {};
84
+ }
85
+ }
86
+ },
87
+ // ####uid
52
88
  // Some sudo Objects use a unique integer as a `tag` for identification.
53
89
  // (Views for example). This ensures they are indeed unique.
54
- //
55
- // `private`
56
- _uid_: 0,
57
- // ####_unique_
90
+ uid: 0,
91
+ // ####unique
58
92
  // An integer used as 'tags' by some sudo Objects as well
59
93
  // as a unique string for views when needed
60
94
  //
61
95
  // `param` {string} prefix. Optional string identifier
62
- //
63
- // `private`
64
- _unique_: function _unique_(prefix) {
65
- return prefix ? prefix + this._uid_++ : this._uid_++;
96
+ unique: function unique(prefix) {
97
+ return prefix ? prefix + this.uid++ : this.uid++;
98
+ },
99
+ // ###unsetPath
100
+ // Remove a key:value pair from this object's data store
101
+ // located at <path>
102
+ //
103
+ // `param` {String} `path`
104
+ // `param` {Object} `obj` The object to operate on.
105
+ unsetPath: function unsetPath(path, obj) {
106
+ var p = path.split('.'), key;
107
+ for (key; p.length && (key = p.shift());) {
108
+ if(!p.length) {
109
+ delete obj[key];
110
+ } else {
111
+ // this can fail if a faulty path is passed.
112
+ // using getPath beforehand can prevent that
113
+ obj = obj[key];
114
+ }
115
+ }
66
116
  }
67
117
  };
68
118
  // ##Base Class Object
69
119
  //
70
- // All sudo.js objects inherit base
71
- //
72
- // `param` {Object} data. An optional data object for this instance.
120
+ // All sudo.js objects inherit base, giving the ability
121
+ // to utilize delegation, the `base` function and the
122
+ // `construct` convenience method.
73
123
  //
74
124
  // `constructor`
75
- sudo.Base = function(data) {
76
- this._data_ = data || {};
125
+ sudo.Base = function() {
77
126
  // can delegate
78
- this._delegates_ = [];
79
- // may implement `observable`
80
- this._callbacks_ = [];
81
- this._changeRecords_ = [];
127
+ this.delegates = [];
82
128
  // a beautiful and unique snowflake
83
- this._uid_ = sudo._unique_();
129
+ this.uid = sudo.unique();
84
130
  };
85
131
  // ###addDelegate
86
132
  // Push an instance of a Class Object into this object's `_delegates_` list.
@@ -88,8 +134,8 @@ sudo.Base = function(data) {
88
134
  // `param` {Object} an instance of a sudo.delegates Class Object
89
135
  // `returns` {Object} `this`
90
136
  sudo.Base.prototype.addDelegate = function addDelegate(del) {
91
- del._delegator_ = this;
92
- this._delegates_.push(del);
137
+ del.delegator = this;
138
+ this.delegates.push(del);
93
139
  return this;
94
140
  };
95
141
  // ###base
@@ -99,6 +145,7 @@ sudo.Base.prototype.addDelegate = function addDelegate(del) {
99
145
  // that the first match is not the function that called `base`.
100
146
  //
101
147
  // `params` {*} any other number of arguments to be passed to the looked up method
148
+ // along with the initial method name
102
149
  sudo.Base.prototype.base = function base() {
103
150
  var args = Array.prototype.slice.call(arguments),
104
151
  name = args.shift(),
@@ -127,28 +174,20 @@ sudo.Base.prototype.construct = function construct() {
127
174
  // 1. if `meth` is falsy return the delegate.
128
175
  // 2 if `meth` is truthy bind its method (to the delegate) and return the method
129
176
  //
130
- // `param` {String} `role` The _role_ property to match in this object's delegates list
177
+ // `param` {String} `role` The role property to match in this object's delegates list
131
178
  // `param` {String} `meth` Optional method to bind to the action this delegate is being used for
132
179
  // `returns`
133
180
  sudo.Base.prototype.delegate = function delegate(role, meth) {
134
- var del = this._delegates_, i;
181
+ var del = this.delegates, i;
135
182
  for(i = 0; i < del.length; i++) {
136
- if(del[i]._role_ === role) {
183
+ if(del[i].role === role) {
137
184
  if(!meth) return del[i];
138
185
  return del[i][meth].bind(del[i]);
139
186
  }
140
187
  }
141
188
  };
142
- // ###get
143
- // Returns the value associated with a key in this object's data store.
144
- //
145
- // `param` {String} `key`. The name of the key
146
- // `returns` {*}. The value associated with the key or false if not found.
147
- sudo.Base.prototype.get = function get(key) {
148
- return this._data_[key];
149
- };
150
189
  // ###getDelegate
151
- // Fetch a delegate whose _role_ property matches the passed in argument.
190
+ // Fetch a delegate whose role property matches the passed in argument.
152
191
  // Uses the `delegate` method in its 'single argument' form, included for
153
192
  // API consistency
154
193
  //
@@ -157,24 +196,59 @@ sudo.Base.prototype.get = function get(key) {
157
196
  sudo.Base.prototype.getDelegate = function getDelegate(role) {
158
197
  return this.delegate(role);
159
198
  };
160
- // ###getPath
161
- // Extract a value located at `path` relative to this objects data store
199
+ // ###removeDelegate
200
+ // From this objects `delegates` list remove the object (there should only ever be 1)
201
+ // whose role matches the passed in argument
162
202
  //
163
- // `param` {String} `path`. The key in the form of a dot-delimited path.
164
- // `param` {boolean} `obj`. Optional override, forcing getPath to operate on the passed in `obj`
165
- // argument rather than the default data store owned by `this` Object.
166
- // `returns` {*|undefined}. The value at keypath or undefined if not found.
167
- sudo.Base.prototype.getPath = function getPath(path, obj) {
168
- var key, curr = obj || this._data_, p;
169
- p = path.split('.');
170
- for (key; p.length && (key = p.shift());) {
171
- if(!p.length) {
172
- return curr[key];
173
- } else {
174
- curr = curr[key] || {};
203
+ // `param` {String} `role`
204
+ // `returns` {Object} `this`
205
+ sudo.Base.prototype.removeDelegate = function removeDelegate(role) {
206
+ var del = this.delegates, i;
207
+ for(i = 0; i < del.length; i++) {
208
+ if(del[i].role === role) {
209
+ // no _delegator_ for you
210
+ del[i].delegator = void 0;
211
+ del.splice(i, 1);
212
+ return this;
175
213
  }
176
214
  }
177
- return curr;
215
+ return this;
216
+ };
217
+ // `private`
218
+ sudo.Base.prototype.role = 'base';
219
+ // ##Model Class Object
220
+ //
221
+ // Model Objects expose methods for setting and getting data, and
222
+ // can be observed if implementing the `Observable Extension`
223
+ //
224
+ // `param` {object} data. An initial state for this model.
225
+ //
226
+ // `constructor`
227
+ sudo.Model = function(data) {
228
+ this.data = data || {};
229
+ // only models are `observable`
230
+ this.callbacks = [];
231
+ this.changeRecords = [];
232
+ };
233
+ // Model inherits from sudo.Base
234
+ // `private`
235
+ sudo.inherit(sudo.Base, sudo.Model);
236
+ // ###get
237
+ // Returns the value associated with a key.
238
+ //
239
+ // `param` {String} `key`. The name of the key
240
+ // `returns` {*}. The value associated with the key or false if not found.
241
+ sudo.Model.prototype.get = function get(key) {
242
+ return this.data[key];
243
+ };
244
+ // ###getPath
245
+ //
246
+ // Uses the sudo namespace's getpath function operating on the model's
247
+ // data hash.
248
+ //
249
+ // `returns` {*|undefined}. The value at keypath or undefined if not found.
250
+ sudo.Model.prototype.getPath = function getPath(path) {
251
+ return sudo.getPath(path, this.data);
178
252
  };
179
253
  // ###gets
180
254
  // Assembles and returns an object of key:value pairs for each key
@@ -182,66 +256,38 @@ sudo.Base.prototype.getPath = function getPath(path, obj) {
182
256
  //
183
257
  // `param` {array} `ary`. An array of keys.
184
258
  // `returns` {object}
185
- sudo.Base.prototype.gets = function gets(ary) {
259
+ sudo.Model.prototype.gets = function gets(ary) {
186
260
  var i, obj = {};
187
261
  for (i = 0; i < ary.length; i++) {
188
- obj[ary[i]] = ary[i].indexOf('.') === -1 ? this._data_[ary[i]] :
262
+ obj[ary[i]] = ary[i].indexOf('.') === -1 ? this.data[ary[i]] :
189
263
  this.getPath(ary[i]);
190
264
  }
191
265
  return obj;
192
266
  };
193
- // ###removeDelegate
194
- // From this objects `delegates` list remove the object (there should only ever be 1)
195
- // whose _role_ matches the passed in argument
196
- //
197
- // `param` {String} `role`
198
- // `returns` {Object} `this`
199
- sudo.Base.prototype.removeDelegate = function removeDelegate(role) {
200
- var del = this._delegates_, i;
201
- for(i = 0; i < del.length; i++) {
202
- if(del[i]._role_ === role) {
203
- // no _delegator_ for you
204
- del[i]._delegator_ = void 0;
205
- del.splice(i, 1);
206
- return this;
207
- }
208
- }
209
- return this;
210
- };
211
267
  // `private`
212
- sudo.Base.prototype._role_ = 'base';
268
+ sudo.Model.prototype.role = 'model';
213
269
  // ###set
214
- // Set a key:value pair in `this` object's data store.
270
+ // Set a key:value pair.
215
271
  //
216
272
  // `param` {String} `key`. The name of the key.
217
273
  // `param` {*} `value`. The value associated with the key.
218
274
  // `returns` {Object} `this`
219
- sudo.Base.prototype.set = function set(key, value) {
275
+ sudo.Model.prototype.set = function set(key, value) {
220
276
  // _NOTE: intentional possibilty of setting a falsy value_
221
- this._data_[key] = value;
277
+ this.data[key] = value;
222
278
  return this;
223
279
  };
224
280
  // ###setPath
225
- // Traverse the keypath and get each object
226
- // (or make blank ones) eventually setting the value
227
- // at the end of the path
228
281
  //
229
- // `param` {string} `path`. The path to traverse when setting a value.
230
- // `param` {*} `value`. What to set.
231
- // `param` {Object} `obj`. Optional flag to force setPath to operate on the passed object.
232
- // `returns` {Object} `this`
233
- sudo.Base.prototype.setPath = function setPath(path, value, obj) {
234
- var curr = obj || this._data_, p = path.split('.'), key;
235
- for (key; p.length && (key = p.shift());) {
236
- if(!p.length) {
237
- curr[key] = value;
238
- } else if (curr[key]) {
239
- curr = curr[key];
240
- } else {
241
- curr = curr[key] = {};
242
- }
243
- }
244
- return this;
282
+ // Uses the sudo namespace's setpath function operating on the model's
283
+ // data hash.
284
+ //
285
+ // `param` {String} `path`
286
+ // `param` {*} `value`
287
+ // `returns` {Object} this.
288
+ sudo.Model.prototype.setPath = function setPath(path, value) {
289
+ sudo.setPath(path, value, this.data);
290
+ return this;
245
291
  };
246
292
  // ###sets
247
293
  // Invokes `set()` or `setPath()` for each key value pair in `obj`.
@@ -249,7 +295,7 @@ sudo.Base.prototype.setPath = function setPath(path, value, obj) {
249
295
  //
250
296
  // `param` {Object} `obj`. The keys and values to set.
251
297
  // `returns` {Object} `this`
252
- sudo.Base.prototype.sets = function sets(obj) {
298
+ sudo.Model.prototype.sets = function sets(obj) {
253
299
  var i, k = Object.keys(obj);
254
300
  for(i = 0; i < k.length; i++) {
255
301
  k[i].indexOf('.') === -1 ? this.set(k[i], obj[k[i]]) :
@@ -262,35 +308,25 @@ sudo.Base.prototype.sets = function sets(obj) {
262
308
  //
263
309
  // `param` {String} key
264
310
  // `returns` {Object} `this`
265
- sudo.Base.prototype.unset = function unset(key) {
266
- delete this._data_[key];
311
+ sudo.Model.prototype.unset = function unset(key) {
312
+ delete this.data[key];
267
313
  return this;
268
314
  };
269
315
  // ###unsetPath
270
- // Remove a key:value pair from this object's data store
271
- // located at <path>
316
+ // Uses `sudo.unsetPath` operating on this models data hash
272
317
  //
273
318
  // `param` {String} path
274
319
  // `returns` {Object} `this`
275
- sudo.Base.prototype.unsetPath = function unsetPath(path) {
276
- var curr = this._data_, p = path.split('.'), key;
277
- for (key; p.length && (key = p.shift());) {
278
- if(!p.length) {
279
- delete curr[key];
280
- } else {
281
- // this can fail if a faulty path is passed.
282
- // using getPath beforehand can prevent that
283
- curr = curr[key];
284
- }
285
- }
286
- return this;
320
+ sudo.Model.prototype.unsetPath = function unsetPath(path) {
321
+ sudo.unsetPath(path, this.data);
322
+ return this;
287
323
  };
288
324
  // ###unsets
289
325
  // Deletes a number of keys or paths from this object's data store
290
326
  //
291
327
  // `param` {array} `ary`. An array of keys or paths.
292
328
  // `returns` {Objaect} `this`
293
- sudo.Base.prototype.unsets = function unsets(ary) {
329
+ sudo.Model.prototype.unsets = function unsets(ary) {
294
330
  var i;
295
331
  for(i = 0; i < ary.length; i++) {
296
332
  ary[i].indexOf('.') === -1 ? this.unset(ary[i]) :
@@ -304,13 +340,13 @@ sudo.Base.prototype.unsets = function unsets(ary) {
304
340
  // itself be contained
305
341
  //
306
342
  // `constructor`
307
- sudo.Container = function(data) {
308
- sudo.Base.call(this, data);
309
- this._children_ = [];
310
- this._childNames_ = {};
343
+ sudo.Container = function() {
344
+ sudo.Base.call(this);
345
+ this.children = [];
346
+ this.childNames = {};
311
347
  };
312
- // `private`
313
- sudo._inherit_(sudo.Base, sudo.Container);
348
+ // Container is a subclass of sudo.Base
349
+ sudo.inherit(sudo.Base, sudo.Container);
314
350
  // ###addChild
315
351
  // Adds a View to this container's list of children.
316
352
  // Also adds an 'index' property and an entry in the childNames hash.
@@ -320,12 +356,12 @@ sudo._inherit_(sudo.Base, sudo.Container);
320
356
  // `param` {String} `name`. An optional name for the child that will go in the childNames hash.
321
357
  // `returns` {Object} `this`
322
358
  sudo.Container.prototype.addChild = function addChild(child, name) {
323
- var c = this._children_;
324
- child._parent_ = this;
325
- child._index_ = c.length;
359
+ var c = this.children;
360
+ child.parent = this;
361
+ child.index = c.length;
326
362
  if(name) {
327
- child._name_ = name;
328
- this._childNames_[name] = child._index_;
363
+ child.name = name;
364
+ this.childNames[name] = child.index;
329
365
  }
330
366
  c.push(child);
331
367
  if('addedToParent' in child) child.addedToParent(this);
@@ -335,7 +371,7 @@ sudo.Container.prototype.addChild = function addChild(child, name) {
335
371
  // By default, `bubble` returns the current view's parent (if it has one)
336
372
  //
337
373
  // `returns` {Object|undefined}
338
- sudo.Container.prototype.bubble = function bubble() {return this._parent_;};
374
+ sudo.Container.prototype.bubble = function bubble() {return this.parent;};
339
375
  // ###getChild
340
376
  // If a child was added with a name, via `addChild`,
341
377
  // that object can be fetched by name. This prevents us from having to reference a
@@ -344,8 +380,8 @@ sudo.Container.prototype.bubble = function bubble() {return this._parent_;};
344
380
  // `param` {String|Number} `id`. The string `name` or numeric `index` of the child to fetch.
345
381
  // `returns` {Object|undefined} The found child
346
382
  sudo.Container.prototype.getChild = function getChild(id) {
347
- return typeof id === 'string' ? this._children_[this._childNames_[id]] :
348
- this._children_[id];
383
+ return typeof id === 'string' ? this.children[this.childNames[id]] :
384
+ this.children[id];
349
385
  };
350
386
  // ###_indexChildren_
351
387
  // Method is called with the `index` property of a subview that is being removed.
@@ -353,11 +389,11 @@ sudo.Container.prototype.getChild = function getChild(id) {
353
389
  // `param` {Number} `i`
354
390
  // `private`
355
391
  sudo.Container.prototype._indexChildren_ = function _indexChildren_(i) {
356
- var c = this._children_, obj = this._childNames_, len;
392
+ var c = this.children, obj = this.childNames, len;
357
393
  for (len = c.length; i < len; i++) {
358
- c[i]._index_--;
394
+ c[i].index--;
359
395
  // adjust any entries in childNames
360
- if(c[i]._name_ in obj) obj[c[i]._name_] = c[i]._index_;
396
+ if(c[i].name in obj) obj[c[i].name] = c[i].index;
361
397
  }
362
398
  };
363
399
  // ###removeChild
@@ -370,28 +406,19 @@ sudo.Container.prototype._indexChildren_ = function _indexChildren_(i) {
370
406
  // An object will be assumed to be an actual sudo Class Object.
371
407
  // `returns` {Object} `this`
372
408
  sudo.Container.prototype.removeChild = function removeChild(arg) {
373
- var t = typeof arg, c;
374
- // passed an object (or an array - which will fail so don't do that), proceed
375
- if(t === 'object') this._removeChild_(arg);
376
- else {
377
- c = t === 'string' ? this._children_[this._childNames_[arg]] : this._children_[arg];
378
- this._removeChild_(c);
379
- }
380
- return this;
381
- };
382
- // ###_removeChild_
383
- // Helper method for 'public' removeChild
384
- // `param` {Object} `child`. The view that is going to be removed.
385
- // `private`
386
- sudo.Container.prototype._removeChild_ = function _removeChild_(child) {
387
- var i = child._index_;
409
+ var i, t = typeof arg, c;
410
+ // normalize the input
411
+ if(t === 'object') c = arg;
412
+ else c = t === 'string' ? this.children[this.childNames[arg]] : this.children[arg];
413
+ i = c.index;
388
414
  // remove from the children Array
389
- this._children_.splice(i, 1);
415
+ this.children.splice(i, 1);
390
416
  // remove from the named child hash if present
391
- delete this._childNames_[child._name_];
417
+ delete this.childNames[c.name];
392
418
  // child is now an `orphan`
393
- delete child._parent_;
419
+ delete c.parent;
394
420
  this._indexChildren_(i);
421
+ return this;
395
422
  };
396
423
  // ###removeFromParent
397
424
  // Remove this object from its parents list of children.
@@ -399,10 +426,10 @@ sudo.Container.prototype._removeChild_ = function _removeChild_(child) {
399
426
  // or chaining method calls
400
427
  sudo.Container.prototype.removeFromParent = function removeFromParent() {
401
428
  // will error without a parent, but that would be your fault...
402
- this._parent_._removeChild_(this);
429
+ this.parent.removeChild(this);
403
430
  return this;
404
431
  };
405
- sudo.Container.prototype._role_ = 'container';
432
+ sudo.Container.prototype.role = 'container';
406
433
  // ###send
407
434
  // The call to the specific method on a (un)specified target happens here.
408
435
  // If this Object is part of a `sudo.ext.container` maintained hierarchy
@@ -414,14 +441,14 @@ sudo.Container.prototype._role_ = 'container';
414
441
  // A sendMethod will be located by:
415
442
  // 1. using the first argument if it is a string
416
443
  // 2. looking for a `sendMethod` property if it is an object
417
- // In the case a specified target exists at `this.get('sendTarget')` it will be used
444
+ // In the case a specified target exists at `this.model.get('sendTarget')` it will be used
418
445
  // Any other args will be passed to the sendMethod after `this`
419
446
  // `returns` {Object} `this`
420
447
  sudo.Container.prototype.send = function send(/*args*/) {
421
448
  var args = Array.prototype.slice.call(arguments),
422
- meth, targ, fn;
449
+ d = this.model && this.model.data, meth, targ, fn;
423
450
  // normalize the input, common use cases first
424
- if('sendMethod' in this._data_) meth = this._data_.sendMethod;
451
+ if(d && 'sendMethod' in d) meth = d.sendMethod;
425
452
  else if(typeof args[0] === 'string') meth = args.shift();
426
453
  // less common but viable options
427
454
  if(!meth) {
@@ -431,7 +458,7 @@ sudo.Container.prototype.send = function send(/*args*/) {
431
458
  args[0].sendMethod || void 0;
432
459
  }
433
460
  // target is either specified or my parent
434
- targ = this._data_['sendTarget'] || this._parent_;
461
+ targ = d && d.sendTarget || this.parent;
435
462
  // obvious chance for errors here, don't be dumb
436
463
  fn = targ[meth];
437
464
  while(!fn && (targ = targ.bubble())) {
@@ -459,17 +486,19 @@ sudo.Container.prototype.send = function send(/*args*/) {
459
486
  // a shortcut for `this.$el.find(selector)`
460
487
  //
461
488
  // `param` {string|element|jQuery} `el`. Otional el for the View instance.
462
- // `param` {Object} `data`. Optional data object.
489
+ // `param` {Object} `data`. Optional data object which becomes the initial state
490
+ // of a new model located at `this.model`.
463
491
  //
464
492
  // `constructor`
465
493
  sudo.View = function(el, data) {
466
- sudo.Container.call(this, data);
494
+ sudo.Container.call(this);
495
+ if(data) this.model = new sudo.Model(data);
467
496
  this.setEl(el);
468
- if(this._role_ === 'view') this.init();
497
+ if(this.role === 'view') this.init();
469
498
  };
470
499
  // View inherits from Container
471
500
  // `private`
472
- sudo._inherit_(sudo.Container, sudo.View);
501
+ sudo.inherit(sudo.Container, sudo.View);
473
502
  // ###becomePremier
474
503
  // Premier functionality provides hooks for behavioral differentiation
475
504
  // among elements or class objects.
@@ -477,11 +506,11 @@ sudo._inherit_(sudo.Container, sudo.View);
477
506
  // `returns` {Object} `this`
478
507
  sudo.View.prototype.becomePremier = function becomePremier() {
479
508
  var p, f = function() {
480
- this._isPremier_ = true;
509
+ this.isPremier = true;
481
510
  sudo.premier = this;
482
511
  }.bind(this);
483
512
  // is there an existing premier that isn't me?
484
- if((p = sudo.premier) && p._uid_ !== this._uid_) {
513
+ if((p = sudo.premier) && p.uid !== this.uid) {
485
514
  // ask it to resign and call the cb
486
515
  p.resignPremier(f);
487
516
  } else f(); // no existing premier
@@ -510,9 +539,9 @@ sudo.View.prototype._normalizedEl_ = function _normalizedEl_(el) {
510
539
  // `returns` {Object} `this`
511
540
  sudo.View.prototype.resignPremier = function resignPremier(cb) {
512
541
  var p;
513
- this._isPremier_ = false;
542
+ this.isPremier = false;
514
543
  // only remove the global premier if it is me
515
- if((p = sudo.premier) && p._uid_ === this._uid_) {
544
+ if((p = sudo.premier) && p.uid === this.uid) {
516
545
  sudo.premier = null;
517
546
  }
518
547
  // fire the cb if passed
@@ -520,7 +549,7 @@ sudo.View.prototype.resignPremier = function resignPremier(cb) {
520
549
  return this;
521
550
  };
522
551
  // `private`
523
- sudo.View.prototype._role_ = 'view';
552
+ sudo.View.prototype.role = 'view';
524
553
  // ###setEl
525
554
  // A view must have an element, set that here.
526
555
  // Stores a jquerified object as `this.$el` the raw
@@ -529,12 +558,12 @@ sudo.View.prototype._role_ = 'view';
529
558
  // `param` {string=|element} `el`
530
559
  // `returns` {Object} `this`
531
560
  sudo.View.prototype.setEl = function setEl(el) {
532
- var a, t;
561
+ var d = this.model && this.model.data, a, t;
533
562
  if(!el) {
534
563
  // normalize any relevant data
535
- t = this._data_['tagName'] || 'div';
564
+ t = d ? d.tagName || 'div': 'div';
536
565
  this.$el = $(document.createElement(t));
537
- if((a = this._data_['attributes'])) this.$el.attr(a);
566
+ if(d && (a = d.attributes)) this.$el.attr(a);
538
567
  } else {
539
568
  this.$el = this._normalizedEl_(el);
540
569
  }
@@ -587,14 +616,16 @@ sudo.ViewController = function(el, data) {
587
616
  'ajax:aborted:file': 'onAjaxAbortedFile'
588
617
  };
589
618
  // can be called again if mapping changes...
590
- this.doMapping();
591
- if('descriptor' in this._data_) this.instantiateChildren([this._data_.descriptor]);
592
- if('descriptors' in this._data_) this.instantiateChildren();
593
- if(this._role_ === 'viewController') this.init();
619
+ if(data) {
620
+ this.doMapping();
621
+ if('descriptor' in data) this.instantiateChildren([data.descriptor]);
622
+ else if('descriptors' in data) this.instantiateChildren();
623
+ }
624
+ if(this.role === 'viewController') this.init();
594
625
  };
595
626
  // ViewController inherits from View.
596
627
  // `private`
597
- sudo._inherit_(sudo.View, sudo.ViewController);
628
+ sudo.inherit(sudo.View, sudo.ViewController);
598
629
  // ###doMapping
599
630
  //
600
631
  // assign the proxy mapping for events. This can be called at any time
@@ -604,12 +635,12 @@ sudo._inherit_(sudo.View, sudo.ViewController);
604
635
  sudo.ViewController.prototype.doMapping = function() {
605
636
  // either a single event or an array of them
606
637
  var i,
607
- toMap = this._data_['ujsEvent'] || this._data_['ujsEvents'];
638
+ toMap = this.model.data.ujsEvent || this.model.data.ujsEvents;
608
639
  if(toMap) {
609
- if(typeof toMap === 'string') this._mapEvent(toMap);
640
+ if(typeof toMap === 'string') this._mapEvent_(toMap);
610
641
  else {
611
642
  for(i = 0; i < toMap.length; i++) {
612
- this._mapEvent(toMap[i]);
643
+ this._mapEvent_(toMap[i]);
613
644
  }
614
645
  }
615
646
  }
@@ -628,7 +659,7 @@ sudo.ViewController.prototype._handleObserve_ = function _handleObserve_(obs, c)
628
659
  //
629
660
  // `returns` {object} `this`
630
661
  sudo.ViewController.prototype.instantiateChildren = function instantiateChildren(ary) {
631
- var i, j, curr, c, d = ary || this._data_.descriptors;
662
+ var i, j, curr, c, d = ary || this.model.data.descriptors;
632
663
  for(i = 0; i < d.length; i++) {
633
664
  curr = d[i];
634
665
  c = new curr.is_a(curr.el, curr.data);
@@ -645,10 +676,10 @@ sudo.ViewController.prototype.instantiateChildren = function instantiateChildren
645
676
  }
646
677
  return this;
647
678
  };
648
- // ###_mapEvent
679
+ // ###_mapEvent_
649
680
  // Maps the ajax:event names to methods
650
681
  // `private`
651
- sudo.ViewController.prototype._mapEvent = function(name) {
682
+ sudo.ViewController.prototype._mapEvent_ = function _mapEvent_(name) {
652
683
  // because the signatures vary we need specific methods
653
684
  this.$el.on(name, this[this.eventMap[name]].bind(this));
654
685
  };
@@ -658,7 +689,7 @@ sudo.ViewController.prototype._mapEvent = function(name) {
658
689
  // when viewController's are instantiated.
659
690
  // `private`
660
691
  sudo.ViewController.prototype._objectForPath_ = function _objectForPath_(path) {
661
- return sudo.Base.prototype.getPath.call(this, path, window);
692
+ return sudo.getPath(path, window);
662
693
  };
663
694
  // Virtual methods to override in your child classes for
664
695
  // any events you chose to listen for
@@ -670,7 +701,7 @@ sudo.ViewController.prototype.onAjaxComplete = $.noop;
670
701
  sudo.ViewController.prototype.onAjaxSuccess = $.noop;
671
702
  sudo.ViewController.prototype.onAjaxError = $.noop;
672
703
  // `private`
673
- sudo.ViewController.prototype._role_ = 'viewController';
704
+ sudo.ViewController.prototype.role = 'viewController';
674
705
  // ###Templating
675
706
 
676
707
  // Allow the default {{ js code }}, {{= key }}, and {{- escape stuff }}
@@ -789,26 +820,29 @@ sudo.template = function template(str, data, scope) {
789
820
  //
790
821
  //`constructor`
791
822
  sudo.Dataview = function(el, data) {
792
- var d, t;
793
- sudo.View.call(this, el, data);
823
+ var d = data || {}, t;
824
+ sudo.View.call(this, el, d);
794
825
  // implements the listener extension
795
826
  $.extend(this, sudo.ext.listener);
796
- d = this._data_;
797
- // dont autoRender on the setting of events
827
+ // dataview's models are observable
828
+ $.extend(this.model, sudo.ext.observable);
829
+ // dont autoRender on the setting of events,
798
830
  // add to this to prevent others if needed
799
- d.autoRenderBlacklist = {event: true, events: true};
831
+ this.autoRenderBlacklist = {event: true, events: true};
832
+ // if autorendering, observe your own model
833
+ // use this ref to unobserve if desired
834
+ if(d.autoRender) this.observer = this.model.observe(this.render.bind(this));
800
835
  // compile my template if not already done
801
836
  if((t = d.template)) {
802
- if(typeof t === 'string') d.template = sudo.template(t);
837
+ if(typeof t === 'string') this.model.data.template = sudo.template(t);
803
838
  }
804
- if(this._role_ === 'dataview') {
805
- // as all events are delegated to this.$el binding can take place here
839
+ if(this.role === 'dataview') {
806
840
  this.bindEvents();
807
841
  this.init();
808
842
  }
809
843
  };
810
844
  // `private`
811
- sudo._inherit_(sudo.View, sudo.Dataview);
845
+ sudo.inherit(sudo.View, sudo.Dataview);
812
846
  // ###addedToParent
813
847
  // Container's will check for the presence of this method and call it if it is present
814
848
  // after adding a child - essentially, this will auto render the dataview when added to a parent
@@ -821,7 +855,7 @@ sudo.Dataview.prototype.addedToParent = function() {
821
855
  //
822
856
  // `returns` {Object} `this`
823
857
  sudo.Dataview.prototype.removeFromParent = function removeFromParent() {
824
- this._parent_._removeChild_(this);
858
+ this.parent.removeChild(this);
825
859
  this.$el.remove();
826
860
  return this;
827
861
  };
@@ -833,9 +867,13 @@ sudo.Dataview.prototype.removeFromParent = function removeFromParent() {
833
867
  // Event unbinding/rebinding is generally not necessary for the Objects innerHTML as all events from the
834
868
  // Object's list of events (`this.get('event(s)'))` are delegated to the $el on instantiation.
835
869
  //
870
+ // `param` {object} `change` dataviews may be observing their model if `autoRender: true`
871
+ //
836
872
  // `returns` {Object} `this`
837
- sudo.Dataview.prototype.render = function render() {
838
- var d = this._data_;
873
+ sudo.Dataview.prototype.render = function render(change) {
874
+ // return early if a `blacklisted` key is set to my model
875
+ if(change && this.autoRenderBlacklist[change.name]) return this;
876
+ var d = this.model.data;
839
877
  this.$el.html(d.template(d));
840
878
  if(d.renderTarget) {
841
879
  this._normalizedEl_(d.renderTarget)[d.renderMethod || 'append'](this.$el);
@@ -844,48 +882,11 @@ sudo.Dataview.prototype.render = function render() {
844
882
  return this;
845
883
  };
846
884
  // `private`
847
- sudo.Dataview.prototype._role_ = 'dataview';
848
- // ###set
849
- // Override `sudo.Base.set` to provide auto rendering if desired
850
- //
851
- // `returns` {Object|*} `this` or call to `render`
852
- sudo.Dataview.prototype.set = function set(key, value) {
853
- this._data_[key] = value;
854
- if(this._data_['autoRender'] && !this._data_.autoRenderBlacklist[key]) return this.render();
855
- return this;
856
- };
857
- // ###setPath
858
- // Override `sudo.Base.setPath` to provide auto rendering if desired
859
- //
860
- // `returns` {Object|*} `this` or call to `render`
861
- sudo.Dataview.prototype.setPath = function setPath(path, value) {
862
- sudo.Base.prototype.setPath.call(this, path, value);
863
- // the blacklist has no paths
864
- if(this._data_['autoRender']) return this.render();
865
- return this;
866
- };
867
- // ###sets
868
- // Override `sudo.Base.sets` in case of autoRender.being true
869
- // Temporarily sets `autoRender` to false (if true) to avoid unnecessary
870
- // rendering, calling `render` when finished with all `set` type operations,
871
- // returning `autoRender` to true (if appropriate)
872
- //
873
- // `returns` {Object|*} `this` or call to `render`
874
- sudo.Dataview.prototype.sets = function sets(obj) {
875
- var a;
876
- if(this._data_['autoRender']) {
877
- this._data_['autoRender'] = false;
878
- a = true;
879
- }
880
- sudo.Base.prototype.sets.call(this, obj);
881
- if(a) {
882
- this._data_['autoRender'] = true;
883
- return this.render();
884
- }
885
- return this;
886
- };
885
+ sudo.Dataview.prototype.role = 'dataview';
887
886
  // ## Observable Extension Object
888
- // Implementaion of the ES6 Harmony Observer pattern
887
+ // Implementaion of the ES6 Harmony Observer pattern.
888
+ // Extend a `sudo.Model` class with this object if
889
+ // data-mutation-observation is required
889
890
  sudo.ext.observable = {
890
891
  // ###_deliver_
891
892
  // Called from deliverChangeRecords when ready to send
@@ -893,7 +894,7 @@ sudo.ext.observable = {
893
894
  //
894
895
  // `private`
895
896
  _deliver_: function _deliver_(obj) {
896
- var i, cb = this._callbacks_;
897
+ var i, cb = this.callbacks;
897
898
  for(i = 0; i < cb.length; i++) {
898
899
  cb[i](obj);
899
900
  }
@@ -904,7 +905,7 @@ sudo.ext.observable = {
904
905
  //
905
906
  // `returns` {Object} `this`
906
907
  deliverChangeRecords: function deliverChangeRecords() {
907
- var rec, cr = this._changeRecords_;
908
+ var rec, cr = this.changeRecords;
908
909
  // FIFO
909
910
  for(rec; cr.length && (rec = cr.shift());) {
910
911
  this._deliver_(rec);
@@ -929,7 +930,7 @@ sudo.ext.observable = {
929
930
  observe: function observe(fn) {
930
931
  // this will fail if mixed-in and no `callbacks` created so don't do that.
931
932
  // Per the spec, do not allow the same callback to be added
932
- var d = this._callbacks_;
933
+ var d = this.callbacks;
933
934
  if(d.indexOf(fn) === -1) d.push(fn);
934
935
  return fn;
935
936
  },
@@ -955,16 +956,16 @@ sudo.ext.observable = {
955
956
  //
956
957
  // `returns` {Object|*} `this` or calls deliverChangeRecords
957
958
  set: function set(key, value, hold) {
958
- var obj = {name: key, object: this._data_};
959
+ var obj = {name: key, object: this.data};
959
960
  // did this key exist already
960
- if(key in this._data_) {
961
+ if(key in this.data) {
961
962
  obj.type = 'updated';
962
963
  // then there is an oldValue
963
- obj.oldValue = this._data_[key];
964
+ obj.oldValue = this.data[key];
964
965
  } else obj.type = 'new';
965
966
  // now actually set the value
966
- this._data_[key] = value;
967
- this._changeRecords_.push(obj);
967
+ this.data[key] = value;
968
+ this.changeRecords.push(obj);
968
969
  // call the observers or not
969
970
  if(hold) return this;
970
971
  return this.deliverChangeRecords();
@@ -973,7 +974,7 @@ sudo.ext.observable = {
973
974
  // Overrides sudo.Base.setPath to check for observers.
974
975
  // Change records originating from a `setPath` operation
975
976
  // send back the passed in `path` as `name` as well as the
976
- // top level object being observed (this observable's _data_).
977
+ // top level object being observed (this observable's data).
977
978
  // this allows for easy filtering either manually or via a
978
979
  // `change delegate`
979
980
  //
@@ -983,7 +984,7 @@ sudo.ext.observable = {
983
984
  // to be delivered upon a call to deliverChangeRecords (truthy)
984
985
  // `returns` {Object|*} `this` or calls deliverChangeRecords
985
986
  setPath: function setPath(path, value, hold) {
986
- var curr = this._data_, obj = {name: path, object: this._data_},
987
+ var curr = this.data, obj = {name: path, object: this.data},
987
988
  p = path.split('.'), key;
988
989
  for (key; p.length && (key = p.shift());) {
989
990
  if(!p.length) {
@@ -999,7 +1000,7 @@ sudo.ext.observable = {
999
1000
  curr = curr[key] = {};
1000
1001
  }
1001
1002
  }
1002
- this._changeRecords_.push(obj);
1003
+ this.changeRecords.push(obj);
1003
1004
  // call all observers or not
1004
1005
  if(hold) return this;
1005
1006
  return this.deliverChangeRecords();
@@ -1024,7 +1025,7 @@ sudo.ext.observable = {
1024
1025
  // `param` {Function} the function passed in to `observe`
1025
1026
  // `returns` {Object} `this`
1026
1027
  unobserve: function unobserve(fn) {
1027
- var cb = this._callbacks_, i = cb.indexOf(fn);
1028
+ var cb = this.callbacks, i = cb.indexOf(fn);
1028
1029
  if(i !== -1) cb.splice(i, 1);
1029
1030
  return this;
1030
1031
  },
@@ -1048,9 +1049,9 @@ sudo.ext.observable = {
1048
1049
  //
1049
1050
  // `returns` {Object|*} `this` or calls deliverChangeRecords
1050
1051
  unset: function unset(key, hold) {
1051
- var obj = {name: key, object: this._data_, type: 'deleted'},
1052
- val = !!this._data_[key];
1053
- delete this._data_[key];
1052
+ var obj = {name: key, object: this.data, type: 'deleted'},
1053
+ val = !!this.data[key];
1054
+ delete this.data[key];
1054
1055
  // call the observers if there was a val to delete
1055
1056
  return this._unset_(obj, val, hold);
1056
1057
  },
@@ -1060,7 +1061,7 @@ sudo.ext.observable = {
1060
1061
  // `private`
1061
1062
  _unset_: function _unset_(o, v, h) {
1062
1063
  if(v) {
1063
- this._changeRecords_.push(o);
1064
+ this.changeRecords.push(o);
1064
1065
  if(h) return this;
1065
1066
  return this.deliverChangeRecords();
1066
1067
  }
@@ -1075,8 +1076,8 @@ sudo.ext.observable = {
1075
1076
  //
1076
1077
  // `returns` {Object|*} `this` or calls deliverChangeRecords
1077
1078
  unsetPath: function unsetPath(path, hold) {
1078
- var obj = {name: path, object: this._data_, type: 'deleted'},
1079
- curr = this._data_, p = path.split('.'),
1079
+ var obj = {name: path, object: this.data, type: 'deleted'},
1080
+ curr = this.data, p = path.split('.'),
1080
1081
  key, val;
1081
1082
  for (key; p.length && (key = p.shift());) {
1082
1083
  if(!p.length) {
@@ -1233,7 +1234,7 @@ sudo.ext.bindable = {
1233
1234
  //
1234
1235
  // `returns` {Object} `this`
1235
1236
  setBinding: function setBinding() {
1236
- return this._setBinding_(this._data_.binding);
1237
+ return this._setBinding_(this.model.data.binding);
1237
1238
  },
1238
1239
  // ###_setBinding_
1239
1240
  // Given a single explicit binding, create it. Called from
@@ -1255,10 +1256,10 @@ sudo.ext.bindable = {
1255
1256
  //
1256
1257
  // `returns` {Object} `this`
1257
1258
  setBindings: function setBindings() {
1258
- var b, i;
1259
+ var d = this.model.data, b, i;
1259
1260
  // handle the single binding use case
1260
- if((b = this._data_.binding)) return this._setBinding_(b);
1261
- if(!(b = this._data_.bindings)) return this;
1261
+ if((b = d.binding)) return this._setBinding_(b);
1262
+ if(!(b = d.bindings)) return this;
1262
1263
  for(i = 0; i < b.length; i++) {
1263
1264
  this._setBinding_(b[i]);
1264
1265
  }
@@ -1300,7 +1301,7 @@ sudo.ext.listener = {
1300
1301
  // `returns` {Object} `this`
1301
1302
  bindEvents: function bindEvents() {
1302
1303
  var e;
1303
- if((e = this._data_.event || this._data_.events)) this._handleEvents_(e, 1);
1304
+ if((e = this.model.data.event || this.model.data.events)) this._handleEvents_(e, 1);
1304
1305
  return this;
1305
1306
  },
1306
1307
  // Use the jQuery `on` or 'off' method, optionally delegating to a selector if present
@@ -1333,12 +1334,90 @@ sudo.ext.listener = {
1333
1334
  // `returns` {Object} `this`
1334
1335
  unbindEvents: function unbindEvents() {
1335
1336
  var e;
1336
- if((e = this._data_.event || this._data_.events)) this._handleEvents_(e);
1337
+ if((e = this.model.data.event || this.model.data.events)) this._handleEvents_(e);
1337
1338
  return this;
1338
1339
  }
1339
1340
  };
1341
+ //##Change Delegate
1342
+
1343
+ // Delegates, if present, can override or extend the behavior
1344
+ // of objects. The change delegate is specifically designed to
1345
+ // filter change records from an Observable instance and only forward
1346
+ // the ones matching a given `filters` criteria (key or path).
1347
+ // The forwarded messages will be sent to the specified method
1348
+ // on the delegates `delegator` (bound to the _delegator_ scope)
1349
+ //
1350
+ // `param` {Object} data
1351
+ sudo.delegates.Change = function(data) {
1352
+ this.construct(data);
1353
+ };
1354
+ // Delegates inherit from Model
1355
+ sudo.delegates.Change.prototype = Object.create(sudo.Model.prototype);
1356
+ // Change records are delivered here and filtered, calling any matching
1357
+ // methods specified in `this.get('filters').
1358
+ //
1359
+ // `returns` {Object} a call to the specified _delegator_ method, passing
1360
+ // a hash containing:
1361
+ // 1. the `type` of Change
1362
+ // 2. the value located at the key/path
1363
+ // 3. the `oldValue` of the key if present
1364
+ sudo.delegates.Change.prototype.filter = function(change) {
1365
+ var filters = this.data.filters, name = change.name, obj = {};
1366
+ // does my delegator care about this?
1367
+ if(name in filters && filters.hasOwnProperty(name)) {
1368
+ // assemble the object to return to the method
1369
+ obj.type = change.type;
1370
+ obj.value = name.indexOf('.') === -1 ? change.object[change.name] :
1371
+ this.getPath(name, change.object);
1372
+ obj.oldValue = change.oldValue;
1373
+ return this.delegator[filters[name]].call(this.delegator, obj);
1374
+ }
1375
+ };
1376
+ // `private`
1377
+ sudo.delegates.Change.prototype.role = 'change';
1378
+ //##Data Delegate
1379
+
1380
+ // Delegates, if present, can extend the behavior
1381
+ // of objects, lessening the need for subclassing.
1382
+ // The data delegate is specifically designed to
1383
+ // filter through an object, looking for specified keys or paths
1384
+ // and returning values for those if found
1385
+ //
1386
+ // `param` {Object} data
1387
+ // `returns` {*} the value found at the specified key/path if found
1388
+ sudo.delegates.Data = function(data) {
1389
+ this.construct(data);
1390
+ };
1391
+ // inherits from Model
1392
+ sudo.delegates.Data.prototype = Object.create(sudo.Model.prototype);
1393
+ // ###filter
1394
+ // iterates over a given object literal and returns a value (if present)
1395
+ // located at a given key or path
1396
+ //
1397
+ // `param` {Object} `obj`
1398
+ sudo.delegates.Data.prototype.filter = function(obj) {
1399
+ var filters = this.data.filters, key, o, k;
1400
+ for(key in filters) {
1401
+ if(filters.hasOwnProperty(key)) {
1402
+ // keys and paths need different handling
1403
+ if(key.indexOf('.') === -1) {
1404
+ if(key in obj) this.delegator[filters[key]].call(
1405
+ this.delegator, obj[key]);
1406
+ } else {
1407
+ // the chars after the last refinement are the key we need to check for
1408
+ k = key.slice(key.lastIndexOf('.') + 1);
1409
+ // and the ones prior are the object
1410
+ o = sudo.getPath(key.slice(0, key.lastIndexOf('.')), obj);
1411
+ if(o && k in o) this.delegator[filters[key]].call(
1412
+ this.delegator, o[k]);
1413
+ }
1414
+ }
1415
+ }
1416
+ };
1417
+ // `private`
1418
+ sudo.delegates.Data.prototype.role = 'data';
1340
1419
 
1341
- sudo.version = "0.8.0";
1420
+ sudo.version = "0.9.0";
1342
1421
  window.sudo = sudo;
1343
1422
  if(typeof window._ === "undefined") window._ = sudo;
1344
1423
  }).call(this, this);