backbone_extensions 0.0.5 → 0.0.6
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/README.markdown +2 -1
- data/config/jshint.yml +15 -5
- data/lib/assets/javascripts/backbone_extensions/associations.js +166 -0
- data/lib/assets/javascripts/backbone_extensions/decorator.js +34 -48
- data/lib/assets/javascripts/backbone_extensions/delegate_events.js +94 -0
- data/lib/assets/javascripts/backbone_extensions/include.js +4 -3
- data/lib/backbone_extensions/version.rb +1 -1
- metadata +9 -5
data/README.markdown
CHANGED
@@ -13,5 +13,6 @@ Installing
|
|
13
13
|
3. In your application.js //= require backbone
|
14
14
|
4. In your application.js //= require backbone_extensions/include
|
15
15
|
5. In your application.js //= require backbone_extensions/decorator
|
16
|
+
6. In your application.js //= require backbone_extensions/associations
|
16
17
|
|
17
|
-
Copyright (c) 2012 Ryan Dy, released under the MIT license
|
18
|
+
Copyright (c) 2012-3 Ryan Dy, Thomas Bukowski, released under the MIT license
|
data/config/jshint.yml
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
paths:
|
10
10
|
- lib/assets/javascripts/**/*.js
|
11
|
+
- spec/javascripts/**/*.js
|
11
12
|
|
12
13
|
exclude_paths:
|
13
14
|
|
@@ -20,7 +21,7 @@ exclude_paths:
|
|
20
21
|
adsafe: false # true if ADsafe rules should be enforced. See http://www.ADsafe.org/
|
21
22
|
bitwise: true # true if bitwise operators should not be allowed
|
22
23
|
newcap: true # true if Initial Caps must be used with constructor functions
|
23
|
-
eqeqeq:
|
24
|
+
eqeqeq: true # true if === should be required (for ALL equality comparisons)
|
24
25
|
immed: false # true if immediate function invocations must be wrapped in parens
|
25
26
|
nomen: false # true if initial or trailing underscore in identifiers should be forbidden
|
26
27
|
onevar: false # true if only one var statement per function should be allowed
|
@@ -43,6 +44,7 @@ fragment: false # true if HTML fragments should be allowed
|
|
43
44
|
laxbreak: false # true if statement breaks should not be checked
|
44
45
|
on: false # true if HTML event handlers (e.g. onclick="...") should be allowed
|
45
46
|
sub: false # true if subscript notation may be used for expressions better expressed in dot notation
|
47
|
+
validthis: true # true if suppresses warnings about possible strict violations when the code is running in strict mode and you use this in a non-constructor function
|
46
48
|
|
47
49
|
# other options
|
48
50
|
|
@@ -55,6 +57,14 @@ predef: # Names of predefined global variables - comma-separated strin
|
|
55
57
|
- _
|
56
58
|
- Backbone
|
57
59
|
- jQuery
|
60
|
+
- $
|
61
|
+
- describe
|
62
|
+
- it
|
63
|
+
- beforeEach
|
64
|
+
- afterEach
|
65
|
+
- expect
|
66
|
+
- jasmine
|
67
|
+
- spyOn
|
58
68
|
|
59
69
|
browser: true # true if the standard browser globals should be predefined
|
60
70
|
rhino: false # true if the Rhino environment globals should be predefined
|
@@ -64,12 +74,12 @@ devel: false # true if functions like alert, confirm, console, prompt etc
|
|
64
74
|
|
65
75
|
# jshint options
|
66
76
|
loopfunc: true # true if functions should be allowed to be defined within loops
|
67
|
-
asi:
|
68
|
-
boss:
|
69
|
-
couch:
|
77
|
+
asi: false # true if automatic semicolon insertion should be tolerated
|
78
|
+
boss: false # true if advanced usage of assignments and == should be allowed
|
79
|
+
couch: false # true if CouchDB globals should be predefined
|
70
80
|
curly: false # true if curly braces around blocks should be required (even in if/for/while)
|
71
81
|
noarg: true # true if arguments.caller and arguments.callee should be disallowed
|
72
|
-
node:
|
82
|
+
node: false # true if the Node.js environment globals should be predefined
|
73
83
|
noempty: true # true if empty blocks should be disallowed
|
74
84
|
nonew: true # true if using `new` for side-effects should be disallowed
|
75
85
|
|
@@ -0,0 +1,166 @@
|
|
1
|
+
//= require backbone_extensions/include
|
2
|
+
//= require underscore.string
|
3
|
+
(function(_, Backbone) {
|
4
|
+
'use strict';
|
5
|
+
function mixin(namespace) {
|
6
|
+
namespace = namespace || {};
|
7
|
+
|
8
|
+
function mergeAssociationOptions() {
|
9
|
+
return _(arguments).chain().toArray().reduce(function(result, options) { return _(result).extend(options); }, {})
|
10
|
+
.omit('class', 'className', 'inverseOf', 'parseName', 'through').value();
|
11
|
+
}
|
12
|
+
|
13
|
+
function buildAssociation(associationType, associationName, options) {
|
14
|
+
function through() {
|
15
|
+
function association() {
|
16
|
+
var t = (_(options.through).isFunction() && options.through.call(this)) || _.str.camelize(options.through);
|
17
|
+
return this[t] && this[t]() && this[t]()[associationName] && this[t]()[associationName]();
|
18
|
+
}
|
19
|
+
return options.through && association.call(this);
|
20
|
+
}
|
21
|
+
|
22
|
+
function throughCollection() {
|
23
|
+
return (this.collection && this.collection[associationName] && this.collection[associationName]()) ||
|
24
|
+
(this._options && this._options.collection && this._options.collection[associationName] && this._options.collection[associationName]());
|
25
|
+
}
|
26
|
+
|
27
|
+
function createAssociation() {
|
28
|
+
var collectionName = _.str.classify(associationName), className = options.className && _.str.classify(options.className),
|
29
|
+
newOptions = mergeAssociationOptions(options, this._options);
|
30
|
+
|
31
|
+
if (options.inverseOf) {
|
32
|
+
newOptions[_.str.camelize(options.inverseOf)] = _(function() { return this; }).bind(this);
|
33
|
+
}
|
34
|
+
|
35
|
+
return _((options['class'] && new options['class'](null, newOptions)) ||
|
36
|
+
(className && namespace[className] && new namespace[className](null, newOptions)) ||
|
37
|
+
(namespace[collectionName] && new namespace[collectionName](null, newOptions))).tap(_(function(association) {
|
38
|
+
through.call(this, association);
|
39
|
+
}).bind(this));
|
40
|
+
}
|
41
|
+
|
42
|
+
var associations = {
|
43
|
+
hasMany: createAssociation,
|
44
|
+
hasOne: function() { return throughCollection.call(this) || createAssociation.call(this); },
|
45
|
+
belongsTo: function() { return throughCollection.call(this) || through.call(this); }
|
46
|
+
};
|
47
|
+
|
48
|
+
this.prototype[associationName] = function() {
|
49
|
+
return (this._associations || (this._associations = {})) && this._associations[associationName] ||
|
50
|
+
(this._associations[associationName] = (this._options && _(this._options).result(associationName)) || associations[associationType].call(this));
|
51
|
+
};
|
52
|
+
}
|
53
|
+
|
54
|
+
function parseAssociation(associationType, associationName, options) {
|
55
|
+
function parseResponseWith(key, response) {
|
56
|
+
var camelized = _.str.camelize(key), underscored = _.str.underscored(key);
|
57
|
+
if (response[camelized]) {
|
58
|
+
return {key: camelized, response: response[camelized]};
|
59
|
+
}
|
60
|
+
else if (response[underscored]) {
|
61
|
+
return {key: underscored, response: response[underscored]};
|
62
|
+
}
|
63
|
+
else {
|
64
|
+
return {response: null};
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
function through(response) {
|
69
|
+
var t = parseResponseWith(_(options).result('through'), response).response,
|
70
|
+
singularAssociationName = _.singularize && _(associationName).singularize(),
|
71
|
+
p = options.parseName || singularAssociationName;
|
72
|
+
return {response: t && p && _(t)[associationType === 'hasOne' ? 'result' : 'pluck'](p)};
|
73
|
+
}
|
74
|
+
|
75
|
+
if (options.parse) {
|
76
|
+
var associations = {
|
77
|
+
hasMany: function(assocResponse, association, newOptions) {
|
78
|
+
association.add(assocResponse, newOptions);
|
79
|
+
},
|
80
|
+
hasOne: function(assocResponse, association, newOptions) {
|
81
|
+
association.clear({silent: true}).set(assocResponse, newOptions);
|
82
|
+
}
|
83
|
+
};
|
84
|
+
|
85
|
+
if (associations[associationType]) {
|
86
|
+
var parseFunc = _(options.parse).isFunction() &&
|
87
|
+
function(response) { return {response: options.parse.call(this, response) }; } ||
|
88
|
+
function(response) {
|
89
|
+
return (options.through && through.call(this, response)) ||
|
90
|
+
(options.parseName && parseResponseWith(options.parseName, response)) ||
|
91
|
+
(options.className && parseResponseWith(options.className, response)) ||
|
92
|
+
parseResponseWith(associationName, response);
|
93
|
+
};
|
94
|
+
|
95
|
+
if(!this._parsers) {
|
96
|
+
var originalParse = this.prototype.parse,
|
97
|
+
parsers = this._parsers = [];
|
98
|
+
this.prototype.parse = function(response) {
|
99
|
+
return _(originalParse.call(this, response)).tap(_(function(parsedResponse) {
|
100
|
+
_(parsers)
|
101
|
+
.chain()
|
102
|
+
.map(function(parser) {
|
103
|
+
return _(parser.parseFn.call(this, parsedResponse)).tap(_(function(result) {
|
104
|
+
parser.associationFn.call(this, result.response);
|
105
|
+
}).bind(this)).key;
|
106
|
+
}, this)
|
107
|
+
.each(function(key) {
|
108
|
+
return key && delete parsedResponse[key];
|
109
|
+
});
|
110
|
+
}).bind(this));
|
111
|
+
};
|
112
|
+
}
|
113
|
+
|
114
|
+
this._parsers.push({
|
115
|
+
parseFn: parseFunc,
|
116
|
+
associationFn: function(assocResponse) {
|
117
|
+
associations[associationType].call(this, assocResponse, this[associationName](), mergeAssociationOptions(options, this.options));
|
118
|
+
}
|
119
|
+
});
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
return {
|
125
|
+
included: function(source) {
|
126
|
+
var associations = _({
|
127
|
+
belongsTo: {}, hasMany: {parse: true}, hasOne: {parse: true}
|
128
|
+
}).reduce(function(associations, defaultOptions, associationType) {
|
129
|
+
associations[associationType] = function(name, options) {
|
130
|
+
var associationName = _.str.camelize(name);
|
131
|
+
options = _({}).extend(defaultOptions, options);
|
132
|
+
buildAssociation.call(this, associationType, associationName, options);
|
133
|
+
parseAssociation.call(this, associationType, associationName, options);
|
134
|
+
return this;
|
135
|
+
};
|
136
|
+
return associations;
|
137
|
+
}, {});
|
138
|
+
|
139
|
+
_(source).extend(associations, {
|
140
|
+
associations: function() {
|
141
|
+
_(arguments).chain().toArray().each(function(options) {
|
142
|
+
_(associations).chain().keys().each(function(associationType) {
|
143
|
+
if (options[associationType]) {
|
144
|
+
associations[associationType].call(source, options[associationType], _(options).omit(associationType));
|
145
|
+
}
|
146
|
+
});
|
147
|
+
});
|
148
|
+
},
|
149
|
+
|
150
|
+
extend: _(source.extend).wrap(function(oldExtend, protoProps, classProps) {
|
151
|
+
return _(oldExtend.call(this, protoProps, classProps)).tap(function() {
|
152
|
+
source.associations((protoProps || {}).associations);
|
153
|
+
});
|
154
|
+
})
|
155
|
+
});
|
156
|
+
|
157
|
+
source.prototype.initialize = _(source.prototype.initialize).wrap(function(oldInitialize, attrsOrModels, options) {
|
158
|
+
this._options = _(options).clone();
|
159
|
+
oldInitialize.call(this, attrsOrModels, options);
|
160
|
+
});
|
161
|
+
}
|
162
|
+
};
|
163
|
+
}
|
164
|
+
|
165
|
+
Backbone.extensions = _(Backbone.extensions || {}).extend({associations: mixin});
|
166
|
+
})(_, Backbone);
|
@@ -1,59 +1,45 @@
|
|
1
|
-
(function(
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
.functions()
|
7
|
-
.without('collection', 'constructor', 'initialize', 'model')
|
8
|
-
.inject(function(result, method) { result[method] = self[method]; return result; }, {})
|
9
|
-
.value();
|
10
|
-
|
11
|
-
if (decoratee instanceof Array) {
|
12
|
-
_(proto).each(function(fn, name) {
|
13
|
-
self[name] = function() {
|
14
|
-
var args = arguments;
|
15
|
-
return _(decoratee).map(function(model) {
|
16
|
-
return fn.apply(model, args);
|
17
|
-
});
|
18
|
-
};
|
19
|
-
});
|
20
|
-
} else {
|
21
|
-
_(proto).each(function(fn, name) {
|
22
|
-
self[name] = function() {
|
23
|
-
return fn.apply(decoratee, arguments);
|
24
|
-
};
|
25
|
-
});
|
26
|
-
}
|
1
|
+
(function(_, Backbone) {
|
2
|
+
'use strict';
|
3
|
+
function Decorator(models, options) {
|
4
|
+
this._decoratee = models instanceof Backbone.Collection ? models.models : models;
|
5
|
+
this.initialize.call(this, models, options);
|
27
6
|
}
|
28
7
|
|
29
|
-
function
|
30
|
-
|
31
|
-
|
8
|
+
function wrapDecorator(fn) {
|
9
|
+
return function() {
|
10
|
+
var args = arguments;
|
11
|
+
if (_(this._decoratee).isArray()) {
|
12
|
+
return _(this._decoratee).map(function(model) {
|
13
|
+
return fn.apply(model, args);
|
14
|
+
});
|
15
|
+
} else {
|
16
|
+
return fn.apply(this._decoratee, args);
|
17
|
+
}
|
18
|
+
};
|
32
19
|
}
|
33
20
|
|
34
21
|
_(Decorator).extend({
|
35
22
|
extend: function(protoProps, classProps) {
|
36
|
-
var
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
return Klass;
|
23
|
+
var proto = _(protoProps).chain().omit('collection', 'constructor', 'initialize', 'model').reduce(function(proto, fn, name) {
|
24
|
+
proto[name] = wrapDecorator(fn);
|
25
|
+
return proto;
|
26
|
+
}, {}).value();
|
27
|
+
|
28
|
+
return _(Backbone.Model.extend.call(this, _(protoProps).extend(proto), classProps)).tap(function(Klass) {
|
29
|
+
_(['model', 'collection']).each(function(type) {
|
30
|
+
if (protoProps[type]) {
|
31
|
+
protoProps[type].prototype.decorator = function() {
|
32
|
+
return new Klass(this);
|
33
|
+
};
|
34
|
+
}
|
35
|
+
});
|
36
|
+
});
|
51
37
|
}
|
52
|
-
}, Backbone.
|
38
|
+
}, Backbone.extensions && Backbone.extensions.include || {});
|
53
39
|
|
54
40
|
_(Decorator.prototype).extend({
|
55
|
-
initialize:
|
41
|
+
initialize: function(models, options) {}
|
56
42
|
});
|
57
43
|
|
58
|
-
Backbone.
|
59
|
-
})(
|
44
|
+
Backbone.extensions = _(Backbone.extensions || {}).extend({Decorator: Decorator});
|
45
|
+
})(_, Backbone);
|
@@ -0,0 +1,94 @@
|
|
1
|
+
(function(_, Backbone) {
|
2
|
+
'use strict';
|
3
|
+
function bindModelEvents(tuple) {
|
4
|
+
var self = this, subject = tuple[0], eventNames = tuple[1], modelEvents = self._modelEvents;
|
5
|
+
modelEvents.push(tuple);
|
6
|
+
|
7
|
+
_(subject && eventNames).each(function(callback, event) {
|
8
|
+
_(event.split(' ')).each(function(e) {
|
9
|
+
_([callback]).chain().flatten().each(function(c) {
|
10
|
+
if (_(c).isFunction()) {
|
11
|
+
subject.on(e, c, self);
|
12
|
+
} else {
|
13
|
+
subject.on(e, self[c], self);
|
14
|
+
}
|
15
|
+
});
|
16
|
+
});
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
function unbindModelEvents() {
|
21
|
+
var self = this, modelEvents = self._modelEvents;
|
22
|
+
_(modelEvents).each(function(tuple) {
|
23
|
+
var subject = tuple[0], events = tuple[1];
|
24
|
+
|
25
|
+
_(subject && events).each(function(callback, event) {
|
26
|
+
_(event.split(' ')).each(function(e) {
|
27
|
+
_([callback]).chain().flatten().each(function(c) {
|
28
|
+
if (_(c).isFunction()) {
|
29
|
+
subject.off(e, c);
|
30
|
+
} else {
|
31
|
+
subject.off(e, self[c]);
|
32
|
+
}
|
33
|
+
});
|
34
|
+
});
|
35
|
+
});
|
36
|
+
});
|
37
|
+
}
|
38
|
+
|
39
|
+
function wrapUndelegateEvents(callback) {
|
40
|
+
var oldUndelegateEvents = this.undelegateEvents;
|
41
|
+
this.undelegateEvents();
|
42
|
+
this.undelegateEvents = function() {};
|
43
|
+
callback.call(this);
|
44
|
+
this.undelegateEvents = oldUndelegateEvents;
|
45
|
+
}
|
46
|
+
|
47
|
+
var delegateEvents = {
|
48
|
+
included: function(source) {
|
49
|
+
_(source.prototype).extend({
|
50
|
+
initialize: _(source.prototype.initialize).wrap(function(oldInitialize, attrsOrModels, options) {
|
51
|
+
this._modelEvents = [];
|
52
|
+
oldInitialize.call(this, attrsOrModels, options);
|
53
|
+
}),
|
54
|
+
|
55
|
+
delegateEvents: _(source.prototype.delegateEvents).wrap(function(oldDelegateEvents) {
|
56
|
+
var self = this, args = _(arguments).rest(1);
|
57
|
+
|
58
|
+
if (!args.length) {
|
59
|
+
return oldDelegateEvents.call(this);
|
60
|
+
}
|
61
|
+
|
62
|
+
wrapUndelegateEvents.call(this, function() {
|
63
|
+
_(args).chain().toArray().compact().each(function(obj) {
|
64
|
+
var arg = _(obj);
|
65
|
+
if (arg.isArray()) {
|
66
|
+
bindModelEvents.call(self, obj);
|
67
|
+
} else {
|
68
|
+
arg.each(function(callbacks, event) {
|
69
|
+
_([callbacks]).chain().flatten().each(function(callback) {
|
70
|
+
oldDelegateEvents.call(self, _({}).tap(function(obj) {
|
71
|
+
obj[event] = callback;
|
72
|
+
}));
|
73
|
+
});
|
74
|
+
});
|
75
|
+
}
|
76
|
+
});
|
77
|
+
});
|
78
|
+
}),
|
79
|
+
|
80
|
+
undelegateEvents: _(source.prototype.undelegateEvents).wrap(function(oldUndelegateEvents) {
|
81
|
+
unbindModelEvents.call(this);
|
82
|
+
return oldUndelegateEvents.call(this);
|
83
|
+
}),
|
84
|
+
|
85
|
+
remove: _(source.prototype.remove).wrap(function(oldRemove) {
|
86
|
+
this.undelegateEvents();
|
87
|
+
return oldRemove.call(this);
|
88
|
+
})
|
89
|
+
});
|
90
|
+
}
|
91
|
+
};
|
92
|
+
|
93
|
+
Backbone.extensions = _(Backbone.extensions || {}).extend({delegateEvents: delegateEvents});
|
94
|
+
})(_, Backbone);
|
@@ -1,4 +1,5 @@
|
|
1
|
-
(function(Backbone) {
|
1
|
+
(function(_, Backbone) {
|
2
|
+
'use strict';
|
2
3
|
var include = {
|
3
4
|
include: function() {
|
4
5
|
var self = this;
|
@@ -11,5 +12,5 @@
|
|
11
12
|
}
|
12
13
|
};
|
13
14
|
|
14
|
-
Backbone.
|
15
|
-
})(Backbone);
|
15
|
+
Backbone.extensions = _(Backbone.extensions || {}).extend({include: include});
|
16
|
+
})(_, Backbone);
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backbone_extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ryan Dy
|
9
|
+
- Thomas Bukowski
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: fuubar
|
@@ -96,12 +97,15 @@ description: Adds extensions to the backbone javascript library. It adds the jav
|
|
96
97
|
underscore, backbone and the extensions you need.
|
97
98
|
email:
|
98
99
|
- ryan.dy@gmail.com
|
100
|
+
- me@neodude.net
|
99
101
|
executables: []
|
100
102
|
extensions: []
|
101
103
|
extra_rdoc_files: []
|
102
104
|
files:
|
103
105
|
- config/jshint.yml
|
106
|
+
- lib/assets/javascripts/backbone_extensions/associations.js
|
104
107
|
- lib/assets/javascripts/backbone_extensions/decorator.js
|
108
|
+
- lib/assets/javascripts/backbone_extensions/delegate_events.js
|
105
109
|
- lib/assets/javascripts/backbone_extensions/include.js
|
106
110
|
- lib/backbone_extensions/version.rb
|
107
111
|
- lib/backbone_extensions.rb
|
@@ -122,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
126
|
version: '0'
|
123
127
|
segments:
|
124
128
|
- 0
|
125
|
-
hash:
|
129
|
+
hash: 1650966973307354674
|
126
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
131
|
none: false
|
128
132
|
requirements:
|
@@ -131,10 +135,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
135
|
version: '0'
|
132
136
|
segments:
|
133
137
|
- 0
|
134
|
-
hash:
|
138
|
+
hash: 1650966973307354674
|
135
139
|
requirements: []
|
136
140
|
rubyforge_project:
|
137
|
-
rubygems_version: 1.8.
|
141
|
+
rubygems_version: 1.8.24
|
138
142
|
signing_key:
|
139
143
|
specification_version: 3
|
140
144
|
summary: Extensions to backbone javascript library as a rails engine
|