backbone_extensions 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|