From: Zachary Vance Date: Fri, 20 Mar 2015 01:59:48 +0000 (-0700) Subject: Split js files apart X-Git-Url: https://git.za3k.com/?a=commitdiff_plain;h=ef1a8adf9002739d0eafb6e91a4417800e822fa6;p=flowy.git Split js files apart --- diff --git a/Gruntfile.js b/Gruntfile.js index da26c6d..b4cc935 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,4 +1,6 @@ + module.exports = function(grunt) { + path = require("path"); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), @@ -24,16 +26,41 @@ module.exports = function(grunt) { } }, concat: { - options: { - separator: ';' - }, - dist: { - src: ['src/**/*.js'], - dest: 'dist/<%= pkg.name %>.js' - }, css: { src: ['src/**/*.css'], dest: 'dist/<%= pkg.name %>.css' + }, + js: { // Wrap subtask + options: { + banner: "$(function(){\n", + footer: "\n});\n", + }, + src: 'dist/<%= pkg.name %>.unwrapped.js', + dest: 'dist/<%= pkg.name %>.js', + } + }, + concat_in_order: { + options: { + separator: ';', + extractRequired: function(filepath, filecontent) { + var workingdir = path.normalize(filepath).split(path.sep); + workingdir.pop(); + + var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent); + deps.forEach(function(dep, i) { + var dependency = workingdir.concat([dep]); + deps[i] = path.join.apply(null, dependency); + }); + return deps; + }, + extractDeclared: function(filepath) { + return [filepath]; + }, + onlyConcatRequiredFiles: true + }, + dist: { + src: ['src/flowy.js'], + dest: 'dist/<%= pkg.name %>.unwrapped.js' } }, copy: { @@ -48,8 +75,9 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-bower-concat'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-concat-in-order'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.registerTask('default', ['jshint', 'bower_concat', 'concat', 'copy']); + grunt.registerTask('default', ['jshint', 'bower_concat', 'concat_in_order', 'concat', 'copy']); }; diff --git a/dist/flowy.js b/dist/flowy.js index bf6b3a3..697df19 100644 --- a/dist/flowy.js +++ b/dist/flowy.js @@ -1,4 +1,35 @@ -$(function() { +$(function(){ +var TodoView = Backbone.View.extend({ + tagName: 'div', + className: 'todo', + events: { + "click > .text": "toggleComplete", + }, + initialize: function() { + this.listenTo(this.model, "change", this.render); + this.listenTo(this.model, 'destroy', this.remove); + }, + toggleComplete: function(e) { + this.model.toggleComplete(); + e.stopPropagation(); + }, + template: "
{{text}}
", + addChild: function(el, position) { + if(typeof position === 'undefined') { + console.log("TodoView:addChild called without a position"); + } + this.$el.find("> .bullets").append(el); + return this; + }, + render: function() { + var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers + this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes + this.$el.toggleClass('completed', this.model.get('completed')); + this.$el.toggleClass('collapsed', this.model.get('collapsed')); + this.$el.find("> .bullets").append(oldChildren); + return this; + }, +}); var TodoModel = Backbone.Model.extend({ defaults: function() { @@ -35,6 +66,10 @@ var TodoModel = Backbone.Model.extend({ }, }); +/** + * @depend ../models/todo.js + */ + flowyDocDefaults = { rootId: null }; var FlowyDocModel = Backbone.Collection.extend({ initialize: function(options) { @@ -66,6 +101,12 @@ var FlowyDocModel = Backbone.Collection.extend({ comparator: 'order', }); +/** + * @depend ../views/todo.js + * @depend ../models/flowyDoc.js + * @depend ../models/todo.js + */ + var todos = new FlowyDocModel({ id: "master", default: [ @@ -113,38 +154,6 @@ var todos = new FlowyDocModel({ ], }); -var TodoView = Backbone.View.extend({ - tagName: 'div', - className: 'todo', - events: { - "click > .text": "toggleComplete", - }, - initialize: function() { - this.listenTo(this.model, "change", this.render); - this.listenTo(this.model, 'destroy', this.remove); - }, - toggleComplete: function(e) { - this.model.toggleComplete(); - e.stopPropagation(); - }, - template: "
{{text}}
", - addChild: function(el, position) { - if(typeof position === 'undefined') { - console.log("TodoView:addChild called without a position"); - } - this.$el.find("> .bullets").append(el); - return this; - }, - render: function() { - var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers - this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes - this.$el.toggleClass('completed', this.model.get('completed')); - this.$el.toggleClass('collapsed', this.model.get('collapsed')); - this.$el.find("> .bullets").append(oldChildren); - return this; - }, -}); - var appDefaults = { list: todos }; var AppView = Backbone.View.extend({ el: $("#todo-app"), @@ -196,6 +205,12 @@ var AppView = Backbone.View.extend({ }, }); -var flowyView = new AppView(); +/** + * @depend views/app.js + */ + +$(function() { + var flowyView = new AppView(); +}); }); diff --git a/src/models/test.js b/dist/flowy.unwrapped.js similarity index 96% rename from src/models/test.js rename to dist/flowy.unwrapped.js index bf6b3a3..75d8002 100644 --- a/src/models/test.js +++ b/dist/flowy.unwrapped.js @@ -1,4 +1,34 @@ -$(function() { +var TodoView = Backbone.View.extend({ + tagName: 'div', + className: 'todo', + events: { + "click > .text": "toggleComplete", + }, + initialize: function() { + this.listenTo(this.model, "change", this.render); + this.listenTo(this.model, 'destroy', this.remove); + }, + toggleComplete: function(e) { + this.model.toggleComplete(); + e.stopPropagation(); + }, + template: "
{{text}}
", + addChild: function(el, position) { + if(typeof position === 'undefined') { + console.log("TodoView:addChild called without a position"); + } + this.$el.find("> .bullets").append(el); + return this; + }, + render: function() { + var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers + this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes + this.$el.toggleClass('completed', this.model.get('completed')); + this.$el.toggleClass('collapsed', this.model.get('collapsed')); + this.$el.find("> .bullets").append(oldChildren); + return this; + }, +}); var TodoModel = Backbone.Model.extend({ defaults: function() { @@ -35,6 +65,10 @@ var TodoModel = Backbone.Model.extend({ }, }); +/** + * @depend ../models/todo.js + */ + flowyDocDefaults = { rootId: null }; var FlowyDocModel = Backbone.Collection.extend({ initialize: function(options) { @@ -66,6 +100,12 @@ var FlowyDocModel = Backbone.Collection.extend({ comparator: 'order', }); +/** + * @depend ../views/todo.js + * @depend ../models/flowyDoc.js + * @depend ../models/todo.js + */ + var todos = new FlowyDocModel({ id: "master", default: [ @@ -113,38 +153,6 @@ var todos = new FlowyDocModel({ ], }); -var TodoView = Backbone.View.extend({ - tagName: 'div', - className: 'todo', - events: { - "click > .text": "toggleComplete", - }, - initialize: function() { - this.listenTo(this.model, "change", this.render); - this.listenTo(this.model, 'destroy', this.remove); - }, - toggleComplete: function(e) { - this.model.toggleComplete(); - e.stopPropagation(); - }, - template: "
{{text}}
", - addChild: function(el, position) { - if(typeof position === 'undefined') { - console.log("TodoView:addChild called without a position"); - } - this.$el.find("> .bullets").append(el); - return this; - }, - render: function() { - var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers - this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes - this.$el.toggleClass('completed', this.model.get('completed')); - this.$el.toggleClass('collapsed', this.model.get('collapsed')); - this.$el.find("> .bullets").append(oldChildren); - return this; - }, -}); - var appDefaults = { list: todos }; var AppView = Backbone.View.extend({ el: $("#todo-app"), @@ -196,6 +204,10 @@ var AppView = Backbone.View.extend({ }, }); -var flowyView = new AppView(); +/** + * @depend views/app.js + */ +$(function() { + var flowyView = new AppView(); }); diff --git a/node_modules/grunt-concat-in-order/.jshintrc b/node_modules/grunt-concat-in-order/.jshintrc new file mode 100644 index 0000000..948d55e --- /dev/null +++ b/node_modules/grunt-concat-in-order/.jshintrc @@ -0,0 +1,13 @@ +{ + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "sub": true, + "undef": true, + "boss": true, + "eqnull": true, + "node": true +} diff --git a/node_modules/grunt-concat-in-order/.npmignore b/node_modules/grunt-concat-in-order/.npmignore new file mode 100644 index 0000000..7903979 --- /dev/null +++ b/node_modules/grunt-concat-in-order/.npmignore @@ -0,0 +1,4 @@ +node_modules +npm-debug.log +tmp +.idea diff --git a/node_modules/grunt-concat-in-order/Gruntfile.js b/node_modules/grunt-concat-in-order/Gruntfile.js new file mode 100644 index 0000000..af9693f --- /dev/null +++ b/node_modules/grunt-concat-in-order/Gruntfile.js @@ -0,0 +1,95 @@ +/* + * grunt-concat-in-order + * https://github.com/miensol/grunt-concat-in-order + * + * Copyright (c) 2013 Piotr Mionskowski + * Licensed under the MIT license. + */ + +'use strict'; + +module.exports = function(grunt) { + var path = require('path'); + + // Project configuration. + grunt.initConfig({ + jshint: { + all: [ + 'Gruntfile.js', + 'tasks/*.js', + '<%= nodeunit.tests %>' + ], + options: { + jshintrc: '.jshintrc' + } + }, + + // Before generating any new files, remove any previously-created files. + clean: { + tests: ['tmp'] + }, + + // Configuration to be run (and then tested). + concat_in_order: { + default_options: { + files: { + 'tmp/default_options.js': ['test/default/**/*.js'] + } + }, + cycle_options: { + files: { + 'tmp/cycle_options.js': ['test/cycle/**/*.js'] + } + }, + missing_options: { + files: { + 'tmp/missing_options.js': ['test/missing/**/*.js'] + } + }, + filebased_options: { + files: { + 'tmp/filebased_options.js': ['test/filebased/AUsingBaseBAndBaseA.js'] + }, + options: { + extractRequired: function(filepath, filecontent) { + var workingdir = path.normalize(filepath).split(path.sep); + workingdir.pop(); + + var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent); + deps.forEach(function(dep, i) { + var dependency = workingdir.concat([dep]); + deps[i] = path.join.apply(null, dependency); + }); + return deps; + }, + extractDeclared: function(filepath) { + return [filepath]; + }, + onlyConcatRequiredFiles: true + } + } + }, + + // Unit tests. + nodeunit: { + tests: ['test/*_test.js'] + } + + }); + + // Actually load this plugin's task(s). + grunt.loadTasks('tasks'); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-nodeunit'); + + // Whenever the "test" task is run, first clean the "tmp" dir, then run this + // plugin's task(s), then test the result. + grunt.registerTask('test', ['clean', 'concat_in_order:default_options', 'concat_in_order:filebased_options', 'nodeunit']); + + // By default, lint and run all tests. + grunt.registerTask('default', ['jshint', 'test']); + +}; diff --git a/node_modules/grunt-concat-in-order/LICENSE-MIT b/node_modules/grunt-concat-in-order/LICENSE-MIT new file mode 100644 index 0000000..42224a2 --- /dev/null +++ b/node_modules/grunt-concat-in-order/LICENSE-MIT @@ -0,0 +1,22 @@ +Copyright (c) 2013 Piotr Mionskowski + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/grunt-concat-in-order/README.md b/node_modules/grunt-concat-in-order/README.md new file mode 100644 index 0000000..2917914 --- /dev/null +++ b/node_modules/grunt-concat-in-order/README.md @@ -0,0 +1,168 @@ +# grunt-concat-in-order + +> Concatenates files respecting dependencies. + +## Getting Started +This plugin requires Grunt `~0.4.1` + +If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: + +```shell +npm install grunt-concat-in-order --save-dev +``` + +Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: + +```js +grunt.loadNpmTasks('grunt-concat-in-order'); +``` + +## The "concat_in_order" task + +### Overview + +The `concat_in_order` task extracts declared required dependencies as well as provided modules/classes from your javascript (or any other text) files. Having this [dependency graph](http://en.wikipedia.org/wiki/Dependency_graph) the task will perform [topological sort](http://en.wikipedia.org/wiki/Topological_sorting) and concatenate file so that all modules will be put after their required dependencies. + +In your project's Gruntfile, add a section named `concat_in_order` to the data object passed into `grunt.initConfig()`. + +```js +grunt.initConfig({ + concat_in_order: { + your_target: { + options: { + /* + this is a default function that extracts required dependencies/module names from file content + (getMatches - function that pick groups from given regexp) + extractRequired: function (filepath, filecontent) { + return this.getMatches(/require\(['"]([^'"]+)['"]/g, filecontent); + }, + this is a default function that extracts declared modules names from file content + extractDeclared: function (filepath, filecontent) { + return this.getMatches(/declare\(['"]([^'"]+)['"]/g, filecontent); + } + */ + }, + files: { + 'build/concatenated.js': ['lib/**/*.js'] + } + } + } +}) +``` +### Sample +Let's say you have 4 files in a `lib` directory + +- AUsingBaseBAndBaseA.js + +```js +/*start AUsingBaseBAndBaseA*/ +framwork.require('module.BaseB'); +framwork.require('module.BaseA'); +framework.declare('module.UsingBaseBAndBaseA'); +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ +``` + +- AUsingBaseA.js + +```js +/*start AUsingBaseA*/ +framwork.require('module.BaseA'); +var second = function secondFunction(){}; +/*end AUsingBaseA*/ +``` + + +- BaseA.js + +```js +/*start BaseA*/ +framework.declare('module.BaseA'); +var first = function firstFunction(){}; +/*end BaseA*/ +``` + +- BaseBUsingBaseA.js + +```js +/*start BaseBUsingBaseA*/ +framwork.require('module.BaseA'); +framework.declare('module.BaseBUsingBaseA'); +framework.declare('module.BaseB'); +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ +``` +Given the above configuration the task will produce `build/concatenated.js` file with following content: + +```js +/*start BaseA*/ +framework.declare('module.BaseA'); +var first = function firstFunction(){}; +/*end BaseA*/ +/*start BaseBUsingBaseA*/ +framwork.require('module.BaseA'); +framework.declare('module.BaseBUsingBaseA'); +framework.declare('module.BaseB'); +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ +/*start AUsingBaseA*/ +framwork.require('module.BaseA'); +var second = function secondFunction(){}; +/*end AUsingBaseA*/ +/*start AUsingBaseBAndBaseA*/ +framwork.require('module.BaseB'); +framwork.require('module.BaseA'); +framework.declare('module.UsingBaseBAndBaseA'); +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ +``` + +### File based +You can enable automatic addition of files with the following example. (notice the onlyConcatRequiredFiles : true) This is the same way of declaring dependencies used by [juicer](https://github.com/cjohansen/juicer) + + files: { + 'dist/mybuild.js': ['js/src/main.js'] + }, + options: { + extractRequired: function(filepath, filecontent) { + var workingdir = path.normalize(filepath).split(path.sep); + workingdir.pop(); + + var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent); + deps.forEach(function(dep, i) { + var dependency = workingdir.concat([dep]); + deps[i] = path.join.apply(null, dependency); + }); + return deps; + }, + extractDeclared: function(filepath) { + return [filepath]; + }, + onlyConcatRequiredFiles: true + } + +This will declare all files as modules using their filenames. In main.js you will typically have these depend statements: + + /** + * @depend ../lib/jquery.js + * @depend otherfile.js + * @depend calculator/add.js + */ + +You only need to specify the main.js and the other dependencies will be added automatically. As well as their dependencies etc. + +If you want to add a file that isn't referenced anywhere you need to add it manually. + + files: { + 'dist/mybuild.js': ['js/src/main.js', 'js/src/unReferencedButWanted.js'] + }, + +The option onlyConcatRequiredFiles will only work if modules are declared and required with their actual filenames. + +## Contributing +In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). + +## Release History + +- 0.1.6 - @mokkabonna updated documenation and fixed path splitting in sample +- 0.1.4 - @mokkabonna added ability to concat only files that are required by some module diff --git a/node_modules/grunt-concat-in-order/package.json b/node_modules/grunt-concat-in-order/package.json new file mode 100644 index 0000000..6ee234c --- /dev/null +++ b/node_modules/grunt-concat-in-order/package.json @@ -0,0 +1,73 @@ +{ + "name": "grunt-concat-in-order", + "description": "Concatenates files respecting declared, required dependencies order.", + "version": "0.1.6", + "homepage": "https://github.com/miensol/grunt-concat-in-order", + "author": { + "name": "Piotr Mionskowski", + "email": "piotr.mionskowski@gmail.com" + }, + "repository": { + "type": "git", + "url": "git://github.com/miensol/grunt-concat-in-order.git" + }, + "bugs": { + "url": "https://github.com/miensol/grunt-concat-in-order/issues" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/miensol/grunt-concat-in-order/blob/master/LICENSE-MIT" + } + ], + "main": "Gruntfile.js", + "engines": { + "node": ">= 0.8.0" + }, + "scripts": { + "test": "grunt test" + }, + "dependencies": {}, + "devDependencies": { + "grunt-contrib-jshint": "~0.6.0", + "grunt-contrib-clean": "~0.4.0", + "grunt-contrib-nodeunit": "~0.2.0", + "grunt": "~0.4.1" + }, + "peerDependencies": { + "grunt": "~0.4.1" + }, + "keywords": [ + "gruntplugin", + "concat", + "concat-in-order", + "topological sort", + "dependency tree", + "concat in order", + "resolve file order", + "sort dependencies", + "dependency" + ], + "readme": "# grunt-concat-in-order\r\n\r\n> Concatenates files respecting dependencies.\r\n\r\n## Getting Started\r\nThis plugin requires Grunt `~0.4.1`\r\n\r\nIf you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:\r\n\r\n```shell\r\nnpm install grunt-concat-in-order --save-dev\r\n```\r\n\r\nOnce the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:\r\n\r\n```js\r\ngrunt.loadNpmTasks('grunt-concat-in-order');\r\n```\r\n\r\n## The \"concat_in_order\" task\r\n\r\n### Overview\r\n\r\nThe `concat_in_order` task extracts declared required dependencies as well as provided modules/classes from your javascript (or any other text) files. Having this [dependency graph](http://en.wikipedia.org/wiki/Dependency_graph) the task will perform [topological sort](http://en.wikipedia.org/wiki/Topological_sorting) and concatenate file so that all modules will be put after their required dependencies.\r\n\r\nIn your project's Gruntfile, add a section named `concat_in_order` to the data object passed into `grunt.initConfig()`.\r\n\r\n```js\r\ngrunt.initConfig({\r\n concat_in_order: {\r\n your_target: {\r\n options: {\r\n /*\r\n this is a default function that extracts required dependencies/module names from file content\r\n (getMatches - function that pick groups from given regexp)\r\n extractRequired: function (filepath, filecontent) {\r\n return this.getMatches(/require\\(['\"]([^'\"]+)['\"]/g, filecontent);\r\n },\r\n this is a default function that extracts declared modules names from file content\r\n extractDeclared: function (filepath, filecontent) {\r\n return this.getMatches(/declare\\(['\"]([^'\"]+)['\"]/g, filecontent);\r\n }\r\n */\r\n },\r\n files: {\r\n 'build/concatenated.js': ['lib/**/*.js']\r\n }\r\n }\r\n }\r\n})\r\n```\r\n### Sample\r\nLet's say you have 4 files in a `lib` directory\r\n\r\n- AUsingBaseBAndBaseA.js\r\n\r\n```js\r\n/*start AUsingBaseBAndBaseA*/\r\nframwork.require('module.BaseB');\r\nframwork.require('module.BaseA');\r\nframework.declare('module.UsingBaseBAndBaseA');\r\nvar forth = function fourthFunction(){};\r\n/*end AUsingBaseBAnddBaseA*/\r\n```\r\n\r\n- AUsingBaseA.js\r\n\r\n```js\r\n/*start AUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nvar second = function secondFunction(){};\r\n/*end AUsingBaseA*/\r\n```\r\n\r\n\r\n- BaseA.js\r\n\r\n```js\r\n/*start BaseA*/\r\nframework.declare('module.BaseA');\r\nvar first = function firstFunction(){};\r\n/*end BaseA*/\r\n```\r\n\r\n- BaseBUsingBaseA.js\r\n\r\n```js\r\n/*start BaseBUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nframework.declare('module.BaseBUsingBaseA');\r\nframework.declare('module.BaseB');\r\nvar third = function thirdFunction(){};\r\n/*end BaseBUsingBaseA*/\r\n```\r\nGiven the above configuration the task will produce `build/concatenated.js` file with following content:\r\n\r\n```js\r\n/*start BaseA*/\r\nframework.declare('module.BaseA');\r\nvar first = function firstFunction(){};\r\n/*end BaseA*/\r\n/*start BaseBUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nframework.declare('module.BaseBUsingBaseA');\r\nframework.declare('module.BaseB');\r\nvar third = function thirdFunction(){};\r\n/*end BaseBUsingBaseA*/\r\n/*start AUsingBaseA*/\r\nframwork.require('module.BaseA');\r\nvar second = function secondFunction(){};\r\n/*end AUsingBaseA*/\r\n/*start AUsingBaseBAndBaseA*/\r\nframwork.require('module.BaseB');\r\nframwork.require('module.BaseA');\r\nframework.declare('module.UsingBaseBAndBaseA');\r\nvar forth = function fourthFunction(){};\r\n/*end AUsingBaseBAnddBaseA*/\r\n```\r\n\r\n### File based\r\nYou can enable automatic addition of files with the following example. (notice the onlyConcatRequiredFiles : true) This is the same way of declaring dependencies used by [juicer](https://github.com/cjohansen/juicer)\r\n\r\n files: {\r\n 'dist/mybuild.js': ['js/src/main.js']\r\n },\r\n options: {\r\n extractRequired: function(filepath, filecontent) {\r\n var workingdir = path.normalize(filepath).split(path.sep);\r\n workingdir.pop();\r\n\r\n var deps = this.getMatches(/\\*\\s*@depend\\s(.*\\.js)/g, filecontent);\r\n deps.forEach(function(dep, i) {\r\n var dependency = workingdir.concat([dep]);\r\n deps[i] = path.join.apply(null, dependency);\r\n });\r\n return deps;\r\n },\r\n extractDeclared: function(filepath) {\r\n return [filepath];\r\n },\r\n onlyConcatRequiredFiles: true\r\n }\r\n\r\nThis will declare all files as modules using their filenames. In main.js you will typically have these depend statements:\r\n\r\n /**\r\n * @depend ../lib/jquery.js\r\n * @depend otherfile.js\r\n * @depend calculator/add.js\r\n */\r\n\r\nYou only need to specify the main.js and the other dependencies will be added automatically. As well as their dependencies etc.\r\n\r\nIf you want to add a file that isn't referenced anywhere you need to add it manually.\r\n\r\n files: {\r\n 'dist/mybuild.js': ['js/src/main.js', 'js/src/unReferencedButWanted.js']\r\n },\r\n\r\nThe option onlyConcatRequiredFiles will only work if modules are declared and required with their actual filenames.\r\n\r\n## Contributing\r\nIn lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).\r\n\r\n## Release History\r\n\r\n- 0.1.6 - @mokkabonna updated documenation and fixed path splitting in sample\r\n- 0.1.4 - @mokkabonna added ability to concat only files that are required by some module\r\n", + "readmeFilename": "README.md", + "_id": "grunt-concat-in-order@0.1.6", + "dist": { + "shasum": "26e0d2b6ac9fdcbd6611b05937df78d68c5a8b0e", + "tarball": "http://registry.npmjs.org/grunt-concat-in-order/-/grunt-concat-in-order-0.1.6.tgz" + }, + "_from": "grunt-concat-in-order@*", + "_npmVersion": "1.2.21", + "_npmUser": { + "name": "miensol", + "email": "piotr.mionskowski@gmail.com" + }, + "maintainers": [ + { + "name": "miensol", + "email": "piotr.mionskowski@gmail.com" + } + ], + "directories": {}, + "_shasum": "26e0d2b6ac9fdcbd6611b05937df78d68c5a8b0e", + "_resolved": "https://registry.npmjs.org/grunt-concat-in-order/-/grunt-concat-in-order-0.1.6.tgz" +} diff --git a/node_modules/grunt-concat-in-order/tasks/concat_in_order.js b/node_modules/grunt-concat-in-order/tasks/concat_in_order.js new file mode 100644 index 0000000..b542a5c --- /dev/null +++ b/node_modules/grunt-concat-in-order/tasks/concat_in_order.js @@ -0,0 +1,168 @@ +/* + * grunt-concat-in-order + * https://github.com/miensol/grunt-concat-in-order + * + * Copyright (c) 2013 Piotr Mionskowski + * Licensed under the MIT license. + */ + +'use strict'; + +var path = require('path'), + EOL = require('os').EOL; + +module.exports = function (grunt) { + + var defaultOptions = { + getMatches: function (regex, string, index) { + var matches = [], match; + if(arguments.length < 3){ + index = 1; + } + while (match = regex.exec(string)) { + matches.push(match[index]); + } + return matches; + }, + extractRequired: function (filepath, filecontent) { + return this.getMatches(/require\(['"]([^'"]+)['"]/g, filecontent); + }, + extractDeclared: function (filepath, filecontent) { + return this.getMatches(/declare\(['"]([^'"]+)['"]/g, filecontent); + } + }, getExistingFiles = function (files) { + return files.src.filter(function (filepath) { + // Warn on and remove invalid source files (if nonull was set). + if (!grunt.file.exists(filepath)) { + grunt.log.warn('Source file "' + filepath + '" not found.'); + return false; + } else { + return true; + } + }); + }, ensureNoCycleExists = function (depsTree) { + var graph = {}; + depsTree.forEach(function (item) { + graph[item.file] = item; + }); + depsTree.forEach(function (item) { + var visited = {}; + ensureNoCycleExistsDfs(item, visited, graph); + }); + }, ensureNoCycleExistsDfs = function (item, visited, graph) { + var graphKeys = Object.keys(graph), + graphArray = graphKeys.map(function (key) { + return graph[key]; + }), + findItemsDeclaringIgnoreCase = function(declaration){ + return graphArray.filter(function (anyItem) { + return anyItem.declared.map(function(declared){ + return declared.toLowerCase(); + }).indexOf(declaration.toLowerCase()) !== -1; + }); + }, + findItemsDeclaring = function (declaration) { + return graphArray.filter(function (anyItem) { + return anyItem.declared.indexOf(declaration) !== -1; + }); + }, + findItemDeclaring = function (declaration) { + return findItemsDeclaring(declaration)[0]; + }; + visited[item.file] = item; + item.required.forEach(function (requiredItem) { + var message, + declaringItem = findItemDeclaring(requiredItem); + if (!declaringItem) { + message = findItemsDeclaringIgnoreCase(requiredItem).map(function(matching){ + return matching.declared.join(', ') + ' in ' + matching.file; + }).join(', '); + if(message.length){ + message = "\nMaybe " + message; + } + grunt.fail.fatal("Dependency required in " + item.file + " does not exist: " + requiredItem + message); + } + + if (!visited[declaringItem.file]) { + ensureNoCycleExistsDfs(declaringItem, visited, graph); + } else { + message = Object.keys(visited).join(', '); + grunt.fail.fatal("Cycle found! Current item is " + item.file + '\nVisited nodes are ' + message); + } + }); + delete visited[item.file]; + }, writeArray = function(array, prefix){ + grunt.verbose.writeln(prefix +':'); + if(array.length){ + array.forEach(function(item){ + grunt.verbose.writeln('\t' + item); + }); + } else { + grunt.verbose.writeln('\tNo items.'); + } + }; + + grunt.registerMultiTask('concat_in_order', 'Concatenates files in order', function () { + + var options = this.options(defaultOptions); + + this.files.forEach(function (fileSet) { + grunt.verbose.writeln('Extracting dependencies from "' + fileSet.src + '".'); + + var depsTree = [], ordered = [], current, countOfPreviouslySatisfiedDependencies, + previouslyDeclared = [], + previouslyDeclaredFilter = function (dep) { + return previouslyDeclared.filter(function (previous) { + return previous === dep; + }).length > 0; + }; + + getExistingFiles(fileSet).map(function extractAndAddDependencies(filepath) { + //do not process this file again if already added + if (depsTree.some(function(item) { + return item.file === path.normalize(filepath); + })) { + return; + } + + var content = grunt.file.read(filepath), + required = options.extractRequired(filepath, content), + declared = options.extractDeclared(filepath, content); + + grunt.verbose.writeln('File %s', filepath); + writeArray(required, 'required'); + writeArray(declared, 'declared'); + if (options.onlyConcatRequiredFiles) { + required.map(extractAndAddDependencies); + } + + depsTree.push({ + content: content, + file: path.normalize(filepath), + required: required, + declared: declared + }); + }); + + ensureNoCycleExists(depsTree); + + while (depsTree.length) { + current = depsTree.shift(); + countOfPreviouslySatisfiedDependencies = current.required.filter(previouslyDeclaredFilter).length; + if (countOfPreviouslySatisfiedDependencies === current.required.length) { + previouslyDeclared.push.apply(previouslyDeclared, current.declared); + ordered.push(current); + } else { + depsTree.push(current); + } + } + + grunt.file.write(fileSet.dest, ordered.map(function (item) { + return item.content; + }).join(EOL)); + + grunt.log.writeln('File "' + fileSet.dest + '" created.'); + }); + }); + +}; diff --git a/node_modules/grunt-concat-in-order/test/concat_in_order_test.js b/node_modules/grunt-concat-in-order/test/concat_in_order_test.js new file mode 100644 index 0000000..088d1ae --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/concat_in_order_test.js @@ -0,0 +1,48 @@ +'use strict'; + +var grunt = require('grunt'); + +/* + ======== A Handy Little Nodeunit Reference ======== + https://github.com/caolan/nodeunit + + Test methods: + test.expect(numAssertions) + test.done() + Test assertions: + test.ok(value, [message]) + test.equal(actual, expected, [message]) + test.notEqual(actual, expected, [message]) + test.deepEqual(actual, expected, [message]) + test.notDeepEqual(actual, expected, [message]) + test.strictEqual(actual, expected, [message]) + test.notStrictEqual(actual, expected, [message]) + test.throws(block, [error], [message]) + test.doesNotThrow(block, [error], [message]) + test.ifError(value) +*/ + +exports.concat_in_order = { + setUp: function(done) { + // setup here if necessary + done(); + }, + default_options: function(test) { + test.expect(1); + + var actual = grunt.file.read('tmp/default_options.js'); + var expected = grunt.file.read('test/expected/default_options.js'); + test.equal(actual, expected, 'should concatenate files respecting file order'); + + test.done(); + }, + filebased_options : function(test) { + test.expect(1); + + var actual = grunt.file.read('tmp/filebased_options.js'); + var expected = grunt.file.read('test/expected/filebased_options.js'); + test.equal(actual, expected, 'should include files automatically when filebased'); + + test.done(); + } +}; diff --git a/node_modules/grunt-concat-in-order/test/cycle/A.js b/node_modules/grunt-concat-in-order/test/cycle/A.js new file mode 100644 index 0000000..6a29fc9 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/cycle/A.js @@ -0,0 +1,2 @@ +declare('A'); +require('B'); \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/cycle/B.js b/node_modules/grunt-concat-in-order/test/cycle/B.js new file mode 100644 index 0000000..6bc7c39 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/cycle/B.js @@ -0,0 +1,2 @@ +declare('B'); +require('C'); \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/cycle/C.js b/node_modules/grunt-concat-in-order/test/cycle/C.js new file mode 100644 index 0000000..acd01ea --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/cycle/C.js @@ -0,0 +1,2 @@ +declare('C'); +require('A'); \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/default/AUsingBaseA.js b/node_modules/grunt-concat-in-order/test/default/AUsingBaseA.js new file mode 100644 index 0000000..1fa2898 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/default/AUsingBaseA.js @@ -0,0 +1,4 @@ +/*start AUsingBaseA*/ +framwork.require('module.BaseA'); +var second = function secondFunction(){}; +/*end AUsingBaseA*/ \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/default/AUsingBaseBAndBaseA.js b/node_modules/grunt-concat-in-order/test/default/AUsingBaseBAndBaseA.js new file mode 100644 index 0000000..45d41b5 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/default/AUsingBaseBAndBaseA.js @@ -0,0 +1,6 @@ +/*start AUsingBaseBAndBaseA*/ +framwork.require('module.BaseB'); +framwork.require('module.BaseA'); +framework.declare('module.UsingBaseBAndBaseA'); +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/default/BaseA.js b/node_modules/grunt-concat-in-order/test/default/BaseA.js new file mode 100644 index 0000000..72a7e85 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/default/BaseA.js @@ -0,0 +1,4 @@ +/*start BaseA*/ +framework.declare('module.BaseA'); +var first = function firstFunction(){}; +/*end BaseA*/ \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/default/BaseBUsingBaseA.js b/node_modules/grunt-concat-in-order/test/default/BaseBUsingBaseA.js new file mode 100644 index 0000000..9f58588 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/default/BaseBUsingBaseA.js @@ -0,0 +1,6 @@ +/*start BaseBUsingBaseA*/ +framwork.require('module.BaseA'); +framework.declare('module.BaseBUsingBaseA'); +framework.declare('module.BaseB'); +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/expected/custom_options b/node_modules/grunt-concat-in-order/test/expected/custom_options new file mode 100644 index 0000000..e597128 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/expected/custom_options @@ -0,0 +1 @@ +Testing: 1 2 3 !!! \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/expected/default_options.js b/node_modules/grunt-concat-in-order/test/expected/default_options.js new file mode 100644 index 0000000..a667d5e --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/expected/default_options.js @@ -0,0 +1,20 @@ +/*start BaseA*/ +framework.declare('module.BaseA'); +var first = function firstFunction(){}; +/*end BaseA*/ +/*start BaseBUsingBaseA*/ +framwork.require('module.BaseA'); +framework.declare('module.BaseBUsingBaseA'); +framework.declare('module.BaseB'); +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ +/*start AUsingBaseA*/ +framwork.require('module.BaseA'); +var second = function secondFunction(){}; +/*end AUsingBaseA*/ +/*start AUsingBaseBAndBaseA*/ +framwork.require('module.BaseB'); +framwork.require('module.BaseA'); +framework.declare('module.UsingBaseBAndBaseA'); +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/expected/filebased_options.js b/node_modules/grunt-concat-in-order/test/expected/filebased_options.js new file mode 100644 index 0000000..6b35550 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/expected/filebased_options.js @@ -0,0 +1,26 @@ +/*start BaseA*/ +var first = function firstFunction(){}; +/*end BaseA*/ + +/*start AUsingBaseA*/ +/** + * @depend BaseA.js + */ +var second = function secondFunction(){}; +/*end AUsingBaseA*/ + +/*start BaseBUsingBaseA*/ +/** + * @depend AUsingBaseA.js + */ +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ + +/*start AUsingBaseBAndBaseA*/ +/** + * @depend BaseBUsingBaseA.js + * @depend BaseA.js + * + */ +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ diff --git a/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseA.js b/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseA.js new file mode 100644 index 0000000..1928938 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseA.js @@ -0,0 +1,6 @@ +/*start AUsingBaseA*/ +/** + * @depend BaseA.js + */ +var second = function secondFunction(){}; +/*end AUsingBaseA*/ diff --git a/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseBAndBaseA.js b/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseBAndBaseA.js new file mode 100644 index 0000000..c82323b --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/filebased/AUsingBaseBAndBaseA.js @@ -0,0 +1,8 @@ +/*start AUsingBaseBAndBaseA*/ +/** + * @depend BaseBUsingBaseA.js + * @depend BaseA.js + * + */ +var forth = function fourthFunction(){}; +/*end AUsingBaseBAnddBaseA*/ diff --git a/node_modules/grunt-concat-in-order/test/filebased/BaseA.js b/node_modules/grunt-concat-in-order/test/filebased/BaseA.js new file mode 100644 index 0000000..12e6fa6 --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/filebased/BaseA.js @@ -0,0 +1,3 @@ +/*start BaseA*/ +var first = function firstFunction(){}; +/*end BaseA*/ diff --git a/node_modules/grunt-concat-in-order/test/filebased/BaseBUsingBaseA.js b/node_modules/grunt-concat-in-order/test/filebased/BaseBUsingBaseA.js new file mode 100644 index 0000000..a30bd2a --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/filebased/BaseBUsingBaseA.js @@ -0,0 +1,6 @@ +/*start BaseBUsingBaseA*/ +/** + * @depend AUsingBaseA.js + */ +var third = function thirdFunction(){}; +/*end BaseBUsingBaseA*/ diff --git a/node_modules/grunt-concat-in-order/test/missing/lowercase.js b/node_modules/grunt-concat-in-order/test/missing/lowercase.js new file mode 100644 index 0000000..6a05c6d --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/missing/lowercase.js @@ -0,0 +1 @@ +declare('lowercase'); \ No newline at end of file diff --git a/node_modules/grunt-concat-in-order/test/missing/requiresUpperCase.js b/node_modules/grunt-concat-in-order/test/missing/requiresUpperCase.js new file mode 100644 index 0000000..0cb0d9d --- /dev/null +++ b/node_modules/grunt-concat-in-order/test/missing/requiresUpperCase.js @@ -0,0 +1 @@ +require('LOWERCASE'); \ No newline at end of file diff --git a/package.json b/package.json index d8d5bc0..7437ab1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "grunt-contrib-jshint": "^0.11.0", "grunt-contrib-watch": "^0.6.1" }, - "devDependencies": {}, + "devDependencies": { + "grunt-concat-in-order": "^0.1.6" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/src/flowy.js b/src/flowy.js new file mode 100644 index 0000000..8aacf11 --- /dev/null +++ b/src/flowy.js @@ -0,0 +1,7 @@ +/** + * @depend views/app.js + */ + +$(function() { + var flowyView = new AppView(); +}); diff --git a/src/models/flowyDoc.js b/src/models/flowyDoc.js new file mode 100644 index 0000000..893f5a9 --- /dev/null +++ b/src/models/flowyDoc.js @@ -0,0 +1,34 @@ +/** + * @depend ../models/todo.js + */ + +flowyDocDefaults = { rootId: null }; +var FlowyDocModel = Backbone.Collection.extend({ + initialize: function(options) { + options = _.defaults({}, options, flowyDocDefaults); + this.id = options.id; + this.default = options.default; + this.rootId = options.rootId; + }, + model: TodoModel, + localStorage: new Backbone.LocalStorage("todos-backbone"), + fetch: function(options) { // TODO: Move to base class as default behavior for localStorage + // If during the initial fetch, the collection is not present, instead populate it using this.default + options = options ? _.clone(options) : {}; + var error = options.error; + var collection = this; + options.error = function(_, resp, __) { + if (resp ==="Record Not Found" && collection.default) { + var method = options.reset ? 'reset' : 'set'; + collection[method](collection.default); + } + if (error) error(collection, resp, options); + }; + Backbone.Collection.prototype.fetch.call(this, options); + }, + nextOrder: function() { + if (!this.length) return 1; + return this.last().get('order') + 1; + }, + comparator: 'order', +}); diff --git a/src/models/todo.js b/src/models/todo.js new file mode 100644 index 0000000..1b5f230 --- /dev/null +++ b/src/models/todo.js @@ -0,0 +1,34 @@ +var TodoModel = Backbone.Model.extend({ + defaults: function() { + return { + completed: false, + collapsed: false, + text: "Should never be visible", + bullets: [], + parent: null, + }; + }, + toggleComplete: function() { + this.save({completed: !this.get("completed")}); + }, + toggleCollapsed: function() { + this.save({collapsed: !this.get("collapsed")}); + }, + setText: function(text) { + this.save({text: text}); + }, + getChildren: function(collection) { + return _.map(this.get("bullets"), function(id) { + return collection.get(id); + }, this); + }, + getParent: function(collection) { + return collection.get(this.get("parent")); + }, + isTopLevel: function(collection) { + return this.get("parent") === collection.rootId; + }, + isParentLoaded: function(collection, collectionView) { + return this.isTopLevel(collection) ? true : (this.getParent(collection) && collectionView.getView(this.getParent(collection))); + }, +}); diff --git a/src/views/app.js b/src/views/app.js new file mode 100644 index 0000000..920b454 --- /dev/null +++ b/src/views/app.js @@ -0,0 +1,103 @@ +/** + * @depend ../views/todo.js + * @depend ../models/flowyDoc.js + * @depend ../models/todo.js + */ + +var todos = new FlowyDocModel({ + id: "master", + default: [ + new TodoModel({ + parent: null, + id: 1, + text: "Daily todos", + bullets: [2,3,4], + }), + new TodoModel({ + parent: 1, + id: 2, + text: "Shave", + completed: true + }), + new TodoModel({ + parent: 1, + id: 3, + text: "Check t-mail", + completed: true + }), + new TodoModel({ + parent: 1, + id: 4, + text: "Eat green eggs and ham", + }), + new TodoModel({ + parent: null, + id: 5, + text: "To do this year", + collapsed: true, + bullets: [6], + }), + new TodoModel({ + parent: 5, + id: 6, + text: "Save the world", + bullets: [7], + }), + new TodoModel({ + parent: 6, + id: 7, + text: "Save California", + }), + ], +}); + +var appDefaults = { list: todos }; +var AppView = Backbone.View.extend({ + el: $("#todo-app"), + initialize: function(options) { + options = _.defaults({}, options, appDefaults); + this.listenTo(todos, 'add', this.addOne); + this.listenTo(todos, 'reset', this.addAll); + this.list = options.list; + this.views = {}; // A list of views for each element in the collection + this.list.fetch(); + }, + render: function() { + return this; + }, + addOne: function(todo) { + this.renderTodo(todo); + }, + renderTodo: function(todo) { + if (this.getView(todo)) { + console.log("View rendered more than once for todo #" + todo.id); + return; + } + var view = new TodoView({model: todo}); + this.setView(todo, view); + if (todo.isTopLevel(this.list)) { + this.$("#todo-list").append(view.render().el); + } else if (todo.isParentLoaded(this.list, this)) { + var parent = todo.getParent(this.list); + var parentView = this.getView(parent); + parentView.addChild(view.render().el); + } + + // Find unrendered descendents and render them, too. + _.each(todo.getChildren(this.list), function(child) { + if (child && !this.getView(child)) { + this.renderTodo(child); + } + }, this); + return this; + }, + setView: function(model, view) { + this.views[model.id] = view; + }, + getView: function(model) { + return this.views[model.id]; + }, + addAll: function() { + this.list.each(this.addOne, this); + }, +}); diff --git a/src/views/todo.js b/src/views/todo.js new file mode 100644 index 0000000..abba964 --- /dev/null +++ b/src/views/todo.js @@ -0,0 +1,31 @@ +var TodoView = Backbone.View.extend({ + tagName: 'div', + className: 'todo', + events: { + "click > .text": "toggleComplete", + }, + initialize: function() { + this.listenTo(this.model, "change", this.render); + this.listenTo(this.model, 'destroy', this.remove); + }, + toggleComplete: function(e) { + this.model.toggleComplete(); + e.stopPropagation(); + }, + template: "
{{text}}
", + addChild: function(el, position) { + if(typeof position === 'undefined') { + console.log("TodoView:addChild called without a position"); + } + this.$el.find("> .bullets").append(el); + return this; + }, + render: function() { + var oldChildren = this.$el.find("> .bullets > *").detach(); // detach keeps handlers + this.$el.html(Mustache.to_html(this.template, this.model.toJSON())); // Should hopefully be model.attributes + this.$el.toggleClass('completed', this.model.get('completed')); + this.$el.toggleClass('collapsed', this.model.get('collapsed')); + this.$el.find("> .bullets").append(oldChildren); + return this; + }, +});