sudojs-rails 0.1.7
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +86 -0
- data/Rakefile +28 -0
- data/lib/generators/sudojs/class/USAGE +47 -0
- data/lib/generators/sudojs/class/class_generator.rb +107 -0
- data/lib/generators/sudojs/class/templates/class.coffee.erb +13 -0
- data/lib/generators/sudojs/class/templates/class.js.erb +14 -0
- data/lib/generators/sudojs/class/templates/manifest.js.erb +5 -0
- data/lib/generators/sudojs/helpers.rb +44 -0
- data/lib/generators/sudojs/install/USAGE +27 -0
- data/lib/generators/sudojs/install/install_generator.rb +34 -0
- data/lib/generators/sudojs/install/templates/app_manifest.js.erb +4 -0
- data/lib/generators/sudojs/install/templates/model.coffee.erb +9 -0
- data/lib/generators/sudojs/install/templates/model.js.erb +9 -0
- data/lib/generators/sudojs/install/templates/namespace.coffee.erb +8 -0
- data/lib/generators/sudojs/install/templates/namespace.js.erb +8 -0
- data/lib/generators/sudojs/install/templates/sudo_js.erb.yml +11 -0
- data/lib/sudojs/engine.rb +4 -0
- data/lib/sudojs/version.rb +3 -0
- data/lib/sudojs-rails.rb +2 -0
- data/sudojs-rails.gemspec +22 -0
- data/vendor/assets/javascripts/README.md +3 -0
- data/vendor/assets/javascripts/sudo-x.js +1344 -0
- data/vendor/assets/javascripts/sudo.js +778 -0
- metadata +94 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
// Global model is a sudo.Observable
|
2
|
+
<%= name %>.Model = function(data) {
|
3
|
+
this.construct(data);
|
4
|
+
$.extend(this, _.ext.observable);
|
5
|
+
};
|
6
|
+
// `private`
|
7
|
+
<%= name %>.Model.prototype = Object.create(sudo.Base.prototype);
|
8
|
+
// attach the model to the <%= name %> namespace
|
9
|
+
<%= name %>.model = new <%= name %>.Model();
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Define the top-level app namespace as defined in the sudo_js yaml file.
|
2
|
+
|
3
|
+
# The `descriptors` array is a global necessity that solves the issue of
|
4
|
+
# child objects being parsed on a _partial before their parent ViewControllers
|
5
|
+
# (who are declared on a containing view file)
|
6
|
+
window.<%= name %> =
|
7
|
+
descriptors: []
|
8
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
// Define the top-level app namespace as defined in the sudo_js yaml file.
|
2
|
+
|
3
|
+
// The `descriptors` array is a global necessity that solves the issue of
|
4
|
+
// child objects being parsed on a _partial before their parent ViewControllers
|
5
|
+
// (who are declared on a containing view file)
|
6
|
+
window.<%= name %> = {
|
7
|
+
descriptors: []
|
8
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# What is the applications top level namespace?
|
2
|
+
js_namespace: <%= name %>
|
3
|
+
|
4
|
+
# sudo.js with templating or without?
|
5
|
+
which_sudo: <%= which_sudo_arg %>
|
6
|
+
|
7
|
+
# erb or haml or ...?
|
8
|
+
which_markup: <%= which_markup_arg %>
|
9
|
+
|
10
|
+
# coffee?
|
11
|
+
use_coffee: <%= use_coffee_arg %>
|
data/lib/sudojs-rails.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'sudojs/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "sudojs-rails"
|
9
|
+
gem.version = Sudojs::VERSION
|
10
|
+
gem.authors = ["robrobbins"]
|
11
|
+
gem.email = ["rob.robb.ns@gmail.com"]
|
12
|
+
gem.description = "Install the latest versions of sudo.js and provide generators for fast and easy use."
|
13
|
+
gem.summary = "Install and use sudo.js quickly and easily with rails 3.2+."
|
14
|
+
gem.homepage = "https://github.com/robrobbins/sudojs-rails"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency "rspec", "~> 2.6"
|
22
|
+
end
|
@@ -0,0 +1,1344 @@
|
|
1
|
+
(function(window) {
|
2
|
+
// #Sudo Namespace
|
3
|
+
var sudo = {
|
4
|
+
// Namespace for `Delegate` Class Objects used to delegate functionality
|
5
|
+
// from a `delegator`
|
6
|
+
//
|
7
|
+
// `namespace`
|
8
|
+
delegates: {},
|
9
|
+
// The sudo.ext namespace holds the `extensions` which are stand alone `modules` that
|
10
|
+
// can be `implemented` (mixed-in) to sudo Class Objects
|
11
|
+
//
|
12
|
+
// `namespace`
|
13
|
+
ext: {},
|
14
|
+
// ###_inherit_
|
15
|
+
// Inherit the prototype from a parent to a child.
|
16
|
+
// Set the childs constructor for subclasses of child.
|
17
|
+
// A _private_ method as subclasses of the library base classes will not
|
18
|
+
// want to use this function in *most* use-cases. Why? User Sudo Class Objects
|
19
|
+
// possess their own constructors and any call back to a `superclass` constructor
|
20
|
+
// will generally be looking for the library Object's constructor.
|
21
|
+
//
|
22
|
+
// `param` {function} `parent`
|
23
|
+
// `param` {function} `child`
|
24
|
+
//
|
25
|
+
// `private`
|
26
|
+
_inherit_: function _inherit_(parent, child) {
|
27
|
+
child.prototype = Object.create(parent.prototype);
|
28
|
+
child.prototype.constructor = child;
|
29
|
+
},
|
30
|
+
// ###makeMeASandwich
|
31
|
+
// Notice there is no need to extrinsically instruct *how* to
|
32
|
+
// make the sandwich, just the elegant single command.
|
33
|
+
//
|
34
|
+
// `returns` {string}
|
35
|
+
makeMeASandwich: function makeMeASandwich() {return 'Okay.';},
|
36
|
+
// ###namespace
|
37
|
+
// Convenience method for assuring a Namespace is defined. Uses
|
38
|
+
// the optional `obj` arg with the Base objects `setPath` method.
|
39
|
+
//
|
40
|
+
// `param` {string} `path`. The path that leads to a blank Object.
|
41
|
+
namespace: function namespace(path) {
|
42
|
+
if (!sudo.Base.prototype.getPath.call(this, path, window)) {
|
43
|
+
sudo.Base.prototype.setPath.call(this, path, {}, window);
|
44
|
+
}
|
45
|
+
},
|
46
|
+
// ###premier
|
47
|
+
// The premier object takes precedence over all others so define it at the topmost level.
|
48
|
+
//
|
49
|
+
// `type` {Object}
|
50
|
+
premier: null,
|
51
|
+
// ####_uid_
|
52
|
+
// Some sudo Objects use a unique integer as a `tag` for identification.
|
53
|
+
// (Views for example). This ensures they are indeed unique.
|
54
|
+
//
|
55
|
+
// `private`
|
56
|
+
_uid_: 0,
|
57
|
+
// ####_unique_
|
58
|
+
// An integer used as 'tags' by some sudo Objects as well
|
59
|
+
// as a unique string for views when needed
|
60
|
+
//
|
61
|
+
// `param` {string} prefix. Optional string identifier
|
62
|
+
//
|
63
|
+
// `private`
|
64
|
+
_unique_: function _unique_(prefix) {
|
65
|
+
return prefix ? prefix + this._uid_++ : this._uid_++;
|
66
|
+
}
|
67
|
+
};
|
68
|
+
// ##Base Class Object
|
69
|
+
//
|
70
|
+
// All sudo.js objects inherit base
|
71
|
+
//
|
72
|
+
// `param` {Object} data. An optional data object for this instance.
|
73
|
+
//
|
74
|
+
// `constructor`
|
75
|
+
sudo.Base = function(data) {
|
76
|
+
this._data_ = data || {};
|
77
|
+
// can delegate
|
78
|
+
this._delegates_ = [];
|
79
|
+
// may implement `observable`
|
80
|
+
this._callbacks_ = [];
|
81
|
+
this._changeRecords_ = [];
|
82
|
+
// a beautiful and unique snowflake
|
83
|
+
this._uid_ = sudo._unique_();
|
84
|
+
};
|
85
|
+
// ###addDelegate
|
86
|
+
// Push an instance of a Class Object into this object's `_delegates_` list.
|
87
|
+
//
|
88
|
+
// `param` {Object} an instance of a sudo.delegates Class Object
|
89
|
+
// `returns` {Object} `this`
|
90
|
+
sudo.Base.prototype.addDelegate = function addDelegate(del) {
|
91
|
+
del._delegator_ = this;
|
92
|
+
this._delegates_.push(del);
|
93
|
+
return this;
|
94
|
+
};
|
95
|
+
// ###base
|
96
|
+
// Lookup the function matching the name passed in and call it with
|
97
|
+
// any passed in argumets scoped to the calling object.
|
98
|
+
// This method will avoid the recursive-loop problem by making sure
|
99
|
+
// that the first match is not the function that called `base`.
|
100
|
+
//
|
101
|
+
// `params` {*} any other number of arguments to be passed to the looked up method
|
102
|
+
sudo.Base.prototype.base = function base() {
|
103
|
+
var args = Array.prototype.slice.call(arguments),
|
104
|
+
name = args.shift(),
|
105
|
+
found = false,
|
106
|
+
obj = this,
|
107
|
+
curr;
|
108
|
+
// find method on the prototype, excluding the caller
|
109
|
+
while(!found) {
|
110
|
+
curr = Object.getPrototypeOf(obj);
|
111
|
+
if(curr[name] && curr[name] !== this[name]) found = true;
|
112
|
+
// keep digging
|
113
|
+
else obj = curr;
|
114
|
+
}
|
115
|
+
return curr[name].apply(this, args);
|
116
|
+
};
|
117
|
+
// ###construct
|
118
|
+
// A convenience method that alleviates the need to place:
|
119
|
+
// `Object.getPrototypeOf(this).consturctor.apply(this, arguments)`
|
120
|
+
// in every constructor
|
121
|
+
sudo.Base.prototype.construct = function construct() {
|
122
|
+
Object.getPrototypeOf(this).constructor.apply(this, arguments || []);
|
123
|
+
};
|
124
|
+
// ###delegate
|
125
|
+
// From this object's list of delegates find the object whose `_role_` matches
|
126
|
+
// the passed `name` and:
|
127
|
+
// 1. if `meth` is falsy return the delegate.
|
128
|
+
// 2 if `meth` is truthy bind its method (to the delegate) and return the method
|
129
|
+
//
|
130
|
+
// `param` {String} `role` The _role_ property to match in this object's delegates list
|
131
|
+
// `param` {String} `meth` Optional method to bind to the action this delegate is being used for
|
132
|
+
// `returns`
|
133
|
+
sudo.Base.prototype.delegate = function delegate(role, meth) {
|
134
|
+
var del = this._delegates_, i;
|
135
|
+
for(i = 0; i < del.length; i++) {
|
136
|
+
if(del[i]._role_ === role) {
|
137
|
+
if(!meth) return del[i];
|
138
|
+
return del[i][meth].bind(del[i]);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
};
|
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
|
+
// ###getDelegate
|
151
|
+
// Fetch a delegate whose _role_ property matches the passed in argument.
|
152
|
+
// Uses the `delegate` method in its 'single argument' form, included for
|
153
|
+
// API consistency
|
154
|
+
//
|
155
|
+
// `param` {String} `role`
|
156
|
+
// 'returns' {Object|undefined}
|
157
|
+
sudo.Base.prototype.getDelegate = function getDelegate(role) {
|
158
|
+
return this.delegate(role);
|
159
|
+
};
|
160
|
+
// ###getPath
|
161
|
+
// Extract a value located at `path` relative to this objects data store
|
162
|
+
//
|
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] || {};
|
175
|
+
}
|
176
|
+
}
|
177
|
+
return curr;
|
178
|
+
};
|
179
|
+
// ###gets
|
180
|
+
// Assembles and returns an object of key:value pairs for each key
|
181
|
+
// contained in the passed in Array.
|
182
|
+
//
|
183
|
+
// `param` {array} `ary`. An array of keys.
|
184
|
+
// `returns` {object}
|
185
|
+
sudo.Base.prototype.gets = function gets(ary) {
|
186
|
+
var i, obj = {};
|
187
|
+
for (i = 0; i < ary.length; i++) {
|
188
|
+
obj[ary[i]] = ary[i].indexOf('.') === -1 ? this._data_[ary[i]] :
|
189
|
+
this.getPath(ary[i]);
|
190
|
+
}
|
191
|
+
return obj;
|
192
|
+
};
|
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
|
+
// `private`
|
212
|
+
sudo.Base.prototype._role_ = 'base';
|
213
|
+
// ###set
|
214
|
+
// Set a key:value pair in `this` object's data store.
|
215
|
+
//
|
216
|
+
// `param` {String} `key`. The name of the key.
|
217
|
+
// `param` {*} `value`. The value associated with the key.
|
218
|
+
// `returns` {Object} `this`
|
219
|
+
sudo.Base.prototype.set = function set(key, value) {
|
220
|
+
// _NOTE: intentional possibilty of setting a falsy value_
|
221
|
+
this._data_[key] = value;
|
222
|
+
return this;
|
223
|
+
};
|
224
|
+
// ###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
|
+
//
|
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;
|
245
|
+
};
|
246
|
+
// ###sets
|
247
|
+
// Invokes `set()` or `setPath()` for each key value pair in `obj`.
|
248
|
+
// Any listeners for those keys or paths will be called.
|
249
|
+
//
|
250
|
+
// `param` {Object} `obj`. The keys and values to set.
|
251
|
+
// `returns` {Object} `this`
|
252
|
+
sudo.Base.prototype.sets = function sets(obj) {
|
253
|
+
var i, k = Object.keys(obj);
|
254
|
+
for(i = 0; i < k.length; i++) {
|
255
|
+
k[i].indexOf('.') === -1 ? this.set(k[i], obj[k[i]]) :
|
256
|
+
this.setPath(k[i], obj[k[i]]);
|
257
|
+
}
|
258
|
+
return this;
|
259
|
+
};
|
260
|
+
// ###unset
|
261
|
+
// Remove a key:value pair from this object's data store
|
262
|
+
//
|
263
|
+
// `param` {String} key
|
264
|
+
// `returns` {Object} `this`
|
265
|
+
sudo.Base.prototype.unset = function unset(key) {
|
266
|
+
delete this._data_[key];
|
267
|
+
return this;
|
268
|
+
};
|
269
|
+
// ###unsetPath
|
270
|
+
// Remove a key:value pair from this object's data store
|
271
|
+
// located at <path>
|
272
|
+
//
|
273
|
+
// `param` {String} path
|
274
|
+
// `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;
|
287
|
+
};
|
288
|
+
// ###unsets
|
289
|
+
// Deletes a number of keys or paths from this object's data store
|
290
|
+
//
|
291
|
+
// `param` {array} `ary`. An array of keys or paths.
|
292
|
+
// `returns` {Objaect} `this`
|
293
|
+
sudo.Base.prototype.unsets = function unsets(ary) {
|
294
|
+
var i;
|
295
|
+
for(i = 0; i < ary.length; i++) {
|
296
|
+
ary[i].indexOf('.') === -1 ? this.unset(ary[i]) :
|
297
|
+
this.unsetPath(ary[i]);
|
298
|
+
}
|
299
|
+
return this;
|
300
|
+
};
|
301
|
+
// ##Container Class Object
|
302
|
+
//
|
303
|
+
// A container is any object that can both contain other objects and
|
304
|
+
// itself be contained
|
305
|
+
//
|
306
|
+
// `constructor`
|
307
|
+
sudo.Container = function(data) {
|
308
|
+
sudo.Base.call(this, data);
|
309
|
+
this._children_ = [];
|
310
|
+
this._childNames_ = {};
|
311
|
+
};
|
312
|
+
// `private`
|
313
|
+
sudo._inherit_(sudo.Base, sudo.Container);
|
314
|
+
// ###addChild
|
315
|
+
// Adds a View to this container's list of children.
|
316
|
+
// Also adds an 'index' property and an entry in the childNames hash.
|
317
|
+
// If `addedToParent` if found on the child, call it, sending `this` as an argument.
|
318
|
+
//
|
319
|
+
// `param` {Object} `child`. View (or View subclass) instance.
|
320
|
+
// `param` {String} `name`. An optional name for the child that will go in the childNames hash.
|
321
|
+
// `returns` {Object} `this`
|
322
|
+
sudo.Container.prototype.addChild = function addChild(child, name) {
|
323
|
+
var c = this._children_;
|
324
|
+
child._parent_ = this;
|
325
|
+
child._index_ = c.length;
|
326
|
+
if(name) {
|
327
|
+
child._name_ = name;
|
328
|
+
this._childNames_[name] = child._index_;
|
329
|
+
}
|
330
|
+
c.push(child);
|
331
|
+
if('addedToParent' in child) child.addedToParent(this);
|
332
|
+
return this;
|
333
|
+
};
|
334
|
+
// ###bubble
|
335
|
+
// By default, `bubble` returns the current view's parent (if it has one)
|
336
|
+
//
|
337
|
+
// `returns` {Object|undefined}
|
338
|
+
sudo.Container.prototype.bubble = function bubble() {return this._parent_;};
|
339
|
+
// ###getChild
|
340
|
+
// If a child was added with a name, via `addChild`,
|
341
|
+
// that object can be fetched by name. This prevents us from having to reference a
|
342
|
+
// containers children by index. That is possible however, though not preferred.
|
343
|
+
//
|
344
|
+
// `param` {String|Number} `id`. The string `name` or numeric `index` of the child to fetch.
|
345
|
+
// `returns` {Object|undefined} The found child
|
346
|
+
sudo.Container.prototype.getChild = function getChild(id) {
|
347
|
+
return typeof id === 'string' ? this._children_[this._childNames_[id]] :
|
348
|
+
this._children_[id];
|
349
|
+
};
|
350
|
+
// ###_indexChildren_
|
351
|
+
// Method is called with the `index` property of a subview that is being removed.
|
352
|
+
// Beginning at <i> decrement subview indices.
|
353
|
+
// `param` {Number} `i`
|
354
|
+
// `private`
|
355
|
+
sudo.Container.prototype._indexChildren_ = function _indexChildren_(i) {
|
356
|
+
var c = this._children_, obj = this._childNames_, len;
|
357
|
+
for (len = c.length; i < len; i++) {
|
358
|
+
c[i]._index_--;
|
359
|
+
// adjust any entries in childNames
|
360
|
+
if(c[i]._name_ in obj) obj[c[i]._name_] = c[i]._index_;
|
361
|
+
}
|
362
|
+
};
|
363
|
+
// ###removeChild
|
364
|
+
// Find the intended child from my list of children and remove it, removing the name reference and re-indexing
|
365
|
+
// remaining children. This method does not remove the child's DOM.
|
366
|
+
// Override this method, doing whatever you want to the child's DOM, then call `base('removeChild')` to do so.
|
367
|
+
//
|
368
|
+
// `param` {String|Number|Object} `arg`. Children will always have an `index` number, and optionally a `name`.
|
369
|
+
// If passed a string `name` is assumed, so be sure to pass an actual number if expecting to use index.
|
370
|
+
// An object will be assumed to be an actual sudo Class Object.
|
371
|
+
// `returns` {Object} `this`
|
372
|
+
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_;
|
388
|
+
// remove from the children Array
|
389
|
+
this._children_.splice(i, 1);
|
390
|
+
// remove from the named child hash if present
|
391
|
+
delete this._childNames_[child._name_];
|
392
|
+
// child is now an `orphan`
|
393
|
+
delete child._parent_;
|
394
|
+
this._indexChildren_(i);
|
395
|
+
};
|
396
|
+
// ###removeFromParent
|
397
|
+
// Remove this object from its parents list of children.
|
398
|
+
// Does not alter the dom - do that yourself by overriding this method
|
399
|
+
// or chaining method calls
|
400
|
+
sudo.Container.prototype.removeFromParent = function removeFromParent() {
|
401
|
+
// will error without a parent, but that would be your fault...
|
402
|
+
this._parent_._removeChild_(this);
|
403
|
+
return this;
|
404
|
+
};
|
405
|
+
sudo.Container.prototype._role_ = 'container';
|
406
|
+
// ###send
|
407
|
+
// The call to the specific method on a (un)specified target happens here.
|
408
|
+
// If this Object is part of a `sudo.ext.container` maintained hierarchy
|
409
|
+
// the 'target' may be left out, causing the `bubble()` method to be called.
|
410
|
+
// What this does is allow children of a `sudo.ext.container` to simply pass
|
411
|
+
// events upward, delegating the responsibility of deciding what to do to the parent.
|
412
|
+
//
|
413
|
+
// `param` {*} Any number of arguments is supported, but the first is the only one searched for info.
|
414
|
+
// A sendMethod will be located by:
|
415
|
+
// 1. using the first argument if it is a string
|
416
|
+
// 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
|
418
|
+
// Any other args will be passed to the sendMethod after `this`
|
419
|
+
// `returns` {Object} `this`
|
420
|
+
sudo.Container.prototype.send = function send(/*args*/) {
|
421
|
+
var args = Array.prototype.slice.call(arguments),
|
422
|
+
meth, targ, fn;
|
423
|
+
// normalize the input, common use cases first
|
424
|
+
if('sendMethod' in this._data_) meth = this._data_.sendMethod;
|
425
|
+
else if(typeof args[0] === 'string') meth = args.shift();
|
426
|
+
// less common but viable options
|
427
|
+
if(!meth) {
|
428
|
+
// passed as a jquery custom data attr bound in events
|
429
|
+
meth = 'data' in args[0] ? args[0].data.sendMethod :
|
430
|
+
// passed in a hash from something or not passed at all
|
431
|
+
args[0].sendMethod || void 0;
|
432
|
+
}
|
433
|
+
// target is either specified or my parent
|
434
|
+
targ = this._data_['sendTarget'] || this._parent_;
|
435
|
+
// obvious chance for errors here, don't be dumb
|
436
|
+
fn = targ[meth];
|
437
|
+
while(!fn && (targ = targ.bubble())) {
|
438
|
+
fn = targ[meth];
|
439
|
+
}
|
440
|
+
// sendMethods expect a signature (sender, ...)
|
441
|
+
if(fn) {
|
442
|
+
args.unshift(this);
|
443
|
+
fn.apply(targ, args);
|
444
|
+
}
|
445
|
+
return this;
|
446
|
+
};
|
447
|
+
// ##View Class Object
|
448
|
+
|
449
|
+
// Create an instance of a sudo.View object. A view is any object
|
450
|
+
// that maintains its own `el`, that being some type of DOM element.
|
451
|
+
// Pass in a string selector or an actual dom node reference to have the object
|
452
|
+
// set that as its `el`. If no `el` is specified one will be created upon instantiation
|
453
|
+
// based on the `tagName` (`div` by default). Specify `className`, `id` (or other attributes if desired)
|
454
|
+
// as an (optional) `attributes` object literal on the `data` arg.
|
455
|
+
//
|
456
|
+
// The view object uses jquery for dom manipulation
|
457
|
+
// and event delegation etc... A jquerified `this` reference is located
|
458
|
+
// at `this.$el` and `this.$` scopes queries to this objects `el`, i.e it's
|
459
|
+
// a shortcut for `this.$el.find(selector)`
|
460
|
+
//
|
461
|
+
// `param` {string|element|jQuery} `el`. Otional el for the View instance.
|
462
|
+
// `param` {Object} `data`. Optional data object.
|
463
|
+
//
|
464
|
+
// `constructor`
|
465
|
+
sudo.View = function(el, data) {
|
466
|
+
sudo.Container.call(this, data);
|
467
|
+
this.setEl(el);
|
468
|
+
if(this._role_ === 'view') this.init();
|
469
|
+
};
|
470
|
+
// View inherits from Container
|
471
|
+
// `private`
|
472
|
+
sudo._inherit_(sudo.Container, sudo.View);
|
473
|
+
// ###becomePremier
|
474
|
+
// Premier functionality provides hooks for behavioral differentiation
|
475
|
+
// among elements or class objects.
|
476
|
+
//
|
477
|
+
// `returns` {Object} `this`
|
478
|
+
sudo.View.prototype.becomePremier = function becomePremier() {
|
479
|
+
var p, f = function() {
|
480
|
+
this._isPremier_ = true;
|
481
|
+
sudo.premier = this;
|
482
|
+
}.bind(this);
|
483
|
+
// is there an existing premier that isn't me?
|
484
|
+
if((p = sudo.premier) && p._uid_ !== this._uid_) {
|
485
|
+
// ask it to resign and call the cb
|
486
|
+
p.resignPremier(f);
|
487
|
+
} else f(); // no existing premier
|
488
|
+
return this;
|
489
|
+
};
|
490
|
+
// ###init
|
491
|
+
// A 'contruction-time' hook to call for further initialization needs in
|
492
|
+
// View objects (and their subclasses). A noop by default child classes should override.
|
493
|
+
sudo.View.prototype.init = $.noop;
|
494
|
+
// the el needs to be normalized before use
|
495
|
+
// `private`
|
496
|
+
sudo.View.prototype._normalizedEl_ = function _normalizedEl_(el) {
|
497
|
+
if(typeof el === 'string') {
|
498
|
+
return $(el);
|
499
|
+
} else {
|
500
|
+
// Passed an already `jquerified` Element?
|
501
|
+
// It will have a length of 1 if so.
|
502
|
+
return el.length ? el : $(el);
|
503
|
+
}
|
504
|
+
};
|
505
|
+
// ### resignPremier
|
506
|
+
// Resign premier status
|
507
|
+
//
|
508
|
+
// `param` {Function} `cb`. An optional callback to execute
|
509
|
+
// after resigning premier status.
|
510
|
+
// `returns` {Object} `this`
|
511
|
+
sudo.View.prototype.resignPremier = function resignPremier(cb) {
|
512
|
+
var p;
|
513
|
+
this._isPremier_ = false;
|
514
|
+
// only remove the global premier if it is me
|
515
|
+
if((p = sudo.premier) && p._uid_ === this._uid_) {
|
516
|
+
sudo.premier = null;
|
517
|
+
}
|
518
|
+
// fire the cb if passed
|
519
|
+
if(cb) cb();
|
520
|
+
return this;
|
521
|
+
};
|
522
|
+
// `private`
|
523
|
+
sudo.View.prototype._role_ = 'view';
|
524
|
+
// ###setEl
|
525
|
+
// A view must have an element, set that here.
|
526
|
+
// Stores a jquerified object as `this.$el` the raw
|
527
|
+
// node is always then available as `this.$el[0]`.
|
528
|
+
//
|
529
|
+
// `param` {string=|element} `el`
|
530
|
+
// `returns` {Object} `this`
|
531
|
+
sudo.View.prototype.setEl = function setEl(el) {
|
532
|
+
var a, t;
|
533
|
+
if(!el) {
|
534
|
+
// normalize any relevant data
|
535
|
+
t = this._data_['tagName'] || 'div';
|
536
|
+
this.$el = $(document.createElement(t));
|
537
|
+
if((a = this._data_['attributes'])) this.$el.attr(a);
|
538
|
+
} else {
|
539
|
+
this.$el = this._normalizedEl_(el);
|
540
|
+
}
|
541
|
+
return this;
|
542
|
+
};
|
543
|
+
// ###this.$
|
544
|
+
// Return a single Element matching `sel` scoped to this View's el.
|
545
|
+
// This is an alias to `this.$el.find(sel)`.
|
546
|
+
//
|
547
|
+
// `param` {string} `sel`. A jQuery compatible selector
|
548
|
+
// `returns` {jQuery} A 'jquerified' result matching the selector
|
549
|
+
sudo.View.prototype.$ = function(sel) {
|
550
|
+
return this.$el.find(sel);
|
551
|
+
};
|
552
|
+
// ##ViewController Class Object
|
553
|
+
|
554
|
+
// ViewControllers were designed for Rails projects for 2 specific use-cases:
|
555
|
+
//
|
556
|
+
// 1. ViewControllers can instantiate any `descriptors` found in their model
|
557
|
+
// when constructing, adding them as `child` objects. Why? Sometimes a 'partial' will
|
558
|
+
// need to define a javascript object that should, by design, be the child of a parent View
|
559
|
+
// that is itself defined on the Rails view that owns the 'partial'. Since any JS introduced
|
560
|
+
// by a partial will be parsed before the JS on its parent Rails View this usually isn't possible.
|
561
|
+
// Our solution? Pushing `Descriptor objects` (see docs) into an array (somewhere in your namespace) from a
|
562
|
+
// 'partial' and then passing a reference to that array into the ViewController as 'descriptors'
|
563
|
+
// in its optional data argument when instantiated. The ViewController will then iterate over those
|
564
|
+
// and instantiate them, adding them as children as it goes (also setting up any stated observers)
|
565
|
+
//
|
566
|
+
// 2. ViewControllers also abstract away connecting UJS style events by allowing the developer to
|
567
|
+
// pass in the name(s) of any desired UJS events to observe: `ujsEvent: ajax:success` for example,
|
568
|
+
// and expect that a method named onAjaxSuccess, if present on the ViewController, will be called
|
569
|
+
// with the arguments returned by the UJS plugin*
|
570
|
+
//
|
571
|
+
// `param` {string|element} `el`. Otional el for the View instance.
|
572
|
+
// `param` {object} `data`. Optional data object.
|
573
|
+
//
|
574
|
+
// `see` sudo.View.
|
575
|
+
//
|
576
|
+
// `constructor`
|
577
|
+
sudo.ViewController = function(el, data) {
|
578
|
+
sudo.View.call(this, el, data);
|
579
|
+
// map the names of events to methods we expect to proxy to
|
580
|
+
this.eventMap = {
|
581
|
+
'ajax:before': 'onAjaxBefore',
|
582
|
+
'ajax:beforeSend': 'onAjaxBeforeSend',
|
583
|
+
'ajax:success': 'onAjaxSuccess',
|
584
|
+
'ajax:error': 'onAjaxError',
|
585
|
+
'ajax:complete': 'onAjaxComplete',
|
586
|
+
'ajax:aborted:required': 'onAjaxAbortedRequired',
|
587
|
+
'ajax:aborted:file': 'onAjaxAbortedFile'
|
588
|
+
};
|
589
|
+
// 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();
|
594
|
+
};
|
595
|
+
// ViewController inherits from View.
|
596
|
+
// `private`
|
597
|
+
sudo._inherit_(sudo.View, sudo.ViewController);
|
598
|
+
// ###doMapping
|
599
|
+
//
|
600
|
+
// assign the proxy mapping for events. This can be called at any time
|
601
|
+
// if the listened for events change
|
602
|
+
//
|
603
|
+
// `returns` {Object} `this`
|
604
|
+
sudo.ViewController.prototype.doMapping = function() {
|
605
|
+
// either a single event or an array of them
|
606
|
+
var i,
|
607
|
+
toMap = this._data_['ujsEvent'] || this._data_['ujsEvents'];
|
608
|
+
if(toMap) {
|
609
|
+
if(typeof toMap === 'string') this._mapEvent(toMap);
|
610
|
+
else {
|
611
|
+
for(i = 0; i < toMap.length; i++) {
|
612
|
+
this._mapEvent(toMap[i]);
|
613
|
+
}
|
614
|
+
}
|
615
|
+
}
|
616
|
+
return this;
|
617
|
+
};
|
618
|
+
// ###_handleObserve_
|
619
|
+
// Helper for instantiateChildren
|
620
|
+
// `private`
|
621
|
+
sudo.ViewController.prototype._handleObserve_ = function _handleObserve_(obs, c) {
|
622
|
+
var obj = obs.object ? this._objectForPath_(obs.object) : this;
|
623
|
+
obj.observe(c[obs.cb].bind(c));
|
624
|
+
};
|
625
|
+
// ###instantiateChildren
|
626
|
+
// instantiate the children described in the passed in array or the `descriptors` array
|
627
|
+
// set in this object's data store
|
628
|
+
//
|
629
|
+
// `returns` {object} `this`
|
630
|
+
sudo.ViewController.prototype.instantiateChildren = function instantiateChildren(ary) {
|
631
|
+
var i, j, curr, c, d = ary || this._data_.descriptors;
|
632
|
+
for(i = 0; i < d.length; i++) {
|
633
|
+
curr = d[i];
|
634
|
+
c = new curr.is_a(curr.el, curr.data);
|
635
|
+
this.addChild(c, curr.name);
|
636
|
+
// handle any observe(s)
|
637
|
+
if('observe' in curr) {
|
638
|
+
this._handleObserve_(curr.observe, c);
|
639
|
+
}
|
640
|
+
else if('observes' in curr) {
|
641
|
+
for(j = 0; j < curr.observes.length; j++) {
|
642
|
+
this._handleObserve_(curr.observes[j], c);
|
643
|
+
}
|
644
|
+
}
|
645
|
+
}
|
646
|
+
return this;
|
647
|
+
};
|
648
|
+
// ###_mapEvent
|
649
|
+
// Maps the ajax:event names to methods
|
650
|
+
// `private`
|
651
|
+
sudo.ViewController.prototype._mapEvent = function(name) {
|
652
|
+
// because the signatures vary we need specific methods
|
653
|
+
this.$el.on(name, this[this.eventMap[name]].bind(this));
|
654
|
+
};
|
655
|
+
// ###_objectForPath_
|
656
|
+
// The objects used for callbacks and connections need to be
|
657
|
+
// looked-up via a key-path like address as they likely will not exist
|
658
|
+
// when viewController's are instantiated.
|
659
|
+
// `private`
|
660
|
+
sudo.ViewController.prototype._objectForPath_ = function _objectForPath_(path) {
|
661
|
+
return sudo.Base.prototype.getPath.call(this, path, window);
|
662
|
+
};
|
663
|
+
// Virtual methods to override in your child classes for
|
664
|
+
// any events you chose to listen for
|
665
|
+
sudo.ViewController.prototype.onAjaxAbortedFile = $.noop;
|
666
|
+
sudo.ViewController.prototype.onAjaxAbortedRequired = $.noop;
|
667
|
+
sudo.ViewController.prototype.onAjaxBefore = $.noop;
|
668
|
+
sudo.ViewController.prototype.onAjaxBeforeSend = $.noop;
|
669
|
+
sudo.ViewController.prototype.onAjaxComplete = $.noop;
|
670
|
+
sudo.ViewController.prototype.onAjaxSuccess = $.noop;
|
671
|
+
sudo.ViewController.prototype.onAjaxError = $.noop;
|
672
|
+
// `private`
|
673
|
+
sudo.ViewController.prototype._role_ = 'viewController';
|
674
|
+
// ###Templating
|
675
|
+
|
676
|
+
// Allow the default {{ js code }}, {{= key }}, and {{- escape stuff }}
|
677
|
+
// micro templating delimiters to be overridden if desired
|
678
|
+
//
|
679
|
+
// `type` {Object}
|
680
|
+
sudo.templateSettings = {
|
681
|
+
evaluate: /\{\{([\s\S]+?)\}\}/g,
|
682
|
+
interpolate: /\{\{=([\s\S]+?)\}\}/g,
|
683
|
+
escape: /\{\{-([\s\S]+?)\}\}/g
|
684
|
+
};
|
685
|
+
// Certain characters need to be escaped so that they can be put
|
686
|
+
// into a string literal when templating.
|
687
|
+
//
|
688
|
+
// `type` {Object}
|
689
|
+
sudo.escapes = {};
|
690
|
+
(function(s) {
|
691
|
+
var e = {
|
692
|
+
'\\': '\\',
|
693
|
+
"'": "'",
|
694
|
+
r: '\r',
|
695
|
+
n: '\n',
|
696
|
+
t: '\t',
|
697
|
+
u2028: '\u2028',
|
698
|
+
u2029: '\u2029'
|
699
|
+
};
|
700
|
+
for (var key in e) s.escapes[e[key]] = key;
|
701
|
+
}(sudo));
|
702
|
+
// lookup hash for `escape`
|
703
|
+
//
|
704
|
+
// `type` {Object}
|
705
|
+
sudo.htmlEscapes = {
|
706
|
+
'&': '&',
|
707
|
+
'<': '<',
|
708
|
+
'>': '>',
|
709
|
+
'"': '"',
|
710
|
+
"'": ''',
|
711
|
+
'/': '/'
|
712
|
+
};
|
713
|
+
// Escapes certain characters for templating
|
714
|
+
//
|
715
|
+
// `type` {regexp}
|
716
|
+
sudo.escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
|
717
|
+
// Escape unsafe HTML
|
718
|
+
//
|
719
|
+
// `type` {regexp}
|
720
|
+
sudo.htmlEscaper = /[&<>"'\/]/g;
|
721
|
+
// Unescapes certain characters for templating
|
722
|
+
//
|
723
|
+
// `type` {regexp}
|
724
|
+
sudo.unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
|
725
|
+
// ###escape
|
726
|
+
// Remove unsafe characters from a string
|
727
|
+
//
|
728
|
+
// `param` {String} str
|
729
|
+
sudo.escape = function(str) {
|
730
|
+
return str.replace(sudo.htmlEscaper, function(match) {
|
731
|
+
return sudo.htmlEscapes[match];
|
732
|
+
});
|
733
|
+
};
|
734
|
+
// ###unescape
|
735
|
+
// Within an interpolation, evaluation, or escaping,
|
736
|
+
// remove HTML escaping that had been previously added.
|
737
|
+
//
|
738
|
+
// `param` {string} str
|
739
|
+
sudo.unescape = function unescape(str) {
|
740
|
+
return str.replace(sudo.unescaper, function(match, escape) {
|
741
|
+
return sudo.escapes[escape];
|
742
|
+
});
|
743
|
+
};
|
744
|
+
// ###template
|
745
|
+
// JavaScript micro-templating, similar to John Resig's (and it's offspring) implementation.
|
746
|
+
// sudo templating preserves whitespace, and correctly escapes quotes within interpolated code.
|
747
|
+
// Unlike others sudo.template requires a scope name (to avoid the use of `with`) and will spit at you
|
748
|
+
// if it is not present.
|
749
|
+
//
|
750
|
+
// `param` {string} `str`. The 'templated' string.
|
751
|
+
// `param` {Object} `data`. Optional hash of key:value pairs.
|
752
|
+
// `param` {string} `scope`. Optional context name of your `data object`, set to 'data' if falsy.
|
753
|
+
sudo.template = function template(str, data, scope) {
|
754
|
+
scope || (scope = 'data');
|
755
|
+
var settings = sudo.templateSettings, render, template,
|
756
|
+
// Compile the template source, taking care to escape characters that
|
757
|
+
// cannot be included in a string literal and then unescape them in code blocks.
|
758
|
+
source = "_p+='" + str.replace(sudo.escaper, function(match) {
|
759
|
+
return '\\' + sudo.escapes[match];
|
760
|
+
}).replace(settings.escape, function(match, code) {
|
761
|
+
return "'+\n((_t=(" + sudo.unescape(code) + "))==null?'':sudo.escape(_t))+\n'";
|
762
|
+
}).replace(settings.interpolate, function(match, code) {
|
763
|
+
return "'+\n((_t=(" + sudo.unescape(code) + "))==null?'':_t)+\n'";
|
764
|
+
}).replace(settings.evaluate, function(match, code) {
|
765
|
+
return "';\n" + sudo.unescape(code) + "\n_p+='";
|
766
|
+
}) + "';\n";
|
767
|
+
source = "var _t,_p='';" + source + "return _p;\n";
|
768
|
+
render = new Function(scope, source);
|
769
|
+
if (data) return render(data);
|
770
|
+
template = function(data) {
|
771
|
+
return render.call(this, data);
|
772
|
+
};
|
773
|
+
// Provide the compiled function source as a convenience for reflection/compilation
|
774
|
+
template.source = 'function(' + scope + '){\n' + source + '}';
|
775
|
+
return template;
|
776
|
+
};
|
777
|
+
// ##Dataview Class Object
|
778
|
+
|
779
|
+
// Create an instance of an Object, inheriting from sudo.View that:
|
780
|
+
// 1. Expects to have a template located in its internal data Store accessible via `this.get('template')`.
|
781
|
+
// 2. Can have a `renderTarget` property in its data store. If so this will be the location
|
782
|
+
// the child injects itself into (if not already in) the DOM
|
783
|
+
// 3. Can have a 'renderMethod' property in its data store. If so this is the jQuery method
|
784
|
+
// that the child will use to place itself in it's `renderTarget`.
|
785
|
+
// 4. Has a `render` method that when called re-hydrates it's $el by passing its
|
786
|
+
// internal data store to its template
|
787
|
+
// 5. Handles event binding/unbinding by implementing the sudo.ext.listener
|
788
|
+
// extension object
|
789
|
+
//
|
790
|
+
//`constructor`
|
791
|
+
sudo.Dataview = function(el, data) {
|
792
|
+
var d, t;
|
793
|
+
sudo.View.call(this, el, data);
|
794
|
+
// implements the listener extension
|
795
|
+
$.extend(this, sudo.ext.listener);
|
796
|
+
d = this._data_;
|
797
|
+
// dont autoRender on the setting of events
|
798
|
+
// add to this to prevent others if needed
|
799
|
+
d.autoRenderBlacklist = {event: true, events: true};
|
800
|
+
// compile my template if not already done
|
801
|
+
if((t = d.template)) {
|
802
|
+
if(typeof t === 'string') d.template = sudo.template(t);
|
803
|
+
}
|
804
|
+
if(this._role_ === 'dataview') {
|
805
|
+
// as all events are delegated to this.$el binding can take place here
|
806
|
+
this.bindEvents();
|
807
|
+
this.init();
|
808
|
+
}
|
809
|
+
};
|
810
|
+
// `private`
|
811
|
+
sudo._inherit_(sudo.View, sudo.Dataview);
|
812
|
+
// ###addedToParent
|
813
|
+
// Container's will check for the presence of this method and call it if it is present
|
814
|
+
// after adding a child - essentially, this will auto render the dataview when added to a parent
|
815
|
+
sudo.Dataview.prototype.addedToParent = function() {
|
816
|
+
return this.render();
|
817
|
+
};
|
818
|
+
// ###removeFromParent
|
819
|
+
// Remove this object from the DOM and its parent's list of children.
|
820
|
+
// Overrides `sudo.View.removeFromParent` to actually remove the DOM as well
|
821
|
+
//
|
822
|
+
// `returns` {Object} `this`
|
823
|
+
sudo.Dataview.prototype.removeFromParent = function removeFromParent() {
|
824
|
+
this._parent_._removeChild_(this);
|
825
|
+
this.$el.remove();
|
826
|
+
return this;
|
827
|
+
};
|
828
|
+
// ###render
|
829
|
+
// (Re)hydrate the innerHTML of this object via its template and internal data store.
|
830
|
+
// If a `renderTarget` is present this Object will inject itself into the target via
|
831
|
+
// `this.get('renderMethod')` or defualt to `$.append`. After injection, the `renderTarget`
|
832
|
+
// is deleted from this Objects data store.
|
833
|
+
// Event unbinding/rebinding is generally not necessary for the Objects innerHTML as all events from the
|
834
|
+
// Object's list of events (`this.get('event(s)'))` are delegated to the $el on instantiation.
|
835
|
+
//
|
836
|
+
// `returns` {Object} `this`
|
837
|
+
sudo.Dataview.prototype.render = function render() {
|
838
|
+
var d = this._data_;
|
839
|
+
this.$el.html(d.template(d));
|
840
|
+
if(d.renderTarget) {
|
841
|
+
this._normalizedEl_(d.renderTarget)[d.renderMethod || 'append'](this.$el);
|
842
|
+
delete d.renderTarget;
|
843
|
+
}
|
844
|
+
return this;
|
845
|
+
};
|
846
|
+
// `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
|
+
};
|
887
|
+
// ## Observable Extension Object
|
888
|
+
// Implementaion of the ES6 Harmony Observer pattern
|
889
|
+
sudo.ext.observable = {
|
890
|
+
// ###_deliver_
|
891
|
+
// Called from deliverChangeRecords when ready to send
|
892
|
+
// changeRecords to observers.
|
893
|
+
//
|
894
|
+
// `private`
|
895
|
+
_deliver_: function _deliver_(obj) {
|
896
|
+
var i, cb = this._callbacks_;
|
897
|
+
for(i = 0; i < cb.length; i++) {
|
898
|
+
cb[i](obj);
|
899
|
+
}
|
900
|
+
},
|
901
|
+
// ###deliverChangeRecords
|
902
|
+
// Iterate through the changeRecords array(emptying it as you go), delivering them to the
|
903
|
+
// observers. You can override this method to change the standard delivery behavior.
|
904
|
+
//
|
905
|
+
// `returns` {Object} `this`
|
906
|
+
deliverChangeRecords: function deliverChangeRecords() {
|
907
|
+
var rec, cr = this._changeRecords_;
|
908
|
+
// FIFO
|
909
|
+
for(rec; cr.length && (rec = cr.shift());) {
|
910
|
+
this._deliver_(rec);
|
911
|
+
}
|
912
|
+
return this;
|
913
|
+
},
|
914
|
+
// ###observe
|
915
|
+
// In a quasi-ES6 Object.observe pattern, calling observe on an `observable` and
|
916
|
+
// passing a callback will cause that callback to be called whenever any
|
917
|
+
// property on the observable's data store is set, changed or deleted
|
918
|
+
// via set, unset, setPath or unsetPath with an object containing:
|
919
|
+
// {
|
920
|
+
// type: <new, updated, deleted>,
|
921
|
+
// object: <the object being observed>,
|
922
|
+
// name: <the key that was modified>,
|
923
|
+
// oldValue: <if a previous value existed for this key>
|
924
|
+
// }
|
925
|
+
// For ease of 'unobserving' the same Function passed in is returned.
|
926
|
+
//
|
927
|
+
// `param` {Function} `fn` The callback to be called with changeRecord(s)
|
928
|
+
// `returns` {Function} the Function passed in as an argument
|
929
|
+
observe: function observe(fn) {
|
930
|
+
// this will fail if mixed-in and no `callbacks` created so don't do that.
|
931
|
+
// Per the spec, do not allow the same callback to be added
|
932
|
+
var d = this._callbacks_;
|
933
|
+
if(d.indexOf(fn) === -1) d.push(fn);
|
934
|
+
return fn;
|
935
|
+
},
|
936
|
+
// ###observes
|
937
|
+
// Allow an array of callbacks to be registered as changeRecord recipients
|
938
|
+
//
|
939
|
+
// `param` {Array} ary
|
940
|
+
// `returns` {Object} `this`
|
941
|
+
observes: function observes(ary) {
|
942
|
+
var i;
|
943
|
+
for(i = 0; i < ary.length; i++) {
|
944
|
+
this.observe(ary[i]);
|
945
|
+
}
|
946
|
+
return this;
|
947
|
+
},
|
948
|
+
// ###set
|
949
|
+
// Overrides sudo.Base.set to check for observers
|
950
|
+
//
|
951
|
+
// `param` {String} `key`. The name of the key
|
952
|
+
// `param` {*} `value`
|
953
|
+
// `param` {Bool} `hold` Call _deliver_ (falsy) or store the change notification
|
954
|
+
// to be delivered upon a call to deliverChangeRecords (truthy)
|
955
|
+
//
|
956
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
957
|
+
set: function set(key, value, hold) {
|
958
|
+
var obj = {name: key, object: this._data_};
|
959
|
+
// did this key exist already
|
960
|
+
if(key in this._data_) {
|
961
|
+
obj.type = 'updated';
|
962
|
+
// then there is an oldValue
|
963
|
+
obj.oldValue = this._data_[key];
|
964
|
+
} else obj.type = 'new';
|
965
|
+
// now actually set the value
|
966
|
+
this._data_[key] = value;
|
967
|
+
this._changeRecords_.push(obj);
|
968
|
+
// call the observers or not
|
969
|
+
if(hold) return this;
|
970
|
+
return this.deliverChangeRecords();
|
971
|
+
},
|
972
|
+
// ###setPath
|
973
|
+
// Overrides sudo.Base.setPath to check for observers.
|
974
|
+
// Change records originating from a `setPath` operation
|
975
|
+
// send back the passed in `path` as `name` as well as the
|
976
|
+
// top level object being observed (this observable's _data_).
|
977
|
+
// this allows for easy filtering either manually or via a
|
978
|
+
// `change delegate`
|
979
|
+
//
|
980
|
+
// `param` {String} `path`
|
981
|
+
// `param` {*} `value`
|
982
|
+
// `param` {Bool} `hold` Call _deliver_ (falsy) or store the change notification
|
983
|
+
// to be delivered upon a call to deliverChangeRecords (truthy)
|
984
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
985
|
+
setPath: function setPath(path, value, hold) {
|
986
|
+
var curr = this._data_, obj = {name: path, object: this._data_},
|
987
|
+
p = path.split('.'), key;
|
988
|
+
for (key; p.length && (key = p.shift());) {
|
989
|
+
if(!p.length) {
|
990
|
+
// reached the last refinement, pre-existing?
|
991
|
+
if (key in curr) {
|
992
|
+
obj.type = 'updated';
|
993
|
+
obj.oldValue = curr[key];
|
994
|
+
} else obj.type = 'new';
|
995
|
+
curr[key] = value;
|
996
|
+
} else if (curr[key]) {
|
997
|
+
curr = curr[key];
|
998
|
+
} else {
|
999
|
+
curr = curr[key] = {};
|
1000
|
+
}
|
1001
|
+
}
|
1002
|
+
this._changeRecords_.push(obj);
|
1003
|
+
// call all observers or not
|
1004
|
+
if(hold) return this;
|
1005
|
+
return this.deliverChangeRecords();
|
1006
|
+
},
|
1007
|
+
// ###sets
|
1008
|
+
// Overrides Base.sets to hold the call to _deliver_ until
|
1009
|
+
// all operations are done
|
1010
|
+
//
|
1011
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
1012
|
+
sets: function sets(obj, hold) {
|
1013
|
+
var i, k = Object.keys(obj);
|
1014
|
+
for(i = 0; i < k.length; i++) {
|
1015
|
+
k[i].indexOf('.') === -1 ? this.set(k[i], obj[k[i]], true) :
|
1016
|
+
this.setPath(k[i], obj[k[i]], true);
|
1017
|
+
}
|
1018
|
+
if(hold) return this;
|
1019
|
+
return this.deliverChangeRecords();
|
1020
|
+
},
|
1021
|
+
// ###unobserve
|
1022
|
+
// Remove a particular callback from this observable
|
1023
|
+
//
|
1024
|
+
// `param` {Function} the function passed in to `observe`
|
1025
|
+
// `returns` {Object} `this`
|
1026
|
+
unobserve: function unobserve(fn) {
|
1027
|
+
var cb = this._callbacks_, i = cb.indexOf(fn);
|
1028
|
+
if(i !== -1) cb.splice(i, 1);
|
1029
|
+
return this;
|
1030
|
+
},
|
1031
|
+
// ###unobserves
|
1032
|
+
// Allow an array of callbacks to be unregistered as changeRecord recipients
|
1033
|
+
//
|
1034
|
+
// `param` {Array} ary
|
1035
|
+
// `returns` {Object} `this`
|
1036
|
+
unobserves: function unobserves(ary) {
|
1037
|
+
var i;
|
1038
|
+
for(i = 0; i < ary.length; i++) {
|
1039
|
+
this.unobserve(ary[i]);
|
1040
|
+
}
|
1041
|
+
return this;
|
1042
|
+
},
|
1043
|
+
// ###unset
|
1044
|
+
// Overrides sudo.Base.unset to check for observers
|
1045
|
+
//
|
1046
|
+
// `param` {String} `key`. The name of the key
|
1047
|
+
// `param` {Bool} `hold`
|
1048
|
+
//
|
1049
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
1050
|
+
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];
|
1054
|
+
// call the observers if there was a val to delete
|
1055
|
+
return this._unset_(obj, val, hold);
|
1056
|
+
},
|
1057
|
+
// ###_unset_
|
1058
|
+
// Helper for the unset functions
|
1059
|
+
//
|
1060
|
+
// `private`
|
1061
|
+
_unset_: function _unset_(o, v, h) {
|
1062
|
+
if(v) {
|
1063
|
+
this._changeRecords_.push(o);
|
1064
|
+
if(h) return this;
|
1065
|
+
return this.deliverChangeRecords();
|
1066
|
+
}
|
1067
|
+
return this;
|
1068
|
+
},
|
1069
|
+
// ###setPath
|
1070
|
+
// Overrides sudo.Base.unsetPath to check for observers
|
1071
|
+
//
|
1072
|
+
// `param` {String} `path`
|
1073
|
+
// `param` {*} `value`
|
1074
|
+
// `param` {bool} `hold`
|
1075
|
+
//
|
1076
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
1077
|
+
unsetPath: function unsetPath(path, hold) {
|
1078
|
+
var obj = {name: path, object: this._data_, type: 'deleted'},
|
1079
|
+
curr = this._data_, p = path.split('.'),
|
1080
|
+
key, val;
|
1081
|
+
for (key; p.length && (key = p.shift());) {
|
1082
|
+
if(!p.length) {
|
1083
|
+
// reached the last refinement
|
1084
|
+
val = !!curr[key];
|
1085
|
+
delete curr[key];
|
1086
|
+
} else {
|
1087
|
+
// this can obviously fail, but can be prevented by checking
|
1088
|
+
// with `getPath` first.
|
1089
|
+
curr = curr[key];
|
1090
|
+
}
|
1091
|
+
}
|
1092
|
+
return this._unset_(obj, val, hold);
|
1093
|
+
},
|
1094
|
+
// ###unsets
|
1095
|
+
// Override of Base.unsets to hold the call to _deliver_ until done
|
1096
|
+
//
|
1097
|
+
// `param` ary
|
1098
|
+
// `param` hold
|
1099
|
+
// `returns` {Object|*} `this` or calls deliverChangeRecords
|
1100
|
+
unsets: function unsets(ary, hold) {
|
1101
|
+
var i;
|
1102
|
+
for(i = 0; i < ary.length; i++) {
|
1103
|
+
ary[i].indexOf('.') === -1 ? this.unset(k[i], true) :
|
1104
|
+
this.unsetPath(k[i], true);
|
1105
|
+
}
|
1106
|
+
if(hold) return this;
|
1107
|
+
return this.deliverChangeRecords();
|
1108
|
+
}
|
1109
|
+
};
|
1110
|
+
// ##Bindable Extension Object
|
1111
|
+
|
1112
|
+
// Bindable methods allow various properties and attributes of
|
1113
|
+
// a sudo Class Object to be synchronized with the data contained
|
1114
|
+
// in a changeRecord recieved via observe().
|
1115
|
+
//
|
1116
|
+
// `namespace`
|
1117
|
+
sudo.ext.bindable = {
|
1118
|
+
// List of attributes - $.attr() to be used.
|
1119
|
+
//
|
1120
|
+
// `private`
|
1121
|
+
_attr_: {
|
1122
|
+
accesskey: true,
|
1123
|
+
align: true,
|
1124
|
+
alt: true,
|
1125
|
+
contenteditable: true,
|
1126
|
+
draggable: true,
|
1127
|
+
href: true,
|
1128
|
+
label: true,
|
1129
|
+
name: true,
|
1130
|
+
rel: true,
|
1131
|
+
src: true,
|
1132
|
+
tabindex: true,
|
1133
|
+
title: true
|
1134
|
+
},
|
1135
|
+
// Some bindings defer to jQuery.css() to be bound.
|
1136
|
+
//
|
1137
|
+
// `private`
|
1138
|
+
_css_: {
|
1139
|
+
display: true,
|
1140
|
+
visibility: true
|
1141
|
+
},
|
1142
|
+
// ###_handleAttr_
|
1143
|
+
// bind the jQuery prop() method to this object, now exposed
|
1144
|
+
// by this name, matching passed `bindings` arguments.
|
1145
|
+
//
|
1146
|
+
// `param` {string} `meth` The name of the method to be bound
|
1147
|
+
// `returns` {Object} `this`
|
1148
|
+
// `private`
|
1149
|
+
_handleAttr_: function _handleAttr_(meth) {
|
1150
|
+
this[meth] = function(obj) {
|
1151
|
+
if(obj.name === meth) this.$el.attr(meth, obj.object[obj.name]);
|
1152
|
+
return this;
|
1153
|
+
};
|
1154
|
+
return this;
|
1155
|
+
},
|
1156
|
+
// ###_handleCss_
|
1157
|
+
// bind the jQuery css() method to this object, now exposed
|
1158
|
+
// by this name, matching passed `bindings` arguments.
|
1159
|
+
//
|
1160
|
+
// `param` {string} `meth` The name of the method to be bound
|
1161
|
+
// `returns` {Object} `this`
|
1162
|
+
// `private`
|
1163
|
+
_handleCss_: function _handleCss_(meth) {
|
1164
|
+
this[meth] = function(obj) {
|
1165
|
+
if(obj.name === meth) this.$el.css(meth, obj.object[obj.name]);
|
1166
|
+
return this;
|
1167
|
+
};
|
1168
|
+
return this;
|
1169
|
+
},
|
1170
|
+
// ###_handleData_
|
1171
|
+
// bind the jQuery data() method to this object, now exposed
|
1172
|
+
// by this name, matching passed `bindings` arguments.
|
1173
|
+
//
|
1174
|
+
// `param` {string} `meth` The name of the method to be bound
|
1175
|
+
// `returns` {Object} `this`
|
1176
|
+
// `private`
|
1177
|
+
_handleData_: function _handleData_(meth) {
|
1178
|
+
this[meth] = function(obj) {
|
1179
|
+
if(obj.name === meth) {
|
1180
|
+
this.$el.data(obj.object[obj.name].key, obj.object[obj.name].value);
|
1181
|
+
return this;
|
1182
|
+
}
|
1183
|
+
};
|
1184
|
+
return this;
|
1185
|
+
},
|
1186
|
+
// ###_handleProp_
|
1187
|
+
// bind the jQuery attr() method to this object, now exposed
|
1188
|
+
// by this name, matching passed `bindings` arguments.
|
1189
|
+
//
|
1190
|
+
// NOTE: If more than 1 data-* attribute is desired you must
|
1191
|
+
// set those up manually as <obj>.data({..}) is what will be
|
1192
|
+
// constructed via this method.
|
1193
|
+
//
|
1194
|
+
// `param` {string} `meth` The name of the method to be bound.
|
1195
|
+
// `returns` {Object} `this`
|
1196
|
+
// `private`
|
1197
|
+
_handleProp_: function _handleProp_(meth) {
|
1198
|
+
this[meth] = function(obj) {
|
1199
|
+
if(obj.name === meth) this.$el.prop(meth, obj.object[obj.name]);
|
1200
|
+
return this;
|
1201
|
+
};
|
1202
|
+
return this;
|
1203
|
+
},
|
1204
|
+
// ###_handleSpec_
|
1205
|
+
// bind the jQuery shorthand methods to this object matching
|
1206
|
+
// passed `bindings` arguments.
|
1207
|
+
//
|
1208
|
+
// `param` {string} `meth` The name of the method to be bound.
|
1209
|
+
// `returns` {Object} `this`
|
1210
|
+
// `private`
|
1211
|
+
_handleSpec_: function _handleSpec_(meth) {
|
1212
|
+
this[meth] = function(obj) {
|
1213
|
+
if(obj.name === meth) this.$el[meth](obj.object[obj.name]);
|
1214
|
+
return this;
|
1215
|
+
};
|
1216
|
+
return this;
|
1217
|
+
},
|
1218
|
+
// List of properties - $.prop() to be used.
|
1219
|
+
//
|
1220
|
+
// `private`
|
1221
|
+
_prop_: {
|
1222
|
+
checked: true,
|
1223
|
+
defaultValue: true,
|
1224
|
+
disabled: true,
|
1225
|
+
location: true,
|
1226
|
+
multiple: true,
|
1227
|
+
readOnly: true,
|
1228
|
+
selected: true
|
1229
|
+
},
|
1230
|
+
// ###setBinding
|
1231
|
+
// Use case when you know there is only a single binding,
|
1232
|
+
// create the expected methods and return
|
1233
|
+
//
|
1234
|
+
// `returns` {Object} `this`
|
1235
|
+
setBinding: function setBinding() {
|
1236
|
+
return this._setBinding_(this._data_.binding);
|
1237
|
+
},
|
1238
|
+
// ###_setBinding_
|
1239
|
+
// Given a single explicit binding, create it. Called from
|
1240
|
+
// _setbindings_ as a convenience for normalizing the
|
1241
|
+
// single vs. multiple bindings scenario
|
1242
|
+
//
|
1243
|
+
// `param` {string} `b` The binding.
|
1244
|
+
// `private`
|
1245
|
+
_setBinding_: function _setBinding_(b) {
|
1246
|
+
if(b in this._spec_) return this[this._spec_[b]](b);
|
1247
|
+
if(b in this._css_) return this._handleCss_(b);
|
1248
|
+
if(b in this._attr_) return this._handleAttr_(b);
|
1249
|
+
if(b in this._prop_) return this._handleProp_(b);
|
1250
|
+
},
|
1251
|
+
// ###setBindings
|
1252
|
+
// Inspect the binding (in the single-bound use case), or the
|
1253
|
+
// bindings Array in this Object's data store and
|
1254
|
+
// create the bound functions expected.
|
1255
|
+
//
|
1256
|
+
// `returns` {Object} `this`
|
1257
|
+
setBindings: function setBindings() {
|
1258
|
+
var b, i;
|
1259
|
+
// handle the single binding use case
|
1260
|
+
if((b = this._data_.binding)) return this._setBinding_(b);
|
1261
|
+
if(!(b = this._data_.bindings)) return this;
|
1262
|
+
for(i = 0; i < b.length; i++) {
|
1263
|
+
this._setBinding_(b[i]);
|
1264
|
+
}
|
1265
|
+
return this;
|
1266
|
+
},
|
1267
|
+
// `Special` binding cases. jQuery shorthand methods to be used.
|
1268
|
+
//
|
1269
|
+
// `private`
|
1270
|
+
_spec_: {
|
1271
|
+
data: '_handleData_',
|
1272
|
+
html: '_handleSpec_',
|
1273
|
+
text: '_handleSpec_',
|
1274
|
+
val: '_handleSpec_'
|
1275
|
+
}
|
1276
|
+
};
|
1277
|
+
// ##Listener Extension Object
|
1278
|
+
|
1279
|
+
// Handles event binding/unbinding via an events array in the form:
|
1280
|
+
// events: [{
|
1281
|
+
// name: `eventName`,
|
1282
|
+
// sel: `an_optional_delegator`,
|
1283
|
+
// data: an_optional_hash_of_data
|
1284
|
+
// fn: `function name`
|
1285
|
+
// }, {...
|
1286
|
+
// This array will be searched for via `this.get('events')`. There is a
|
1287
|
+
// single-event use case as well, pass a single object literal in the above form.
|
1288
|
+
// with the key `event`:
|
1289
|
+
// event: {...same as above}
|
1290
|
+
// Details about the hashes in the array:
|
1291
|
+
// A. name -> jQuery compatible event name
|
1292
|
+
// B. sel -> Optional jQuery compatible selector used to delegate events
|
1293
|
+
// C. data: A hash that will be passed as the custom jQuery Event.data object
|
1294
|
+
// D. fn -> If a {String} bound to the named function on this object, if a
|
1295
|
+
// function assumed to be anonymous and called with no scope manipulation
|
1296
|
+
sudo.ext.listener = {
|
1297
|
+
// ###bindEvents
|
1298
|
+
// Bind the events in the data store to this object's $el
|
1299
|
+
//
|
1300
|
+
// `returns` {Object} `this`
|
1301
|
+
bindEvents: function bindEvents() {
|
1302
|
+
var e;
|
1303
|
+
if((e = this._data_.event || this._data_.events)) this._handleEvents_(e, 1);
|
1304
|
+
return this;
|
1305
|
+
},
|
1306
|
+
// Use the jQuery `on` or 'off' method, optionally delegating to a selector if present
|
1307
|
+
// `private`
|
1308
|
+
_handleEvents_: function _handleEvents_(e, which) {
|
1309
|
+
var i;
|
1310
|
+
if(Array.isArray(e)) {
|
1311
|
+
for(i = 0; i < e.length; i++) {
|
1312
|
+
this._handleEvent_(e[i], which);
|
1313
|
+
}
|
1314
|
+
} else {
|
1315
|
+
this._handleEvent_(e, which);
|
1316
|
+
}
|
1317
|
+
},
|
1318
|
+
// helper for binding and unbinding an individual event
|
1319
|
+
// `param` {Object} e. An event descriptor
|
1320
|
+
// `param` {String} which. `on` or `off`
|
1321
|
+
// `private`
|
1322
|
+
_handleEvent_: function _handleEvent_(e, which) {
|
1323
|
+
if(which) {
|
1324
|
+
this.$el.on(e.name, e.sel, e.data, typeof e.fn === 'string' ? this[e.fn].bind(this) : e.fn);
|
1325
|
+
} else {
|
1326
|
+
// do not re-bind the fn going to off otherwise the unbind will fail
|
1327
|
+
this.$el.off(e.name, e.sel);
|
1328
|
+
}
|
1329
|
+
},
|
1330
|
+
// ###unbindEvents
|
1331
|
+
// Unbind the events in the data store from this object's $el
|
1332
|
+
//
|
1333
|
+
// `returns` {Object} `this`
|
1334
|
+
unbindEvents: function unbindEvents() {
|
1335
|
+
var e;
|
1336
|
+
if((e = this._data_.event || this._data_.events)) this._handleEvents_(e);
|
1337
|
+
return this;
|
1338
|
+
}
|
1339
|
+
};
|
1340
|
+
|
1341
|
+
sudo.version = "0.8.0";
|
1342
|
+
window.sudo = sudo;
|
1343
|
+
if(typeof window._ === "undefined") window._ = sudo;
|
1344
|
+
}).call(this, this);
|