First pass of monkey-patching nested model support. No tests yet, but
it passes jshint and build targets.
Showing
9 changed files
with
814 additions
and
0 deletions
.gitignore
0 → 100644
Gruntfile.js
0 → 100644
1 | // Generated on 2014-02-06 using generator-webapp 0.4.7 | ||
2 | /* global module */ | ||
3 | |||
4 | // # Globbing | ||
5 | // for performance reasons we're only matching one level down: | ||
6 | // 'test/spec/{,*/}*.js' | ||
7 | // use this if you want to recursively match all subfolders: | ||
8 | // 'test/spec/**/*.js' | ||
9 | |||
10 | module.exports = function (grunt) { | ||
11 | /* global require */ | ||
12 | 'use strict'; | ||
13 | |||
14 | var jasmineRequirejsTemplateOptions = function(withInstanbul) { | ||
15 | /* global requirejs */ | ||
16 | var callback; | ||
17 | if (withInstanbul) { | ||
18 | callback = function() { | ||
19 | var oldLoad = requirejs.load; | ||
20 | requirejs.load = function (context, moduleName, url) { | ||
21 | //console.log('context=' + JSON.stringify(arguments), 'moduleName=' + moduleName, 'url=' + url); | ||
22 | var parts = url.split('/'); | ||
23 | for (var i = 0; i < parts.length; ) { | ||
24 | var part = parts[i]; | ||
25 | if (part === '.') { | ||
26 | parts.splice(i, 1); | ||
27 | } else if (part === '') { | ||
28 | parts.splice(i, 1); | ||
29 | } else if (part === '..') { | ||
30 | if (i > 0) { | ||
31 | i--; | ||
32 | parts.splice(i, 2); | ||
33 | } else { | ||
34 | parts.splice(i, 1); | ||
35 | } | ||
36 | } else { | ||
37 | i++; | ||
38 | } | ||
39 | } | ||
40 | url = parts.join('/'); | ||
41 | if (url.indexOf('src/scripts/') === 0) { | ||
42 | url = './.grunt/grunt-contrib-jasmine/' + url; | ||
43 | } | ||
44 | //console.log('url=' + url); | ||
45 | return oldLoad.apply(this, [context, moduleName, url]); | ||
46 | }; | ||
47 | }; | ||
48 | } | ||
49 | return { | ||
50 | requireConfigFile: '<%= yeoman.src %>/scripts/config.js', | ||
51 | requireConfig: { | ||
52 | baseUrl: '<%= yeoman.src %>/scripts', | ||
53 | callback: callback | ||
54 | } | ||
55 | }; | ||
56 | }; | ||
57 | |||
58 | var jasmineInstanbulTemplateOptions = function(nestedTemplate, nestedOptions) { | ||
59 | return { | ||
60 | coverage: 'bin/coverage/coverage.json', | ||
61 | report: 'bin/coverage', | ||
62 | replace: false, | ||
63 | template: require(nestedTemplate), | ||
64 | templateOptions: nestedOptions | ||
65 | }; | ||
66 | }; | ||
67 | |||
68 | // Load grunt tasks automatically | ||
69 | require('load-grunt-tasks')(grunt); | ||
70 | |||
71 | // Time how long tasks take. Can help when optimizing build times | ||
72 | require('time-grunt')(grunt); | ||
73 | |||
74 | // Define the configuration for all the tasks | ||
75 | grunt.initConfig({ | ||
76 | bower: { | ||
77 | target: { | ||
78 | options: { | ||
79 | exclude: [ | ||
80 | 'requirejs', | ||
81 | ], | ||
82 | transitive: true, | ||
83 | }, | ||
84 | rjsConfig: '<%= yeoman.src %>/scripts/config.js' | ||
85 | } | ||
86 | }, | ||
87 | |||
88 | // Project settings | ||
89 | yeoman: { | ||
90 | // Configurable paths | ||
91 | app: 'app', | ||
92 | dist: 'dist', | ||
93 | src: 'src', | ||
94 | }, | ||
95 | |||
96 | // Watches files for changes and runs tasks based on the changed files | ||
97 | watch: { | ||
98 | js: { | ||
99 | files: ['<%= yeoman.src %>/scripts/{,*/}*.js'], | ||
100 | tasks: ['jshint'], | ||
101 | }, | ||
102 | jstest: { | ||
103 | files: ['test/spec/{,*/}*.js'], | ||
104 | tasks: ['test:watch'] | ||
105 | }, | ||
106 | gruntfile: { | ||
107 | files: ['Gruntfile.js'] | ||
108 | }, | ||
109 | styles: { | ||
110 | files: ['<%= yeoman.src %>/styles/{,*/}*.css'], | ||
111 | tasks: ['newer:copy:styles', 'autoprefixer'] | ||
112 | } | ||
113 | }, | ||
114 | |||
115 | // The actual grunt server settings | ||
116 | connect: { | ||
117 | options: { | ||
118 | port: 9000, | ||
119 | // Change this to '0.0.0.0' to access the server from outside | ||
120 | hostname: 'localhost' | ||
121 | }, | ||
122 | app: { | ||
123 | options: { | ||
124 | open: false, | ||
125 | base: [ | ||
126 | '.tmp', | ||
127 | '<%= yeoman.src %>' | ||
128 | ] | ||
129 | } | ||
130 | }, | ||
131 | test: { | ||
132 | options: { | ||
133 | port: 9001, | ||
134 | base: [ | ||
135 | '.tmp', | ||
136 | 'test', | ||
137 | '<%= yeoman.src %>' | ||
138 | ] | ||
139 | } | ||
140 | }, | ||
141 | dist: { | ||
142 | options: { | ||
143 | open: false, | ||
144 | base: '<%= yeoman.dist %>', | ||
145 | } | ||
146 | } | ||
147 | }, | ||
148 | |||
149 | // Empties folders to start fresh | ||
150 | clean: { | ||
151 | dist: { | ||
152 | files: [{ | ||
153 | dot: true, | ||
154 | src: [ | ||
155 | '.tmp', | ||
156 | '<%= yeoman.dist %>/*', | ||
157 | '!<%= yeoman.dist %>/.git*' | ||
158 | ] | ||
159 | }] | ||
160 | }, | ||
161 | server: '.tmp' | ||
162 | }, | ||
163 | |||
164 | // Make sure code styles are up to par and there are no obvious mistakes | ||
165 | jshint: { | ||
166 | options: { | ||
167 | browser: true, | ||
168 | esnext: true, | ||
169 | bitwise: true, | ||
170 | camelcase: true, | ||
171 | curly: true, | ||
172 | eqeqeq: true, | ||
173 | immed: true, | ||
174 | indent: 4, | ||
175 | latedef: true, | ||
176 | newcap: true, | ||
177 | noarg: true, | ||
178 | quotmark: 'single', | ||
179 | undef: true, | ||
180 | unused: true, | ||
181 | strict: true, | ||
182 | trailing: true, | ||
183 | smarttabs: true, | ||
184 | jquery: true, | ||
185 | reporter: require('jshint-stylish') | ||
186 | }, | ||
187 | all: [ | ||
188 | 'Gruntfile.js', | ||
189 | ], | ||
190 | scripts: { | ||
191 | options: { | ||
192 | globals: { | ||
193 | define: false, | ||
194 | } | ||
195 | }, | ||
196 | files: { | ||
197 | src: [ | ||
198 | '<%= yeoman.src %>/scripts/**/*.js', | ||
199 | '!<%= yeoman.src %>/scripts/vendor/*', | ||
200 | ] | ||
201 | } | ||
202 | }, | ||
203 | specs: { | ||
204 | options: { | ||
205 | globals: { | ||
206 | define: false, | ||
207 | describe: false, | ||
208 | expect: false, | ||
209 | it: false, | ||
210 | } | ||
211 | }, | ||
212 | files: { | ||
213 | src: [ | ||
214 | 'test/specs/**/*.spec.js' | ||
215 | ] | ||
216 | } | ||
217 | } | ||
218 | }, | ||
219 | |||
220 | jasmine: { | ||
221 | all: { | ||
222 | src: '<%= yeoman.src %>/scripts/{,**/}*.js', | ||
223 | options: { | ||
224 | specs: 'test/specs/**/*.spec.js', | ||
225 | template: require('grunt-template-jasmine-istanbul'), | ||
226 | templateOptions: jasmineInstanbulTemplateOptions('grunt-template-jasmine-requirejs', jasmineRequirejsTemplateOptions(true)) | ||
227 | } | ||
228 | } | ||
229 | }, | ||
230 | |||
231 | // Mocha testing framework configuration options | ||
232 | mocha: { | ||
233 | all: { | ||
234 | options: { | ||
235 | run: true, | ||
236 | urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html'] | ||
237 | } | ||
238 | } | ||
239 | }, | ||
240 | |||
241 | // Add vendor prefixed styles | ||
242 | autoprefixer: { | ||
243 | options: { | ||
244 | browsers: ['last 1 version'] | ||
245 | }, | ||
246 | dist: { | ||
247 | files: [{ | ||
248 | expand: true, | ||
249 | cwd: '.tmp/styles/', | ||
250 | src: '{,*/}*.css', | ||
251 | dest: '.tmp/styles/' | ||
252 | }] | ||
253 | } | ||
254 | }, | ||
255 | |||
256 | // Automatically inject Bower components into the HTML file | ||
257 | 'bower-install': { | ||
258 | app: { | ||
259 | html: '<%= yeoman.src %>/index.html', | ||
260 | ignorePath: '<%= yeoman.src %>/' | ||
261 | } | ||
262 | }, | ||
263 | |||
264 | // Renames files for browser caching purposes | ||
265 | rev: { | ||
266 | dist: { | ||
267 | files: { | ||
268 | src: [ | ||
269 | '<%= yeoman.dist %>/scripts/*/**/*.js', | ||
270 | '<%= yeoman.dist %>/scripts/!(config)*.js', | ||
271 | '<%= yeoman.dist %>/styles/{,*/}*.css', | ||
272 | '<%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}', | ||
273 | '<%= yeoman.dist %>/styles/fonts/{,*/}*.*' | ||
274 | ] | ||
275 | } | ||
276 | }, | ||
277 | requireconfig: { | ||
278 | files: { | ||
279 | src: [ | ||
280 | '<%= yeoman.dist %>/scripts/config.js' | ||
281 | ] | ||
282 | } | ||
283 | } | ||
284 | }, | ||
285 | |||
286 | requirejs: { | ||
287 | dist: { | ||
288 | options: { | ||
289 | done: function(done) { | ||
290 | var requireModules = grunt.config('requireModules') || {}; | ||
291 | var lines = [ | ||
292 | 'require.bundles = (function(bundles) {', | ||
293 | ]; | ||
294 | for (var key in requireModules) { | ||
295 | var keyS = JSON.stringify(key); | ||
296 | var value = requireModules[key]; | ||
297 | var included = []; | ||
298 | for (var i = 0; i < value.included.length; i++) { | ||
299 | var file = value.included[i]; | ||
300 | if (file.match(/\.js$/)) { | ||
301 | included.push(file.substring(0, file.length - 3)); | ||
302 | } | ||
303 | } | ||
304 | lines.push('bundles[' + keyS + '] = ' + JSON.stringify(included) + ';'); | ||
305 | } | ||
306 | lines.push('return bundles;'); | ||
307 | lines.push('})(require.bundles || {});'); | ||
308 | grunt.file.write('.tmp/scripts/bundles.js', lines.join('\n')); | ||
309 | done(); | ||
310 | }, | ||
311 | baseUrl: '<%= yeoman.src %>/scripts', | ||
312 | mainConfigFile: '<%= yeoman.src %>/scripts/config.js', | ||
313 | wrapShim: true, | ||
314 | dir: '<%= yeoman.dist %>/scripts', | ||
315 | optimize: 'none', | ||
316 | removeCombined: true, | ||
317 | onModuleBundleComplete: function(data) { | ||
318 | if (data.name.slice(0, 'bundles/'.length) === 'bundles/') { | ||
319 | var requireModules = grunt.config('requireModules') || {}; | ||
320 | requireModules[data.name] = data; | ||
321 | grunt.config('requireModules', requireModules); | ||
322 | } | ||
323 | }, | ||
324 | } | ||
325 | }, | ||
326 | }, | ||
327 | |||
328 | // Reads HTML for usemin blocks to enable smart builds that automatically | ||
329 | // concat, minify and revision files. Creates configurations in memory so | ||
330 | // additional tasks can operate on them | ||
331 | useminPrepare: { | ||
332 | options: { | ||
333 | dest: '<%= yeoman.dist %>' | ||
334 | }, | ||
335 | html: '<%= yeoman.src %>/index.html' | ||
336 | }, | ||
337 | |||
338 | // Performs rewrites based on rev and the useminPrepare configuration | ||
339 | usemin: { | ||
340 | options: { | ||
341 | assetsDirs: ['<%= yeoman.dist %>'] | ||
342 | }, | ||
343 | html: ['<%= yeoman.dist %>/{,*/}*.html'], | ||
344 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'] | ||
345 | }, | ||
346 | |||
347 | // The following *-min tasks produce minified files in the dist folder | ||
348 | imagemin: { | ||
349 | dist: { | ||
350 | files: [{ | ||
351 | expand: true, | ||
352 | cwd: '<%= yeoman.src %>/images', | ||
353 | src: '{,*/}*.{gif,jpeg,jpg,png}', | ||
354 | dest: '<%= yeoman.dist %>/images' | ||
355 | }] | ||
356 | } | ||
357 | }, | ||
358 | svgmin: { | ||
359 | dist: { | ||
360 | files: [{ | ||
361 | expand: true, | ||
362 | cwd: '<%= yeoman.src %>/images', | ||
363 | src: '{,*/}*.svg', | ||
364 | dest: '<%= yeoman.dist %>/images' | ||
365 | }] | ||
366 | } | ||
367 | }, | ||
368 | htmlmin: { | ||
369 | dist: { | ||
370 | options: { | ||
371 | collapseBooleanAttributes: true, | ||
372 | collapseWhitespace: true, | ||
373 | removeAttributeQuotes: true, | ||
374 | removeCommentsFromCDATA: true, | ||
375 | removeEmptyAttributes: true, | ||
376 | removeOptionalTags: true, | ||
377 | removeRedundantAttributes: true, | ||
378 | useShortDoctype: true | ||
379 | }, | ||
380 | files: [{ | ||
381 | expand: true, | ||
382 | cwd: '<%= yeoman.dist %>', | ||
383 | src: '{,*/}*.html', | ||
384 | dest: '<%= yeoman.dist %>' | ||
385 | }] | ||
386 | } | ||
387 | }, | ||
388 | |||
389 | // By default, your `index.html`'s <!-- Usemin block --> will take care of | ||
390 | // minification. These next options are pre-configured if you do not wish | ||
391 | // to use the Usemin blocks. | ||
392 | // cssmin: { | ||
393 | // dist: { | ||
394 | // files: { | ||
395 | // '<%= yeoman.dist %>/styles/main.css': [ | ||
396 | // '.tmp/styles/{,*/}*.css', | ||
397 | // '<%= yeoman.src %>/styles/{,*/}*.css' | ||
398 | // ] | ||
399 | // } | ||
400 | // } | ||
401 | // }, | ||
402 | // uglify: { | ||
403 | // dist: { | ||
404 | // files: { | ||
405 | // '<%= yeoman.dist %>/scripts/scripts.js': [ | ||
406 | // '<%= yeoman.dist %>/scripts/scripts.js' | ||
407 | // ] | ||
408 | // } | ||
409 | // } | ||
410 | // }, | ||
411 | // concat: { | ||
412 | // dist: {} | ||
413 | // }, | ||
414 | |||
415 | concat: { | ||
416 | requireconfig: { | ||
417 | } | ||
418 | }, | ||
419 | |||
420 | uglify: { | ||
421 | dist: { | ||
422 | }, | ||
423 | requireconfig: { | ||
424 | files: { | ||
425 | '<%= yeoman.dist %>/scripts/config.js': [ | ||
426 | '<%= yeoman.dist %>/scripts/config.js', | ||
427 | '.tmp/scripts/config.js', | ||
428 | ], | ||
429 | } | ||
430 | } | ||
431 | }, | ||
432 | |||
433 | // Copies remaining files to places other tasks can use | ||
434 | copy: { | ||
435 | dist: { | ||
436 | files: [{ | ||
437 | expand: true, | ||
438 | dot: true, | ||
439 | cwd: '<%= yeoman.src %>', | ||
440 | dest: '<%= yeoman.dist %>', | ||
441 | src: [ | ||
442 | '*.{ico,png,txt}', | ||
443 | '.htaccess', | ||
444 | 'images/{,*/}*.webp', | ||
445 | '{,*/}*.html', | ||
446 | 'styles/fonts/{,*/}*.*' | ||
447 | ] | ||
448 | }] | ||
449 | }, | ||
450 | styles: { | ||
451 | expand: true, | ||
452 | dot: true, | ||
453 | cwd: '<%= yeoman.src %>/styles', | ||
454 | dest: '.tmp/styles/', | ||
455 | src: '{,*/}*.css' | ||
456 | } | ||
457 | }, | ||
458 | |||
459 | |||
460 | // Run some tasks in parallel to speed up build process | ||
461 | concurrent: { | ||
462 | server: [ | ||
463 | 'copy:styles' | ||
464 | ], | ||
465 | test: [ | ||
466 | 'copy:styles' | ||
467 | ], | ||
468 | dist: [ | ||
469 | 'copy:styles', | ||
470 | 'imagemin', | ||
471 | 'svgmin' | ||
472 | ] | ||
473 | } | ||
474 | }); | ||
475 | |||
476 | grunt.loadNpmTasks('grunt-bower-requirejs'); | ||
477 | |||
478 | grunt.registerTask('revconfig', function () { | ||
479 | var prefix = grunt.template.process('<%= yeoman.dist %>/scripts/'); | ||
480 | var pattern = prefix + '**/*.{js,html}'; | ||
481 | var files = grunt.file.expand(pattern); | ||
482 | var lines = []; | ||
483 | grunt.util._.each(files, function(file) { | ||
484 | file = file.substring(prefix.length); | ||
485 | var res = file.match(/^(.*\/)?([0-9a-f]+)\.([^\/]+)\.([^\.]+)$/); | ||
486 | if (!res) { | ||
487 | return; | ||
488 | } | ||
489 | //grunt.log.oklns(JSON.stringify(res)); | ||
490 | var dir = res[1] || ''; | ||
491 | //var hash = res[2]; | ||
492 | var base = res[3]; | ||
493 | var ext = res[4]; | ||
494 | var id; | ||
495 | if (ext === 'js') { | ||
496 | id = dir + base; | ||
497 | file = file.substring(0, file.length - ext.length - 1); | ||
498 | } else if (ext === 'html') { | ||
499 | id = 'text!' + dir + base + '.' + ext; | ||
500 | } | ||
501 | grunt.log.oklns('map: ' + id + ' -> ' + file); | ||
502 | lines.push('require.paths[' + JSON.stringify(id) + ']=' + JSON.stringify(file) + ';\n'); | ||
503 | }); | ||
504 | grunt.file.write('.tmp/scripts/config.js', lines.join('')); | ||
505 | }); | ||
506 | |||
507 | grunt.registerTask('serve', function (target) { | ||
508 | if (target === 'dist') { | ||
509 | return grunt.task.run(['build', 'connect:dist:keepalive']); | ||
510 | } | ||
511 | |||
512 | grunt.task.run([ | ||
513 | 'clean:server', | ||
514 | 'concurrent:server', | ||
515 | 'autoprefixer', | ||
516 | 'connect:app', | ||
517 | 'watch' | ||
518 | ]); | ||
519 | }); | ||
520 | |||
521 | grunt.registerTask('server', function () { | ||
522 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); | ||
523 | grunt.task.run(['serve']); | ||
524 | }); | ||
525 | |||
526 | grunt.registerTask('test', function(target) { | ||
527 | if (target !== 'watch') { | ||
528 | grunt.task.run([ | ||
529 | 'clean:server', | ||
530 | 'concurrent:test', | ||
531 | 'autoprefixer', | ||
532 | ]); | ||
533 | } | ||
534 | |||
535 | grunt.task.run([ | ||
536 | 'connect:test', | ||
537 | 'mocha' | ||
538 | ]); | ||
539 | }); | ||
540 | |||
541 | grunt.registerTask('build', [ | ||
542 | 'clean:dist', | ||
543 | 'useminPrepare', | ||
544 | 'requirejs', | ||
545 | 'concurrent:dist', | ||
546 | 'autoprefixer', | ||
547 | 'concat', | ||
548 | // 'cssmin', | ||
549 | 'uglify:dist', | ||
550 | 'copy:dist', | ||
551 | // 'rev:dist', | ||
552 | 'revconfig', | ||
553 | 'uglify:requireconfig', | ||
554 | // 'rev:requireconfig', | ||
555 | 'usemin', | ||
556 | 'htmlmin' | ||
557 | ]); | ||
558 | |||
559 | grunt.registerTask('dist', [ | ||
560 | 'bower', | ||
561 | 'newer:jshint', | ||
562 | // 'test', | ||
563 | 'build' | ||
564 | ]); | ||
565 | grunt.registerTask('default', []); | ||
566 | }; |
bower.json
0 → 100644
1 | { | ||
2 | "name": "backbone-nested-models", | ||
3 | "version": "0.0.0", | ||
4 | "authors": [ | ||
5 | "Adam Heath <doogie@brainfood.com>" | ||
6 | ], | ||
7 | "private": true, | ||
8 | "ignore": [ | ||
9 | "**/.*", | ||
10 | "node_modules", | ||
11 | "src/lib", | ||
12 | "test" | ||
13 | ], | ||
14 | "dependencies": { | ||
15 | "underscore": "~1.6.0", | ||
16 | "backbone": "~1.1.0", | ||
17 | "backbone-validation": "0.9.1", | ||
18 | "requirejs": "~2.1.10" | ||
19 | } | ||
20 | } |
package.json
0 → 100644
1 | { | ||
2 | "name": "backbone-nested-models", | ||
3 | "version": "0.0.0", | ||
4 | "main": [ | ||
5 | "src/scripts/backbone-nested-models.js" | ||
6 | ], | ||
7 | "dependencies": { | ||
8 | "backbone": "~1.1.0", | ||
9 | "backbone-validation": "0.9.1", | ||
10 | "requirejs": "~2.1.10" | ||
11 | }, | ||
12 | "devDependencies": { | ||
13 | "bower-requirejs": "~0.9.2", | ||
14 | "grunt": "~0.4.1", | ||
15 | "grunt-contrib-copy": "~0.4.1", | ||
16 | "grunt-contrib-concat": "~0.3.0", | ||
17 | "grunt-contrib-uglify": "~0.2.0", | ||
18 | "grunt-contrib-jshint": "~0.7.0", | ||
19 | "grunt-contrib-cssmin": "~0.7.0", | ||
20 | "grunt-contrib-connect": "~0.5.0", | ||
21 | "grunt-contrib-clean": "~0.5.0", | ||
22 | "grunt-contrib-htmlmin": "~0.1.3", | ||
23 | "grunt-bower-install": "~0.7.0", | ||
24 | "grunt-contrib-imagemin": "~0.2.0", | ||
25 | "grunt-contrib-watch": "~0.5.2", | ||
26 | "grunt-rev": "~0.1.0", | ||
27 | "grunt-autoprefixer": "~0.5.0", | ||
28 | "grunt-usemin": "~0.1.10", | ||
29 | "grunt-mocha": "~0.4.0", | ||
30 | "grunt-newer": "~0.6.0", | ||
31 | "grunt-svgmin": "~0.2.0", | ||
32 | "grunt-concurrent": "~0.4.0", | ||
33 | "load-grunt-tasks": "~0.2.0", | ||
34 | "time-grunt": "~0.2.0", | ||
35 | "jshint-stylish": "~0.1.3", | ||
36 | "grunt-contrib-requirejs": "~0.4.0", | ||
37 | "grunt-bower-requirejs": "~0.8.4", | ||
38 | "grunt-template-jasmine-istanbul": "~0.2.6", | ||
39 | "grunt-template-jasmine-requirejs": "~0.1.10", | ||
40 | "grunt-contrib-jasmine": "~0.5.3" | ||
41 | }, | ||
42 | "engines": { | ||
43 | "node": ">=0.8.0" | ||
44 | } | ||
45 | } | ||
46 |
src/scripts/backbone-nested-models.js
0 → 100644
1 | define( | ||
2 | [ | ||
3 | 'underscore', | ||
4 | 'backbone', | ||
5 | 'backbone-validation', | ||
6 | ], | ||
7 | function( | ||
8 | _, | ||
9 | Backbone | ||
10 | ) { | ||
11 | 'use strict'; | ||
12 | |||
13 | function validateNestedValue(attrValue, attrName) { | ||
14 | attrValue.validate(); | ||
15 | var isValid = attrValue.isValid(); | ||
16 | return isValid ? null : (attrName + ' is invalid'); | ||
17 | } | ||
18 | |||
19 | function updateValidation(model) { | ||
20 | var oldValidation = model.validation; | ||
21 | var allKeys = _.uniq(model.keys().concat(_.keys(oldValidation))); | ||
22 | var validation = _.extend({}, oldValidation); | ||
23 | var found; | ||
24 | var f = function(value) { | ||
25 | if (value === validateNestedValue) { | ||
26 | found = true; | ||
27 | } | ||
28 | }; | ||
29 | for (var i = 0; i < allKeys.length; i++) { | ||
30 | var key = allKeys[i]; | ||
31 | var value = model.get(key); | ||
32 | var validators = validation[key]; | ||
33 | if (validators) { | ||
34 | if(_.isArray(validators)) { | ||
35 | validators = validators.concat(); | ||
36 | } else { | ||
37 | validators = [validators]; | ||
38 | } | ||
39 | } else { | ||
40 | validators = []; | ||
41 | } | ||
42 | validation[key] = validators; | ||
43 | if (value instanceof Backbone.Model) { | ||
44 | found = false; | ||
45 | _.each(validators, f); | ||
46 | if (!found) { | ||
47 | validators.push(validateNestedValue); | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | model.validation = validation; | ||
52 | return oldValidation; | ||
53 | } | ||
54 | |||
55 | function wrapValidationFunction(modelClass, methodName) { | ||
56 | var originalMethod = modelClass.prototype[methodName]; | ||
57 | modelClass.prototype[methodName] = function() { | ||
58 | var oldValidation = updateValidation(this); | ||
59 | try { | ||
60 | if (originalMethod) { | ||
61 | return originalMethod.apply(this, arguments); | ||
62 | } else { | ||
63 | return modelClass.__super__[methodName].apply(this, arguments); | ||
64 | } | ||
65 | } finally { | ||
66 | this.validation = oldValidation; | ||
67 | } | ||
68 | }; | ||
69 | } | ||
70 | |||
71 | function wrapSetFunction(modelClass) { | ||
72 | var originalMethod = modelClass.prototype.set; | ||
73 | modelClass.prototype.set = function(key, val, options) { | ||
74 | var attr, attrs, curVal, nestedOptions, newVal; | ||
75 | if (key === null) { | ||
76 | return this; | ||
77 | } | ||
78 | |||
79 | if (typeof key === 'object') { | ||
80 | attrs = key; | ||
81 | options = val; | ||
82 | } else { | ||
83 | (attrs = {})[key] = val; | ||
84 | } | ||
85 | if (options && options.merge) { | ||
86 | nestedOptions = {silent: false, merge: true}; | ||
87 | for (attr in attrs) { | ||
88 | curVal = this.get(attr); | ||
89 | newVal = attrs[attr]; | ||
90 | if (curVal instanceof Backbone.Model && newVal instanceof Backbone.Model) { | ||
91 | delete attrs[attr]; | ||
92 | curVal.set(newVal.attributes, nestedOptions); | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | if (originalMethod) { | ||
97 | return originalMethod.call(this, attrs, options); | ||
98 | } else { | ||
99 | return modelClass.__super__.set.call(this, attrs, options); | ||
100 | } | ||
101 | }; | ||
102 | } | ||
103 | |||
104 | function wrapToJSONFunction(modelClass) { | ||
105 | var originalMethod = modelClass.prototype.toJSON; | ||
106 | modelClass.prototype.toJSON = function(options) { | ||
107 | var result; | ||
108 | if (originalMethod) { | ||
109 | result = originalMethod.apply(this, arguments); | ||
110 | } else { | ||
111 | result = modelClass.__super__.toJSON.apply(this, arguments); | ||
112 | } | ||
113 | if (options && options.deep) { | ||
114 | _.each(result, function(value, key) { | ||
115 | if (value instanceof Backbone.Model) { | ||
116 | result[key] = value.toJSON(options); | ||
117 | } | ||
118 | }); | ||
119 | } | ||
120 | return result; | ||
121 | }; | ||
122 | } | ||
123 | |||
124 | var NestedModels = { | ||
125 | validateNestedValue: validateNestedValue, | ||
126 | |||
127 | wrapSetFunction: wrapSetFunction, | ||
128 | wrapToJSONFunction: wrapToJSONFunction, | ||
129 | wrapValidationFunction: wrapValidationFunction, | ||
130 | |||
131 | mixin: function(modelClass) { | ||
132 | wrapSetFunction(modelClass); | ||
133 | wrapToJSONFunction(modelClass); | ||
134 | wrapValidationFunction(modelClass, 'isValid'); | ||
135 | wrapValidationFunction(modelClass, 'validate'); | ||
136 | wrapValidationFunction(modelClass, 'preValidate'); | ||
137 | return modelClass; | ||
138 | }, | ||
139 | }; | ||
140 | |||
141 | return NestedModels; | ||
142 | |||
143 | } | ||
144 | ); |
src/scripts/config.js
0 → 100644
1 | /* global require:true */ | ||
2 | var require; | ||
3 | require = (function() { | ||
4 | 'use strict'; | ||
5 | |||
6 | var require = { | ||
7 | baseUrl: 'scripts', | ||
8 | shim: { | ||
9 | |||
10 | }, | ||
11 | paths: { | ||
12 | 'backbone-validation': '../lib/backbone-validation/dist/backbone-validation-amd', | ||
13 | backbone: '../lib/backbone/backbone', | ||
14 | underscore: '../lib/underscore/underscore' | ||
15 | } | ||
16 | }; | ||
17 | |||
18 | return require; | ||
19 | })(); |
src/scripts/jquery.js
0 → 100644
1 | define([], {}); |
-
Please register or sign in to post a comment