├── part1 ├── README.md └── index.html ├── part2 ├── README.md ├── PostViewWithTemplate.js ├── PostViewWithoutTemplate.js ├── index.html ├── tests.html ├── PostViewWithEvents.js └── PostView.js ├── part3 ├── README.md ├── public │ ├── js │ │ ├── models │ │ │ └── PostModel.js │ │ ├── views │ │ │ ├── PostView.js │ │ │ └── PostFormView.js │ │ └── basic_samples │ │ │ └── PostModel.js │ ├── samples.html │ ├── index.html │ ├── add.html │ └── lib │ │ ├── underscore-min.js │ │ ├── backbone-min.js │ │ └── mustache.js ├── basic │ ├── index.html │ └── ModelsBasicUsage.js └── posts.rb ├── part4 ├── README.md ├── public │ ├── js │ │ ├── collections │ │ │ └── PostList.js │ │ ├── models │ │ │ └── PostModel.js │ │ └── views │ │ │ ├── PostView.js │ │ │ ├── AppView.js │ │ │ └── PostFormView.js │ ├── index.html │ └── lib │ │ ├── underscore-min.js │ │ ├── backbone-min.js │ │ └── mustache.js ├── samples │ ├── index.html │ ├── server.js │ └── samples.js └── posts.rb ├── part5 ├── README.md ├── blog │ ├── public │ │ ├── js │ │ │ ├── models │ │ │ │ ├── PostReaded.js │ │ │ │ └── PostModel.js │ │ │ ├── collections │ │ │ │ └── PostCollection.js │ │ │ ├── views │ │ │ │ ├── AppView.js │ │ │ │ ├── HeaderView.js │ │ │ │ └── posts │ │ │ │ │ ├── PostView.js │ │ │ │ │ └── PostFormView.js │ │ │ └── AppRouter.js │ │ ├── css │ │ │ └── blog.css │ │ ├── index.html │ │ └── libs │ │ │ └── js │ │ │ ├── backbone.localStorage-min.js │ │ │ ├── underscore-min.js │ │ │ └── mustache.js │ └── posts.rb └── examples │ ├── js │ ├── utils.js │ ├── sync.js │ ├── router.js │ └── events.js │ └── index.html ├── part6 ├── README.md └── contacts-manager │ ├── .gitattributes │ ├── app │ ├── robots.txt │ ├── styles │ │ ├── main.css │ │ └── main.scss │ ├── scripts │ │ └── main.js │ ├── favicon.ico │ ├── index.html │ └── 404.html │ ├── .bowerrc │ ├── .gitignore │ ├── test │ ├── lib │ │ ├── expect.js │ │ └── mocha │ │ │ └── mocha.css │ ├── spec │ │ └── test.js │ └── index.html │ ├── bower.json │ ├── .editorconfig │ ├── .jshintrc │ ├── package.json │ └── Gruntfile.js ├── .gitignore └── README.md /part1/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /part2/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /part3/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /part4/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /part5/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /part6/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /part6/contacts-manager/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /part6/contacts-manager/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /part6/contacts-manager/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /part6/contacts-manager/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test/temp 4 | .sass-cache 5 | app/bower_components 6 | .tmp 7 | test/bower_components/ 8 | -------------------------------------------------------------------------------- /part6/contacts-manager/app/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fafafa; 3 | } 4 | 5 | .hero-unit { 6 | margin: 50px auto 0 auto; 7 | width: 300px; 8 | } -------------------------------------------------------------------------------- /part5/blog/public/js/models/PostReaded.js: -------------------------------------------------------------------------------- 1 | var PostReaded = Backbone.Model.extend({ 2 | localStorage: new Backbone.LocalStorage("PostReaded"), 3 | defaults: { 4 | id: 1, 5 | post_id: '' 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /part4/public/js/collections/PostList.js: -------------------------------------------------------------------------------- 1 | var PostList = Backbone.Collection.extend({ 2 | model: PostModel, 3 | url: '/posts', 4 | comparator: function(post) { 5 | -post.get('id'); 6 | } 7 | }); 8 | 9 | var Posts = new PostList(); -------------------------------------------------------------------------------- /part5/blog/public/js/collections/PostCollection.js: -------------------------------------------------------------------------------- 1 | var PostCollection = Backbone.Collection.extend({ 2 | model: PostModel, 3 | url: '/posts', 4 | comparator: function(post) { 5 | -post.get('id'); 6 | } 7 | }); 8 | 9 | var Posts = new PostCollection(); -------------------------------------------------------------------------------- /part6/contacts-manager/test/lib/expect.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * chai 3 | * Copyright(c) 2011-2012 Jake Luer 4 | * MIT Licensed 5 | */ 6 | 7 | module.exports = function (chai, util) { 8 | chai.expect = function (val, message) { 9 | return new chai.Assertion(val, message); 10 | }; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /part6/contacts-manager/test/spec/test.js: -------------------------------------------------------------------------------- 1 | /*global describe, it */ 2 | 'use strict'; 3 | (function () { 4 | describe('Give it some context', function () { 5 | describe('maybe a bit more context here', function () { 6 | it('should run here few assertions', function () { 7 | 8 | }); 9 | }); 10 | }); 11 | })(); 12 | -------------------------------------------------------------------------------- /part4/public/js/models/PostModel.js: -------------------------------------------------------------------------------- 1 | var PostModel = Backbone.Model.extend({ 2 | defaults: { 3 | title: "", 4 | text: "" 5 | }, 6 | validate: function(attrs) { 7 | if (attrs.title == '') 8 | return 'O título é obrigatório'; 9 | if (attrs.text == '') 10 | return 'O texto é obrigatório' 11 | } 12 | }); -------------------------------------------------------------------------------- /part3/public/js/models/PostModel.js: -------------------------------------------------------------------------------- 1 | var PostModel = Backbone.Model.extend({ 2 | urlRoot: 'posts', 3 | defaults: { 4 | title: "", 5 | text: "" 6 | }, 7 | validate: function(attrs) { 8 | if (attrs.title == '') 9 | return 'O título é obrigatório'; 10 | if (attrs.text == '') 11 | return 'O texto é obrigatório' 12 | } 13 | }); -------------------------------------------------------------------------------- /part6/contacts-manager/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contacts-manager", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "sass-bootstrap": "~3.0.0", 6 | "jquery": "~1.9.0", 7 | "underscore": "~1.4.3", 8 | "backbone": "~1.0.0", 9 | "requirejs": "~2.1.5", 10 | "requirejs-text": "~2.0.5", 11 | "modernizr": "~2.6.2" 12 | }, 13 | "devDependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /part2/PostViewWithTemplate.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | template: _.template("

<%= title %>

<%= content %>

"), 5 | render: function() { 6 | this.$el.html(this.template({title: "Nome do Post", content: "Conteudo do Post"})); 7 | } 8 | }); 9 | 10 | var postView = new PostView(); 11 | postView.render(); 12 | console.log(postView.el); -------------------------------------------------------------------------------- /part2/PostViewWithoutTemplate.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | render: function() { 5 | var htmlGenerated = "

Nome do Post

"; 6 | htmlGenerated += "

Conteudo do post

"; 7 | this.$el.html(htmlGenerated); //Shortcut to $(this.el).html(htmlGenerated); 8 | } 9 | }); 10 | 11 | var postView = new PostView(); 12 | postView.render(); 13 | console.log(postView.el); -------------------------------------------------------------------------------- /part3/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Tutorial Series Part 3 - Model 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /part5/examples/js/utils.js: -------------------------------------------------------------------------------- 1 | Backbone.$ = jQuery.noConflict(); 2 | 3 | // Main Backbone does not use Zepto, will return an error because "pluck" is present on Zepto API 4 | try { 5 | console.log(Backbone.$('body > *').pluck('nodeName')); 6 | } catch (error) { 7 | console.log(error); 8 | } 9 | 10 | var localBackbone = Backbone.noConflict(); 11 | var localBackboneModel = localBackbone.Model.extend({}); 12 | 13 | // The localBackbone will use Zepto 14 | localBackbone.$ = Zepto; 15 | console.log(localBackbone.$('body > *').pluck('nodeName')); -------------------------------------------------------------------------------- /part6/contacts-manager/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /part6/contacts-manager/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "jquery": true 22 | } 23 | -------------------------------------------------------------------------------- /part3/public/samples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Tutorial Series Part 3 - Samples 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /part5/blog/public/js/models/PostModel.js: -------------------------------------------------------------------------------- 1 | var PostModel = Backbone.Model.extend({ 2 | urlRoot: '/posts', 3 | defaults: { 4 | title:'', 5 | text:'' 6 | }, 7 | isReaded: function() { 8 | var lastPost = new PostReaded(); 9 | lastPost.fetch(); 10 | return lastPost.get('post_id') == this.get('id'); 11 | }, 12 | validate: function(attrs) { 13 | if (attrs.title == '') 14 | return 'The title field is required'; 15 | if (attrs.text == '') 16 | return 'The text field is required'; 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /part5/blog/public/js/views/AppView.js: -------------------------------------------------------------------------------- 1 | var AppView = Backbone.View.extend({ 2 | el: $('#content'), 3 | initialize: function() { 4 | _.bindAll(this, 'render', 'addAll', 'addPost', 'showForm'); 5 | }, 6 | render: function() { 7 | this.$el.empty(); 8 | this.addAll(); 9 | }, 10 | addPost: function(post) { 11 | var view = new PostView({ 12 | model: post 13 | }); 14 | this.$el.append(view.render().el); 15 | }, 16 | addAll: function() { 17 | Posts.each(this.addPost); 18 | }, 19 | showForm: function() { 20 | this.form.show(); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /part4/samples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone Tutorial - Part 4 Collections 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /part2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Tutorial Series Part 2 - View 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /part2/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Tutorial Series Part 2 - View 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /part6/contacts-manager/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /part5/blog/public/css/blog.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serifs; 3 | } 4 | 5 | .site-header { 6 | border-bottom: 1px #ccc solid; 7 | } 8 | 9 | h1 { 10 | font-size: 32px; 11 | } 12 | 13 | h1, h2, h2 a { 14 | color: #034C7C; 15 | } 16 | 17 | .toolbar a { 18 | color: #999; 19 | } 20 | 21 | h2 { 22 | float: left; 23 | font-size: 28px; 24 | } 25 | 26 | .readed a { 27 | color: #FF8500; 28 | } 29 | 30 | form { 31 | margin-bottom: 10px; 32 | } 33 | 34 | .remove-button { 35 | color: red; 36 | display: inline-block; 37 | font-size: 12px; 38 | margin-top: 34px; 39 | padding-left: 10px; 40 | } 41 | 42 | .post-content { 43 | clear: both; 44 | } 45 | 46 | .clear { 47 | clear: both; 48 | } 49 | -------------------------------------------------------------------------------- /part4/public/js/views/PostView.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | template: $('#post-template').html(), 5 | 6 | events: { 7 | "click .remove-button": "removePost" 8 | }, 9 | 10 | initialize: function() { 11 | 12 | _.bindAll(this, 'render', 'removePost', 'remove'); 13 | 14 | this.model.on("change", this.render); 15 | this.model.on("destroy", this.remove); 16 | }, 17 | 18 | render: function() { 19 | var viewContent = Mustache.to_html(this.template, this.model.toJSON()); 20 | this.$el.html(viewContent); 21 | return this; 22 | }, 23 | 24 | removePost: function() { 25 | this.model.destroy(); 26 | } 27 | }); -------------------------------------------------------------------------------- /part5/examples/js/sync.js: -------------------------------------------------------------------------------- 1 | // Override Backbone.sync 2 | Backbone.sync = function(method, model, options) { 3 | if (method == 'create') { 4 | console.log('creating a new model...'); 5 | } else { 6 | console.log('not creating, doing now a: ' + method); 7 | } 8 | }; 9 | 10 | // Override Backbone.sync for all Models 11 | Backbone.Model.prototype.sync = function(method, model, options) { 12 | console.log('Models call this sync method...'); 13 | }; 14 | 15 | // Emulate not supported HTTP methods and JSON HTTP Body 16 | Backbone.emulateHTTP = true; 17 | Backbone.emulateJSON = true; 18 | 19 | // Make a request just to show the behavior 20 | var PostModel = Backbone.Model.extend({}); 21 | var post = new PostModel({ 22 | title: 'Title', 23 | content: 'Content' 24 | }); 25 | post.save(); 26 | post.fetch({id: 1}); -------------------------------------------------------------------------------- /part6/contacts-manager/app/scripts/main.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | 'use strict'; 3 | 4 | require.config({ 5 | shim: { 6 | underscore: { 7 | exports: '_' 8 | }, 9 | backbone: { 10 | deps: [ 11 | 'underscore', 12 | 'jquery' 13 | ], 14 | exports: 'Backbone' 15 | }, 16 | bootstrap: { 17 | deps: ['jquery'], 18 | exports: 'jquery' 19 | } 20 | }, 21 | paths: { 22 | jquery: '../bower_components/jquery/jquery', 23 | backbone: '../bower_components/backbone/backbone', 24 | underscore: '../bower_components/underscore/underscore', 25 | bootstrap: 'vendor/bootstrap' 26 | } 27 | }); 28 | 29 | require([ 30 | 'backbone' 31 | ], function (Backbone) { 32 | Backbone.history.start(); 33 | }); 34 | -------------------------------------------------------------------------------- /part1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World 6 | 7 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /part2/PostViewWithEvents.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | template: _.template("

<%= title %>

<%= content %>

"), 5 | 6 | events: { 7 | "dblclick" : "fullScreen", 8 | "click #add-button" : "newPost", 9 | "blur #username" : "searchUsername" 10 | }, 11 | 12 | render: function() { 13 | this.$el.html(this.template({title: "Nome do Post", content: "Conteudo do Post"})); 14 | }, 15 | newPost: function() { 16 | window.alert("Adicionar novo post"); 17 | }, 18 | 19 | searchUsername: function(e) { 20 | window.alert("Searching username " + e.target.value); 21 | }, 22 | 23 | fullScreen: function() { 24 | window.alert("Post full screen"); 25 | } 26 | }); 27 | 28 | var postView = new PostView(); 29 | postView.render(); 30 | console.log(postView.el); -------------------------------------------------------------------------------- /part2/PostView.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | template: _.template('Add Post

<%= title %>

<%= content %>

Comments

'), 5 | events: { 6 | "dblclick" : "fullScreen", 7 | "click #add-button" : "newPost", 8 | "blur #username" : "searchUsername" 9 | }, 10 | render: function() { 11 | this.$el.html(this.template({title: "Nome do Post", content: "Conteúdo do Post"})); 12 | }, 13 | 14 | newPost: function() { 15 | window.alert("Adicionar novo post"); 16 | }, 17 | 18 | searchUsername: function(e) { 19 | window.alert("Searching username " + e.target.value); 20 | }, 21 | 22 | fullScreen: function() { 23 | window.alert("Post full screen"); 24 | } 25 | }); -------------------------------------------------------------------------------- /part3/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Blog 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Blog

15 | Adicionar Post 16 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /part3/public/js/views/PostView.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | events: { 5 | "click #remove-button": "removePost" 6 | }, 7 | 8 | initialize: function() { 9 | _.bindAll(this, 'render', 'removePost', 'refresh'); 10 | 11 | this.template = $('#post-template').html(); 12 | 13 | this.model = new PostModel(); 14 | 15 | this.model.on("change", this.render); 16 | this.model.on("destroy", this.refresh); 17 | this.model.fetch(); 18 | }, 19 | 20 | render: function() { 21 | console.log("Rendering..."); 22 | var rendered = Mustache.to_html(this.template, this.model.toJSON()); 23 | this.$el.html(rendered); 24 | $('body').append(this.el); 25 | }, 26 | 27 | removePost: function() { 28 | this.model.destroy(); 29 | }, 30 | 31 | refresh: function() { 32 | this.model.clear({silent: true}); 33 | this.model.fetch(); 34 | } 35 | }); -------------------------------------------------------------------------------- /part5/examples/js/router.js: -------------------------------------------------------------------------------- 1 | // Backbone.Router 2 | var AppRouter = Backbone.Router.extend({ 3 | initialize: function(options) { 4 | this.route("posts/*ids", "multiple", function (ids) { 5 | console.log("Fetching posts " + ids); 6 | }); 7 | }, 8 | 9 | routes: { 10 | "add" : "callback", 11 | "help" : "helpCallback", 12 | "posts/:id" : "postsCallback" 13 | }, 14 | 15 | callback: function() { 16 | console.log("add callback"); 17 | }, 18 | 19 | helpCallback: function() { 20 | console.log("help callback"); 21 | }, 22 | 23 | postsCallback: function (id) { 24 | console.log("Fetching post " + id); 25 | } 26 | }); 27 | 28 | var router = new AppRouter(); 29 | 30 | // Router events 31 | router.on("route:callback", function() { 32 | console.log("Add route event"); 33 | }) 34 | 35 | // History 36 | if (Backbone.history.start()) { 37 | console.log("Matched the current URL"); 38 | } else { 39 | console.log("Current URL not matched by History"); 40 | } -------------------------------------------------------------------------------- /part4/public/js/views/AppView.js: -------------------------------------------------------------------------------- 1 | var AppView = Backbone.View.extend({ 2 | el: $('#content'), 3 | 4 | initialize: function() { 5 | 6 | _.bindAll(this, 'render', 'addAll', 'addPost', 'showForm'); 7 | 8 | Posts.bind('add', this.addPost); 9 | Posts.bind('reset', this.addAll); 10 | Posts.bind('sync', this.render); 11 | Posts.fetch(); 12 | 13 | $('.add-button').on('click', this.showForm); 14 | 15 | this.form = new PostFormView(); 16 | this.form.render(); 17 | $('header').append(this.form.el); 18 | }, 19 | 20 | render: function() { 21 | this.$el.empty(); 22 | this.addAll(); 23 | }, 24 | 25 | addPost: function(post) { 26 | var view = new PostView({ 27 | model: post 28 | }); 29 | this.$el.append(view.render().el); 30 | }, 31 | 32 | addAll: function() { 33 | Posts.each(this.addPost); 34 | }, 35 | 36 | showForm: function() { 37 | this.form.show(); 38 | } 39 | }); -------------------------------------------------------------------------------- /part3/public/add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backbone Tutorial Series Part 3 - Model 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /part5/examples/js/events.js: -------------------------------------------------------------------------------- 1 | // Uses the Events module 2 | var object = {}; 3 | 4 | _.extend(object, Backbone.Events); 5 | 6 | // Attach a simple event callback 7 | object.on("myevent", function() { 8 | console.log("myevent was triggered"); 9 | }); 10 | 11 | // Trigger the custom event 12 | object.trigger("myevent"); 13 | 14 | // This callback will receive an argument, it will be triggered with the other attached callback 15 | callback = function(argument) { 16 | console.log("Event triggered with the argument: " + argument); 17 | }; 18 | 19 | object.on("myevent", callback, this); 20 | 21 | // This will be a callback to ALL events triggered 22 | function globalCallback() { 23 | console.log("Global callback triggered"); 24 | } 25 | object.on("all", globalCallback); 26 | 27 | // Trigger two different events to show the use of the global callback 28 | object.trigger("test"); 29 | object.trigger("myevent"); 30 | 31 | // Detach the globalcallback 32 | // We could use off to detach the first callback from "myevent" also 33 | object.off("all", globalCallback); 34 | 35 | // Trigger an event with an argument 36 | object.trigger("myevent", "argument"); -------------------------------------------------------------------------------- /part6/contacts-manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contacts-manager", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-copy": "~0.4.0", 8 | "grunt-contrib-concat": "~0.3.0", 9 | "grunt-contrib-coffee": "~0.7.0", 10 | "grunt-contrib-jst": "~0.5.0", 11 | "grunt-contrib-uglify": "~0.2.0", 12 | "grunt-contrib-compass": "~0.5.0", 13 | "grunt-contrib-jshint": "~0.6.3", 14 | "grunt-contrib-cssmin": "~0.6.0", 15 | "grunt-contrib-connect": "~0.3.0", 16 | "grunt-contrib-clean": "~0.5.0", 17 | "grunt-contrib-htmlmin": "~0.1.3", 18 | "grunt-contrib-imagemin": "~0.2.0", 19 | "grunt-contrib-watch": "~0.5.2", 20 | "grunt-mocha": "~0.4.1", 21 | "grunt-bower-requirejs": "~0.7.0", 22 | "grunt-usemin": "~0.1.10", 23 | "grunt-requirejs": "~0.4.0", 24 | "grunt-rev": "~0.1.0", 25 | "grunt-open": "~0.2.0", 26 | "load-grunt-tasks": "~0.1.0", 27 | "connect-livereload": "~0.2.0", 28 | "time-grunt": "~0.1.1", 29 | "jshint-stylish": "~0.1.3" 30 | }, 31 | "engines": { 32 | "node": ">=0.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part3/posts.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'json' 3 | require 'active_record' 4 | 5 | ActiveRecord::Base.include_root_in_json = false 6 | 7 | class Post < ActiveRecord::Base 8 | end 9 | 10 | Post.establish_connection( 11 | :adapter => "sqlite3", 12 | :database => "data.db" 13 | ) 14 | 15 | # Shows the index.html page, which contains the Backbone.js code 16 | get '/' do 17 | File.read(File.join('public', 'index.html')) 18 | end 19 | 20 | # Get the latest post from the database 21 | get '/posts' do 22 | content_type :json 23 | Post.last.to_json 24 | end 25 | 26 | # Create a new post 27 | post '/posts' do 28 | data = JSON.parse request.body.read 29 | 30 | post = Post.new 31 | post.title = data['title'] 32 | post.text = data['text'] 33 | 34 | post.save 35 | end 36 | 37 | # Update an existing post 38 | put '/posts/:id' do 39 | data = JSON.parse request.body.read 40 | 41 | post = Post.find params[:id] 42 | post.title = data['title'] 43 | post.text = data['text'] 44 | 45 | post.save 46 | end 47 | 48 | # Remove an existing post 49 | delete '/posts/:id' do 50 | Post.destroy params[:id] 51 | end 52 | 53 | Post.connection.close -------------------------------------------------------------------------------- /part5/blog/public/js/views/HeaderView.js: -------------------------------------------------------------------------------- 1 | var HeaderView = Backbone.View.extend({ 2 | tagName: 'header', 3 | className: 'site-header', 4 | template: $('#header').html(), 5 | events: { 6 | 'click #new-post': 'addButtonClick' 7 | }, 8 | initialize: function() { 9 | _.bindAll(this, 'render', 'addButtonClick', 'showForm', 'hideForm', 'showControls', 'hideControls'); 10 | }, 11 | render: function() { 12 | var viewContent = Mustache.to_html(this.template); 13 | this.$el.html(viewContent); 14 | return this; 15 | }, 16 | hideControls: function() { 17 | this.$el.find('.toolbar').hide(); 18 | }, 19 | showControls: function() { 20 | this.$el.find('.toolbar').show(); 21 | }, 22 | addButtonClick: function(e) { 23 | if (this.form == null) { 24 | this.showForm(); 25 | } else { 26 | this.hideForm(); 27 | } 28 | e.preventDefault(); 29 | }, 30 | showForm: function() { 31 | this.form = new PostFormView(); 32 | this.form.on("form:hide", this.hideForm, this); 33 | this.form.render(); 34 | this.$el.append(this.form.el); 35 | }, 36 | hideForm: function() { 37 | if (this.form != null) { 38 | this.form.remove(); 39 | this.form = null; 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /part4/posts.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'json' 3 | require 'active_record' 4 | 5 | ActiveRecord::Base.include_root_in_json = false 6 | 7 | class Post < ActiveRecord::Base 8 | end 9 | 10 | Post.establish_connection( 11 | :adapter => "sqlite3", 12 | :database => "data.db" 13 | ) 14 | 15 | # Shows the index.html page, which contains the Backbone.js code 16 | get '/' do 17 | File.read(File.join('public', 'index.html')) 18 | end 19 | 20 | # Get all the posts from the database 21 | get '/posts' do 22 | content_type :json 23 | Post.all.to_json 24 | end 25 | 26 | # Get a post by id, from the database 27 | get 'posts/:id' do 28 | content_type :json 29 | post = Post.find params[:id] 30 | post.to_json 31 | end 32 | 33 | # Create a new post 34 | post '/posts' do 35 | content_type :json 36 | data = JSON.parse request.body.read 37 | 38 | post = Post.new 39 | post.title = data['title'] 40 | post.text = data['text'] 41 | 42 | post.save 43 | post.to_json 44 | end 45 | 46 | # Update an existing post 47 | put '/posts/:id' do 48 | data = JSON.parse request.body.read 49 | 50 | post = Post.find params[:id] 51 | post.title = data['title'] 52 | post.text = data['text'] 53 | 54 | post.save 55 | post.to_json 56 | end 57 | 58 | # Remove an existing post 59 | delete '/posts/:id' do 60 | Post.destroy params[:id] 61 | end 62 | 63 | Post.connection.close 64 | -------------------------------------------------------------------------------- /part5/blog/public/js/views/posts/PostView.js: -------------------------------------------------------------------------------- 1 | var PostView = Backbone.View.extend({ 2 | tagName: 'article', 3 | className: 'page-posts', 4 | template: $('#post-template').html(), 5 | events: { 6 | "click .remove-button": "removePost", 7 | "click .view-button": "showPost" 8 | }, 9 | initialize: function() { 10 | _.bindAll(this, 'render', 'removePost', 'remove'); 11 | this.model.on("change", this.render); 12 | this.model.on("destroy", this.remove); 13 | }, 14 | showPost: function(e) { 15 | router.navigate($(e.currentTarget).attr('href'), {trigger: true}); 16 | e.preventDefault(); 17 | }, 18 | render: function() { 19 | var viewContent = Mustache.to_html(this.template, this.model.toJSON()); 20 | this.$el.html(viewContent); 21 | if (this.model.isReaded()) { 22 | this.$el.find('h2').addClass('readed'); 23 | } 24 | return this; 25 | }, 26 | removePost: function(e) { 27 | e.preventDefault(); 28 | if (window.confirm('Do you relly want to remove this post?')) { 29 | this.model.destroy({ 30 | wait: true, 31 | success: function(model, response, options) { 32 | window.alert('Post removed successfully!'); 33 | router.navigate("/", {trigger: true}); 34 | } 35 | }); 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /part5/blog/public/js/AppRouter.js: -------------------------------------------------------------------------------- 1 | var AppRouter = Backbone.Router.extend({ 2 | routes: { 3 | "": "listAction", 4 | "post/:id": "viewAction" 5 | }, 6 | initialize: function() { 7 | if (!this.header) { 8 | this.header = new HeaderView(); 9 | $('#content').before(this.header.render().el); 10 | } 11 | }, 12 | listAction: function() { 13 | $('#content').html(''); 14 | 15 | this.header.showControls(); 16 | 17 | this.appView = new AppView(); 18 | 19 | Posts.bind('add', this.appView.addPost); 20 | Posts.bind('sync', this.appView.render); 21 | 22 | Posts.fetch(); 23 | }, 24 | viewAction: function(id) { 25 | var lastPost = new PostReaded(); 26 | lastPost.fetch(); 27 | lastPost.set({"post_id":id}); 28 | lastPost.save(); 29 | this.header.hideControls(); 30 | this.header.hideForm(); 31 | $('#content').html(''); 32 | var post = new PostModel({ 33 | id: id 34 | }); 35 | post.fetch({ 36 | success: function(postFetched) { 37 | var postView = new PostView({ 38 | model: postFetched 39 | }); 40 | postView.render(); 41 | $('#content').html(postView.el); 42 | } 43 | }); 44 | } 45 | }); 46 | 47 | var router = new AppRouter(); 48 | Backbone.history.start({pushState: true}); 49 | -------------------------------------------------------------------------------- /part5/blog/posts.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'json' 3 | require 'active_record' 4 | 5 | ActiveRecord::Base.include_root_in_json = false 6 | 7 | class Post < ActiveRecord::Base 8 | end 9 | 10 | Post.establish_connection( 11 | :adapter => "sqlite3", 12 | :database => "data.db" 13 | ) 14 | 15 | # Shows the index.html page, which contains the Backbone.js code 16 | get '/' do 17 | File.read(File.join('public', 'index.html')) 18 | end 19 | 20 | # Get all the posts from the database 21 | get '/posts' do 22 | content_type :json 23 | Post.all.to_json 24 | end 25 | 26 | # Get a post by id, from the database 27 | get '/posts/:id' do 28 | content_type :json 29 | post = Post.find params[:id] 30 | post.to_json 31 | end 32 | 33 | # Create a new post 34 | post '/posts' do 35 | content_type :json 36 | data = JSON.parse request.body.read 37 | 38 | post = Post.new 39 | post.title = data['title'] 40 | post.text = data['text'] 41 | 42 | post.save 43 | post.to_json 44 | end 45 | 46 | # Update an existing post 47 | put '/posts/:id' do 48 | data = JSON.parse request.body.read 49 | 50 | post = Post.find params[:id] 51 | post.title = data['title'] 52 | post.text = data['text'] 53 | 54 | post.save 55 | post.to_json 56 | end 57 | 58 | # Remove an existing post 59 | delete '/posts/:id' do 60 | post = Post.find params[:id] 61 | Post.destroy params[:id] 62 | post.to_json 63 | end 64 | 65 | Post.connection.close 66 | -------------------------------------------------------------------------------- /part3/public/js/views/PostFormView.js: -------------------------------------------------------------------------------- 1 | var PostFormView = Backbone.View.extend({ 2 | tagName: 'form', 3 | className: 'page-form', 4 | id: 'post-form', 5 | attributes: { 6 | action: 'posts', 7 | method: 'POST' 8 | }, 9 | events: { 10 | "submit" : "savePost" 11 | }, 12 | 13 | initialize: function(model) { 14 | _.bindAll(this, 'render', 'savePost', 'goToIndex'); 15 | 16 | this.template = $('#post-form').html(); 17 | this.model = new PostModel(); 18 | 19 | this.model.on("error", this.showError); 20 | this.model.on("sync", this.goToIndex); 21 | this.render(); 22 | }, 23 | 24 | render: function() { 25 | var rendered = Mustache.to_html(this.template); 26 | this.$el.html(rendered); 27 | 28 | this.titleInput = this.$el.find('#post-title'); 29 | this.textInput = this.$el.find('#post-text'); 30 | 31 | $('body').append(this.el); 32 | }, 33 | 34 | savePost: function(e) { 35 | e.preventDefault(); 36 | 37 | var title = this.titleInput.val(); 38 | var text = this.textInput.val(); 39 | 40 | this.model.set({ 41 | title: title, 42 | text: text 43 | }); 44 | 45 | if (this.model.isValid()) 46 | this.model.save(); 47 | }, 48 | 49 | showError:function(model, error) { 50 | window.alert('Ocorreu um erro, motivo: ' + error); 51 | }, 52 | 53 | goToIndex: function() { 54 | window.location = 'index.html'; 55 | } 56 | }); -------------------------------------------------------------------------------- /part5/blog/public/js/views/posts/PostFormView.js: -------------------------------------------------------------------------------- 1 | var PostFormView = Backbone.View.extend({ 2 | tagName: 'form', 3 | className: 'page-form', 4 | id: 'post-form', 5 | attributes: { 6 | action: 'posts', 7 | method: 'POST' 8 | }, 9 | events: { 10 | "submit": "savePost" 11 | }, 12 | initialize: function(model) { 13 | _.bindAll(this, 'render', 'savePost', 'postSaved'); 14 | this.template = $('#post-form').html(); 15 | }, 16 | render: function() { 17 | var rendered = Mustache.to_html(this.template); 18 | this.$el.html(rendered); 19 | 20 | this.titleInput = this.$el.find('#post-title'); 21 | this.textInput = this.$el.find('#post-text'); 22 | return this; 23 | }, 24 | savePost: function(e) { 25 | e.preventDefault(); 26 | 27 | this.model = new PostModel(); 28 | 29 | var title = this.titleInput.val(); 30 | var text = this.textInput.val(); 31 | 32 | this.model.set({ 33 | title: title, 34 | text: text 35 | }); 36 | 37 | if (this.model.isValid()) { 38 | Posts.create(this.model, { 39 | wait: true, 40 | success: this.postSaved 41 | }); 42 | Posts.sort(); 43 | } else { 44 | window.alert('An error has occurred when inserting the post, reason: ' + this.model.validationError); 45 | } 46 | }, 47 | postSaved: function() { 48 | window.alert('Post saved successfully!'); 49 | this.trigger("form:hide"); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /part4/samples/server.js: -------------------------------------------------------------------------------- 1 | var Post = Backbone.Model.extend({}); 2 | var PostList = Backbone.Collection.extend({ 3 | url: "/posts", 4 | model: Post 5 | }); 6 | 7 | var posts = new PostList(); 8 | posts.add({ 9 | id: 1, 10 | title: "Meu post", 11 | text: "Conteudo" 12 | }); 13 | 14 | // showing how the url attribute is used by Models 15 | var post = posts.get(1); 16 | console.log(post.url()); 17 | post.save(); 18 | 19 | // fetch() 20 | posts.on("reset", function() { 21 | console.log("Collection zerada"); 22 | }); 23 | posts.fetch({ 24 | success: function(collection, response) { 25 | console.log("A resposta foi: " + response); 26 | } 27 | }); 28 | 29 | // just append the server data, without reseting 30 | posts.on("reset", function() { 31 | console.log("Este método nunca será chamado pelo fetch com add true"); 32 | }); 33 | posts.fetch({ 34 | add: true, 35 | success: function(collection, response) { 36 | console.log("A resposta foi: " + response); 37 | } 38 | }); 39 | 40 | // jquery.ajax API 41 | posts.fetch({data: {page: 3}}); 42 | 43 | // clear the collection 44 | posts.reset(); 45 | 46 | // parse() 47 | var PostsWithRoot = Backbone.Collection.extend({ 48 | // A resposta é no formato {posts: []} 49 | parse: function(response) { 50 | return response.posts; 51 | } 52 | }); 53 | 54 | // create() 55 | var Posts = Backbone.Collection.extend({ 56 | url: "/posts", 57 | model: Post 58 | }); 59 | 60 | var posts = new Posts(); 61 | 62 | var newPost = new Post({ 63 | title: "Título do novo post", 64 | text: "Conteúdo do novo post" 65 | }); 66 | 67 | posts.create(newPost); -------------------------------------------------------------------------------- /part4/public/js/views/PostFormView.js: -------------------------------------------------------------------------------- 1 | var PostFormView = Backbone.View.extend({ 2 | tagName: 'form', 3 | className: 'page-form', 4 | id: 'post-form', 5 | attributes: { 6 | action: 'posts', 7 | method: 'POST' 8 | }, 9 | events: { 10 | "submit" : "savePost" 11 | }, 12 | 13 | initialize: function(model) { 14 | _.bindAll(this, 'render', 'savePost'); 15 | 16 | this.template = $('#post-form').html(); 17 | }, 18 | 19 | render: function() { 20 | var rendered = Mustache.to_html(this.template); 21 | this.$el.html(rendered); 22 | 23 | this.titleInput = this.$el.find('#post-title'); 24 | this.textInput = this.$el.find('#post-text'); 25 | 26 | this.hide(); 27 | }, 28 | 29 | savePost: function(e) { 30 | e.preventDefault(); 31 | 32 | this.model = new PostModel(); 33 | this.model.on("error", this.showError); 34 | 35 | var title = this.titleInput.val(); 36 | var text = this.textInput.val(); 37 | 38 | this.model.set({ 39 | title: title, 40 | text: text 41 | }); 42 | 43 | if (this.model.isValid()) { 44 | Posts.create(this.model, {wait: true}); 45 | this.hide(); 46 | Posts.sort(); 47 | } 48 | }, 49 | 50 | hide: function() { 51 | this.$el.hide(); 52 | }, 53 | 54 | show: function() { 55 | this.titleInput.val(''); 56 | this.textInput.val(''); 57 | this.$el.toggle(); 58 | }, 59 | 60 | showError:function(model, error) { 61 | window.alert('Ocorreu um erro, motivo: ' + error); 62 | }, 63 | }); -------------------------------------------------------------------------------- /part4/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone Tutorial - Part 4 Collections 6 | 7 | 8 |
9 |

Blog

10 | Novo Post 11 |
12 | 13 |
14 |
15 | 16 | 17 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | -------------------------------------------------------------------------------- /part3/basic/ModelsBasicUsage.js: -------------------------------------------------------------------------------- 1 | var PostModel = Backbone.Model.extend({}); 2 | 3 | var post = new PostModel({ 4 | title: 'Primeiro Post', 5 | text: 'Conteúdo do post' 6 | }); 7 | 8 | console.log(post.get('title')); // Primeiro Post 9 | console.log(post.get('text')); // Conteúdo do Post 10 | post.set({text: 'Novo conteúdo'}); 11 | console.log(post.get('text')); // Novo conteúdo 12 | 13 | // XSS 14 | post.set({ 15 | title: '

HTML

', 16 | text: '

' 17 | }); 18 | 19 | console.log(post.escape('text')); // <h2><script>alert("xss");</script></h2> 20 | 21 | // has, unset, clear 22 | if (post.has('title')) { 23 | post.unset('title'); 24 | } 25 | console.log(post.get('title')); // undefined 26 | post.clear(); 27 | console.log(post.get('text')); // undefined 28 | 29 | 30 | // Defaults 31 | var PostModel = Backbone.Model.extend({ 32 | defaults: { 33 | title: "", 34 | text: "" 35 | } 36 | }); 37 | var post = new PostModel(); 38 | console.log(post.get('title')); // 39 | var post = new PostModel({title: 'Meu Post'}); 40 | console.log(post.get('title')); // Meu Post 41 | console.log(post.get('text')); // 42 | 43 | // toJSON 44 | var post = new PostModel({title: "Primeiro Post"}); 45 | post.set({text: "conteudo"}); 46 | alert(JSON.stringify(post)); 47 | 48 | // id, cid, idAttribute 49 | console.log(post.id); // undefined 50 | 51 | var PostModel = Backbone.Model.extend({ 52 | idAttribute: 'timestamp' 53 | }); 54 | 55 | var post = new PostModel({timestamp: '201201'}); 56 | console.log('Id: ' + post.id); // Id: 201201 57 | console.log('Timestamp: ' + post.get('timestamp')); // Timestamp: 201201 58 | console.log('CID: ' + post.cid); // CID: c4 -------------------------------------------------------------------------------- /part5/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone.js Tutorial Series Samples - Part 5 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | Add | 18 | Help | 19 | Post 1 | 20 | Posts 2 3 | 21 | Posts 4 5 6 | 22 | Help no history 23 |

The file router.js shows how to use the Backbone.Router and Backbone.History components.

24 |

The file sync.js shows how to override the default Backbone.sync function.

25 |

The file events.js shows how to use the Events API from Backbone.

26 |

The file utils.js shows how to use Backbone.noConflict() and how to integrate jQuery in one Backbone installation and Zepto.js in another one.

27 |

Credits: Fernando Mantoan.

28 |
29 | 30 | -------------------------------------------------------------------------------- /part5/blog/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone.js Blog 6 | 7 | 8 | 9 |
10 |
11 | 17 | 25 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /part6/contacts-manager/app/styles/main.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "/bower_components/sass-bootstrap/fonts/"; 2 | 3 | @import 'sass-bootstrap/lib/bootstrap'; 4 | 5 | .browsehappy { 6 | margin: 0.2em 0; 7 | background: #ccc; 8 | color: #000; 9 | padding: 0.2em 0; 10 | } 11 | 12 | /* Space out content a bit */ 13 | body { 14 | padding-top: 20px; 15 | padding-bottom: 20px; 16 | } 17 | 18 | /* Everything but the jumbotron gets side spacing for mobile first views */ 19 | .header, 20 | .marketing, 21 | .footer { 22 | padding-left: 15px; 23 | padding-right: 15px; 24 | } 25 | 26 | /* Custom page header */ 27 | .header { 28 | border-bottom: 1px solid #e5e5e5; 29 | } 30 | 31 | /* Make the masthead heading the same height as the navigation */ 32 | .header h3 { 33 | margin-top: 0; 34 | margin-bottom: 0; 35 | line-height: 40px; 36 | padding-bottom: 19px; 37 | } 38 | 39 | /* Custom page footer */ 40 | .footer { 41 | padding-top: 19px; 42 | color: #777; 43 | border-top: 1px solid #e5e5e5; 44 | } 45 | 46 | .container-narrow > hr { 47 | margin: 30px 0; 48 | } 49 | 50 | /* Main marketing message and sign up button */ 51 | .jumbotron { 52 | text-align: center; 53 | border-bottom: 1px solid #e5e5e5; 54 | } 55 | 56 | .jumbotron .btn { 57 | font-size: 21px; 58 | padding: 14px 24px; 59 | } 60 | 61 | /* Supporting marketing content */ 62 | .marketing { 63 | margin: 40px 0; 64 | } 65 | 66 | .marketing p + h4 { 67 | margin-top: 28px; 68 | } 69 | 70 | /* Responsive: Portrait tablets and up */ 71 | @media screen and (min-width: 768px) { 72 | .container { 73 | max-width: 730px; 74 | } 75 | 76 | /* Remove the padding we set earlier */ 77 | .header, 78 | .marketing, 79 | .footer { 80 | padding-left: 0; 81 | padding-right: 0; 82 | } 83 | /* Space out the masthead */ 84 | .header { 85 | margin-bottom: 30px; 86 | } 87 | /* Remove the bottom border on the jumbotron for visual effect */ 88 | .jumbotron { 89 | border-bottom: 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backbone.js Tutorial Series 2 | 3 | This repository will store the code used in the Backbone.js tutorial series. 4 | 5 | ## Part 1 6 | 7 | The Part 1 of the article gives a simple introduction of the framework Backbone.js, showing a simple "Hello World" View. 8 | 9 | **Permalink**: [http://blog.fernandomantoan.com/serie-backbone-js-parte-1-introducao/](http://blog.fernandomantoan.com/serie-backbone-js-parte-1-introducao/) 10 | 11 | ## Part 2 12 | 13 | This part is focused on introducing and explaining the Backbone.View class, showing practical samples and a full functional code using templates and events. 14 | 15 | **Permalink**: [http://blog.fernandomantoan.com/serie-backbone-js-parte-2-view/](http://blog.fernandomantoan.com/serie-backbone-js-parte-2-view/) 16 | 17 | ## Part 3 18 | 19 | The Part 3 of the series shows how to use Backbone.Model, all of its functionality and an integration of the Model with the View created in Part 2. A simple Sinatra backend is written, to show how Backbone.Model uses a RESTful API with the Backbone.sync() methods to make persistence of data and syncronization. 20 | 21 | **Permalink**: [http://blog.fernandomantoan.com/serie-backbone-js-parte-3-model/](http://blog.fernandomantoan.com/serie-backbone-js-parte-3-model/) 22 | 23 | ## Part 4 24 | 25 | This part shows the Backbone.Collection class, and increments the simple blog developed through the series, showing how to build a list of the Posts, how to iterate and use Backbone.Collection with all the Array functionalities of Underscore.js. A new View is built to support the blog posts listing and some other resources are implemented, also the Sinatra backend is modified to add the Posts listing support. 26 | 27 | **Permalink**: [http://blog.fernandomantoan.com/serie-backbone-js-parte-4-collection/](http://blog.fernandomantoan.com/serie-backbone-js-parte-4-collection/) 28 | 29 | ## Part 5 30 | 31 | The Part 5 of the series is the last one introducing features and components of the framework. It will show the Backbone.Router class along with Backbone.History, adding some routes to the blog developed and the usage of the pushState HTML5 API. Also the Backbone.sync function is presented, with some examples of customization, and some more details about the events API of the framework. 32 | 33 | **Permalink**: [http://blog.fernandomantoan.com/serie-backbone-js-parte-5-router-historico-backbone-sync-eventos-e-mais/](http://blog.fernandomantoan.com/serie-backbone-js-parte-5-router-historico-backbone-sync-eventos-e-mais/) 34 | 35 | ## Part 6 36 | This is the last part of the Backbone.js tutorial series, focusing in a deep practical experience on how to build an application from scratch, using good practices, other JS libraries and a PHP backend. Some topics covered: 37 | 38 | * Laravel Framework 39 | * Models 40 | * Views 41 | * Underscore.js 42 | * Require.js 43 | * JWT Authentication 44 | * Twitter Bootstrap. 45 | 46 | **Permalink**: N/A 47 | -------------------------------------------------------------------------------- /part5/blog/public/libs/js/backbone.localStorage-min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Backbone localStorage Adapter 3 | * Version 1.1.6 4 | * 5 | * https://github.com/jeromegn/Backbone.localStorage 6 | */(function(a,b){typeof exports=="object"&&a.require?module.exports=b(require("underscore"),require("backbone")):typeof define=="function"&&define.amd?define(["underscore","backbone"],function(c,d){return b(c||a._,d||a.Backbone)}):b(_,Backbone)})(this,function(a,b){function c(){return((1+Math.random())*65536|0).toString(16).substring(1)}function d(){return c()+c()+"-"+c()+"-"+c()+"-"+c()+"-"+c()+c()+c()}return b.LocalStorage=window.Store=function(a){if(!this.localStorage)throw"Backbone.localStorage: Environment does not support localStorage.";this.name=a;var b=this.localStorage().getItem(this.name);this.records=b&&b.split(",")||[]},a.extend(b.LocalStorage.prototype,{save:function(){this.localStorage().setItem(this.name,this.records.join(","))},create:function(a){return a.id||(a.id=d(),a.set(a.idAttribute,a.id)),this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),this.records.push(a.id.toString()),this.save(),this.find(a)},update:function(b){return this.localStorage().setItem(this.name+"-"+b.id,JSON.stringify(b)),a.include(this.records,b.id.toString())||this.records.push(b.id.toString()),this.save(),this.find(b)},find:function(a){return this.jsonData(this.localStorage().getItem(this.name+"-"+a.id))},findAll:function(){return(a.chain||a)(this.records).map(function(a){return this.jsonData(this.localStorage().getItem(this.name+"-"+a))},this).compact().value()},destroy:function(b){return b.isNew()?!1:(this.localStorage().removeItem(this.name+"-"+b.id),this.records=a.reject(this.records,function(a){return a===b.id.toString()}),this.save(),b)},localStorage:function(){return localStorage},jsonData:function(a){return a&&JSON.parse(a)},_clear:function(){var b=this.localStorage(),c=new RegExp("^"+this.name+"-");b.removeItem(this.name),(a.chain||a)(b).keys().filter(function(a){return c.test(a)}).each(function(a){b.removeItem(a)}),this.records.length=0},_storageSize:function(){return this.localStorage().length}}),b.LocalStorage.sync=window.Store.sync=b.localSync=function(a,c,d){var e=c.localStorage||c.collection.localStorage,f,g,h=b.$.Deferred&&b.$.Deferred();try{switch(a){case"read":f=c.id!=undefined?e.find(c):e.findAll();break;case"create":f=e.create(c);break;case"update":f=e.update(c);break;case"delete":f=e.destroy(c)}}catch(i){i.code===22&&e._storageSize()===0?g="Private browsing is unsupported":g=i.message}return f?(d&&d.success&&(b.VERSION==="0.9.10"?d.success(c,f,d):d.success(f)),h&&h.resolve(f)):(g=g?g:"Record Not Found",d&&d.error&&(b.VERSION==="0.9.10"?d.error(c,g,d):d.error(g)),h&&h.reject(g)),d&&d.complete&&d.complete(f),h&&h.promise()},b.ajaxSync=b.sync,b.getSyncMethod=function(a){return a.localStorage||a.collection&&a.collection.localStorage?b.localSync:b.ajaxSync},b.sync=function(a,c,d){return b.getSyncMethod(c).apply(this,[a,c,d])},b.LocalStorage}); 7 | -------------------------------------------------------------------------------- /part3/public/js/basic_samples/PostModel.js: -------------------------------------------------------------------------------- 1 | var PostModel = Backbone.Model.extend({ 2 | urlRoot: 'posts', 3 | defaults: { 4 | title: "", 5 | text: "" 6 | }, 7 | validate: function(attrs) { 8 | if (attrs.title == '') 9 | return 'O título é obrigatório'; 10 | if (attrs.text == '') 11 | return 'O texto é obrigatório' 12 | } 13 | }); 14 | 15 | post = new PostModel(); 16 | 17 | post.fetch(); 18 | 19 | post.fetch({ 20 | success: function(model, response) { 21 | console.log(model.get('title')); 22 | }, 23 | error: function(model, response) { 24 | window.alert('Ocorreu um erro'); 25 | } 26 | }); 27 | 28 | var post = new PostModel({ 29 | title: "First Post", 30 | text: "Text of the post" 31 | }); 32 | post.save(); 33 | 34 | var post = new PostModel({ 35 | id: 1, 36 | title: "First Post", 37 | text: "Text of the post" 38 | }); 39 | post.save(); 40 | 41 | var post = new PostModel(); 42 | // Obtém a última postagem 43 | post.fetch({ 44 | success: function(model, response) { 45 | // Será atualizado somente o atributo "title" 46 | post.save({ 47 | title: "Atualizar o titulo" 48 | }); 49 | } 50 | }); 51 | 52 | var post = new PostModel({ 53 | id: 1, 54 | title: "First Post", 55 | text: "Text of the post" 56 | }); 57 | post.save( 58 | null, 59 | { 60 | success: function (model, response) { 61 | console.log(model.get('title')); 62 | }, 63 | error: function (model, response) { 64 | window.alert('Ocorreu um erro'); 65 | } 66 | } 67 | ); 68 | 69 | var post = new PostModel({ 70 | id: 1 71 | }); 72 | post.destroy({ 73 | success: function(model, response) { 74 | console.log('Postagem removida com sucesso'); 75 | }, 76 | error: function(model, response) { 77 | window.alert('Ocorreu um erro'); 78 | } 79 | }); 80 | 81 | var post = new PostModel(); 82 | post.on('change', function() { 83 | console.log('O evento change foi disparado'); 84 | }); 85 | post.on('sync', function() { 86 | console.log('O evento sync foi disparado'); 87 | }); 88 | post.set({ 89 | title: "Um titulo", 90 | text: "Um texto " 91 | }); // Dispara change 92 | post.fetch(); // Dispara change e sync 93 | 94 | 95 | var post = new PostModel(); 96 | post.on('change', function() { 97 | if (post.hasChanged('title')) 98 | console.log('o atributo titulo foi alterado'); 99 | else 100 | console.log('o atributo titulo não foi alterado'); 101 | }); 102 | post.set({ 103 | title: 'Evento manual', 104 | text: 'Texto' 105 | }, { 106 | silent: true 107 | }); 108 | post.change(); 109 | 110 | var post = new PostModel({ 111 | title: 'Valor antigo', 112 | text: 'Texto antigo' 113 | }); 114 | 115 | post.on('change', function() { 116 | if (post.hasChanged('title')) { 117 | console.log('o atributo titulo foi alterado'); 118 | console.log(post.previous('title')); 119 | console.log(post.get('title')); 120 | } 121 | }); 122 | post.set({title: 'Novo valor'}); 123 | post.change(); 124 | 125 | 126 | var PostModel = Backbone.Model.extend({ 127 | urlRoot: 'posts', 128 | defaults: { 129 | title: "", 130 | text: "" 131 | }, 132 | validate: function(attrs) { 133 | if (attrs.title == '') 134 | return 'O título é obrigatório'; 135 | if (attrs.text == '') 136 | return 'O texto é obrigatório' 137 | } 138 | }); 139 | 140 | var post = new PostModel(); 141 | post.on('error', function(model, error) { 142 | alert(error); 143 | }); 144 | post.save(); 145 | 146 | var post = new PostModel(); 147 | post.on('error', function(model, error) { 148 | if (!model.isValid()) 149 | alert('Erro de validação: ' + error); 150 | else 151 | alert(error); 152 | }); 153 | post.save(); -------------------------------------------------------------------------------- /part6/contacts-manager/app/favicon.ico: -------------------------------------------------------------------------------- 1 |   �( @   -2Op"=p�Jt��Jt��b���������������������������������������������������b���Jt��Jt��"=p�Op-2O`O�O�O�O�O�O�O� $\�Jt��������������v���v���������������Jt�� $\�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� ;n�s���>���>���>���>���s��� ;n�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� $\�]���^n��^n��]��� $\�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O�O�n�*��*��n�O�O�O�O�O�O�O�O�O�O�O�  O�O�O�O�O�O�O�O�O�O�O�5>Y�5>Y�O�O�O�O�O�O�O�O�O�O�O�  -2O�O�O�O�O�O�O�O�O�O�&6e�&6e�O�O�O�O�O�O�O�O�O�O�-25r�4���E��� $\�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O� $\�E���4���5r�5r�E���M���M���v���0\��O�O�O�O�O�O�O� $\� $\�O�O�O�O�O�O�O�0\��v���M���M���E���5r�)��p&��p��&��������������b���Jt��Jt��Jt��0\��#i��.r��.r��#i��0\��Jt��Jt��Jt��b���������������&��p��&��)��p4���&��-���_������������������]���]�������7���p�����������p���7�������]���]�������������������_��-���-���4���qֈp��p��p����������������������p���7���#i��p�����������p���#i��7���p�����������������������p��&��-���qֈ8��(p��p��I���v���v���]���7���n���v���p���#i��]���v���v���]���#i��p���v���n���7���]���v���v���I���-���-���8��(;��`-���M���7���7���7���.r��R��E��R��E��7���7���7���7���E��R��E��R��.r��7���7���7���M���M���;��`���������������������������z��������������������������� 2 | �  ��� 3 | � 9� 9� 9� 9� 9� 9� 9� 9� 4 |  �n�n� 5 |  � 9� 9� 9� 9� 9� 9� 9� 9� 6 | ����*�x*��*��*��*��*��*��*��n�&��#��&��&��n�*��*��*��*��*��*��*��*�x*ݟ*��*��*��*��*��*��!��#��&��#��&��*��!��!��*��*��*��*��*��*��*ݟ*ݿ*��*��*��*��*��*��n�*��*�� 9� 9�*��*���*��*��*��*��*��*��*ݿ*��*��*��*��*��*��*��!��#��&��&��&��*��#��!��*��*��*��*��*��*��*��  ��������I�&��&��&��&��I���������  U��������� 7 |  �n�n� 8 |  ����������-2z����������������������z������������������������ 9 | ����������������������� 10 | ������������������������� 11 | ������������������������-2����������������������U�������������������z5r������������������-25r�U�����������z  ������������������������������?��� -------------------------------------------------------------------------------- /part4/samples/samples.js: -------------------------------------------------------------------------------- 1 | // basic 2 | var Posts = Backbone.Collection.extend({ 3 | model: PostModel 4 | }); 5 | 6 | // toJSON 7 | var myPosts = new Posts([ 8 | {title: "Post um", text: "Conteúdo do Post um"}, 9 | {title: "Post dois", text: "Conteúdo do Post dois"}, 10 | {title: "Post três", text: "Conteúdo do Post três"}, 11 | ]); 12 | console.log(JSON.stringify(myPosts)); 13 | 14 | // add() 15 | var posts = new Posts(); 16 | posts.on("add", function(model, collection, options) { 17 | console.log("O model " + model.get('title') + " foi adicionado na posição " + options.index); 18 | }); 19 | posts.add([ 20 | {title: "Post um", text: "Conteúdo do post um"}, 21 | {title: "Post dois", text: "Conteúdo do post dois"} 22 | ]); 23 | 24 | posts.add({ 25 | title: "Post tres", text: "Post tres" 26 | }, { 27 | at: 0 28 | }); 29 | 30 | // remove() 31 | var posts = new Posts(); 32 | posts.on("remove", function(model, collection, options) { 33 | console.log("O model " + model.get('title') + " foi removido da posição " + options.index); 34 | }); 35 | var models = [ 36 | { 37 | id: 1, title: "Post um", text: "Conteúdo do post um" 38 | }, 39 | { 40 | id: 2, title: "Post dois", text: "Conteúdo do post dois" 41 | } 42 | ]; 43 | posts.add(models); 44 | posts.remove({id: 1}); 45 | 46 | // get() 47 | var post = posts.get(2); 48 | console.log(JSON.stringify(post)); 49 | 50 | // getByCid() 51 | posts.add({title: "Post um", text: "Conteúdo do post um"}); 52 | var post = posts.getByCid('c8'); 53 | console.log(JSON.stringify(post)); 54 | 55 | // at() 56 | var post = posts.at(0); 57 | console.log(JSON.stringify(post)); 58 | 59 | // push() 60 | posts.push({title: "Novo post", text: "Post adicionado"}); 61 | 62 | // pop() 63 | console.log(posts.length); 64 | postRemoved = posts.pop(); 65 | console.log(posts.length); 66 | console.log(postRemoved.get('title')); 67 | 68 | // unshift() 69 | posts.unshift({title: "Novo post", text: "Post adicionado"}); 70 | 71 | // shift() 72 | console.log(posts.length); 73 | postRemoved = posts.shift(); 74 | console.log(posts.length); 75 | console.log(postRemoved.get('title')); 76 | 77 | // length 78 | console.log("Existem " + posts.length + " postagens na coleção."); 79 | 80 | // comparator sortBy() 81 | var posts = new Backbone.Collection; 82 | posts.comparator = function(post) { 83 | return post.get('username'); 84 | }; 85 | posts.add({title: "Postagem 1", text: "Minha postagem", username: "Fernando"}); 86 | posts.add({title: "Postagem 2", text: "Minha postagem 2", username: "Guest"}); 87 | posts.add({title: "Postagem 3", text: "Minha postagem 3", username: "Fernando"}); 88 | 89 | console.log(JSON.stringify(posts)); 90 | 91 | // comparator sort() 92 | var orders = new Backbone.Collection; 93 | orders.comparator = function(firstModel, secondModel) { 94 | if (firstModel.get('count') < secondModel.get('count')) 95 | return -1; 96 | else if (firstModel.get('count') > secondModel.get('count')) 97 | return 1; 98 | return 0; 99 | }; 100 | 101 | orders.add({count: 1}); 102 | orders.add({count: 3}); 103 | orders.add({count: 2}); 104 | orders.add({count: 2}); 105 | orders.add({count: 5}); 106 | orders.add({count: 4}); 107 | 108 | console.log(JSON.stringify(orders)); 109 | 110 | // forcing sort() 111 | var orders = new Backbone.Collection; 112 | orders.comparator = function(firstModel, secondModel) { 113 | if (firstModel.get('count') < secondModel.get('count')) 114 | return -1; 115 | else if (firstModel.get('count') > secondModel.get('count')) 116 | return 1; 117 | return 0; 118 | }; 119 | orders.add({count: 1}); 120 | orders.add({count: 3}); 121 | orders.add({count: 2}); 122 | orders.add({count: 2}); 123 | orders.add({count: 5}); 124 | orders.add({count: 4}); 125 | console.log(JSON.stringify(orders)); 126 | orders.on("reset", function() { 127 | console.log("Collection reseted"); 128 | }); 129 | orders.sort(); 130 | 131 | // pluck() 132 | var users = posts.pluck('username'); 133 | console.log("The users of the blog are: " + JSON.stringify(users)); 134 | 135 | // where() 136 | var guestPosts = posts.where({username: 'Guest'}); 137 | console.log("The guest posts are: " + JSON.stringify(guestPosts)); -------------------------------------------------------------------------------- /part6/contacts-manager/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | contacts manager 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 |
27 |
28 | 33 |

contacts manager

34 |
35 | 36 |
37 |

'Allo, 'Allo!

38 |

Always a pleasure scaffolding your apps.

39 |

Splendid!

40 |
41 | 42 |
43 |
44 |

Now Try,

45 |

yo backbone:router <router>

46 |

yo backbone:collection <collection>

47 |

yo backbone:model <model>

48 |

yo backbone:view <view>

49 |
50 |
51 | 52 |
53 |
54 |

HTML5 Boilerplate

55 |

HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.

56 | 57 |

Bootstrap

58 |

Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.

59 | 60 |

Modernizr

61 |

Modernizr is an open-source JavaScript library that helps you build the next generation of HTML5 and CSS3-powered websites.

62 | 63 |
64 |
65 |

Backbone.js

66 |

Backbone.js gives structure to web applications by providing models, collections, views and connects it all to your existing API over a RESTful JSON interface.

67 | 68 |

Underscore.js

69 |

Underscore.js is a utility-belt library for JavaScript that provides a lot of the functional programming support

70 | 71 |

RequireJS

72 |

RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node.

73 | 74 |
75 |
76 | 77 | 80 | 81 |
82 | 83 | 84 | 85 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /part6/contacts-manager/test/lib/mocha/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | padding: 60px 50px; 6 | } 7 | 8 | #mocha ul, #mocha li { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | #mocha ul { 14 | list-style: none; 15 | } 16 | 17 | #mocha h1, #mocha h2 { 18 | margin: 0; 19 | } 20 | 21 | #mocha h1 { 22 | margin-top: 15px; 23 | font-size: 1em; 24 | font-weight: 200; 25 | } 26 | 27 | #mocha h1 a { 28 | text-decoration: none; 29 | color: inherit; 30 | } 31 | 32 | #mocha h1 a:hover { 33 | text-decoration: underline; 34 | } 35 | 36 | #mocha .suite .suite h1 { 37 | margin-top: 0; 38 | font-size: .8em; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | #mocha h2 { 46 | font-size: 12px; 47 | font-weight: normal; 48 | cursor: pointer; 49 | } 50 | 51 | #mocha .suite { 52 | margin-left: 15px; 53 | } 54 | 55 | #mocha .test { 56 | margin-left: 15px; 57 | overflow: hidden; 58 | } 59 | 60 | #mocha .test.pending:hover h2::after { 61 | content: '(pending)'; 62 | font-family: arial; 63 | } 64 | 65 | #mocha .test.pass.medium .duration { 66 | background: #C09853; 67 | } 68 | 69 | #mocha .test.pass.slow .duration { 70 | background: #B94A48; 71 | } 72 | 73 | #mocha .test.pass::before { 74 | content: '✓'; 75 | font-size: 12px; 76 | display: block; 77 | float: left; 78 | margin-right: 5px; 79 | color: #00d6b2; 80 | } 81 | 82 | #mocha .test.pass .duration { 83 | font-size: 9px; 84 | margin-left: 5px; 85 | padding: 2px 5px; 86 | color: white; 87 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 88 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 89 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | -ms-border-radius: 5px; 93 | -o-border-radius: 5px; 94 | border-radius: 5px; 95 | } 96 | 97 | #mocha .test.pass.fast .duration { 98 | display: none; 99 | } 100 | 101 | #mocha .test.pending { 102 | color: #0b97c4; 103 | } 104 | 105 | #mocha .test.pending::before { 106 | content: '◦'; 107 | color: #0b97c4; 108 | } 109 | 110 | #mocha .test.fail { 111 | color: #c00; 112 | } 113 | 114 | #mocha .test.fail pre { 115 | color: black; 116 | } 117 | 118 | #mocha .test.fail::before { 119 | content: '✖'; 120 | font-size: 12px; 121 | display: block; 122 | float: left; 123 | margin-right: 5px; 124 | color: #c00; 125 | } 126 | 127 | #mocha .test pre.error { 128 | color: #c00; 129 | max-height: 300px; 130 | overflow: auto; 131 | } 132 | 133 | #mocha .test pre { 134 | display: block; 135 | float: left; 136 | clear: left; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | -moz-border-radius: 3px; 145 | -moz-box-shadow: 0 1px 3px #eee; 146 | } 147 | 148 | #mocha .test h2 { 149 | position: relative; 150 | } 151 | 152 | #mocha .test a.replay { 153 | position: absolute; 154 | top: 3px; 155 | right: 0; 156 | text-decoration: none; 157 | vertical-align: middle; 158 | display: block; 159 | width: 15px; 160 | height: 15px; 161 | line-height: 15px; 162 | text-align: center; 163 | background: #eee; 164 | font-size: 15px; 165 | -moz-border-radius: 15px; 166 | border-radius: 15px; 167 | -webkit-transition: opacity 200ms; 168 | -moz-transition: opacity 200ms; 169 | transition: opacity 200ms; 170 | opacity: 0.3; 171 | color: #888; 172 | } 173 | 174 | #mocha .test:hover a.replay { 175 | opacity: 1; 176 | } 177 | 178 | #mocha-report.pass .test.fail { 179 | display: none; 180 | } 181 | 182 | #mocha-report.fail .test.pass { 183 | display: none; 184 | } 185 | 186 | #mocha-error { 187 | color: #c00; 188 | font-size: 1.5 em; 189 | font-weight: 100; 190 | letter-spacing: 1px; 191 | } 192 | 193 | #mocha-stats { 194 | position: fixed; 195 | top: 15px; 196 | right: 10px; 197 | font-size: 12px; 198 | margin: 0; 199 | color: #888; 200 | } 201 | 202 | #mocha-stats .progress { 203 | float: right; 204 | padding-top: 0; 205 | } 206 | 207 | #mocha-stats em { 208 | color: black; 209 | } 210 | 211 | #mocha-stats a { 212 | text-decoration: none; 213 | color: inherit; 214 | } 215 | 216 | #mocha-stats a:hover { 217 | border-bottom: 1px solid #eee; 218 | } 219 | 220 | #mocha-stats li { 221 | display: inline-block; 222 | margin: 0 5px; 223 | list-style: none; 224 | padding-top: 11px; 225 | } 226 | 227 | code .comment { color: #ddd } 228 | code .init { color: #2F6FAD } 229 | code .string { color: #5890AD } 230 | code .keyword { color: #8A6343 } 231 | code .number { color: #2F6FAD } 232 | -------------------------------------------------------------------------------- /part6/contacts-manager/app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /part3/public/lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.3 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break; 10 | g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a, 11 | c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==null&&(a=[]);if(A&& 12 | a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a, 13 | c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b, 14 | a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]}; 17 | j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a= 20 | i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&& 25 | c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty= 26 | function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"}; 27 | b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a, 28 | b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId= 29 | function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape|| 30 | u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c}; 31 | b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d, 32 | this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); -------------------------------------------------------------------------------- /part4/public/lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.3 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break; 10 | g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a, 11 | c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==null&&(a=[]);if(A&& 12 | a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a, 13 | c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b, 14 | a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]}; 17 | j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a= 20 | i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&& 25 | c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty= 26 | function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"}; 27 | b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a, 28 | b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId= 29 | function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape|| 30 | u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c}; 31 | b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d, 32 | this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); -------------------------------------------------------------------------------- /part5/blog/public/libs/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -------------------------------------------------------------------------------- /part6/contacts-manager/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var LIVERELOAD_PORT = 35729; 3 | var SERVER_PORT = 9000; 4 | var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT}); 5 | var mountFolder = function (connect, dir) { 6 | return connect.static(require('path').resolve(dir)); 7 | }; 8 | 9 | // # Globbing 10 | // for performance reasons we're only matching one level down: 11 | // 'test/spec/{,*/}*.js' 12 | // use this if you want to match all subfolders: 13 | // 'test/spec/**/*.js' 14 | // templateFramework: 'lodash' 15 | 16 | module.exports = function (grunt) { 17 | // show elapsed time at the end 18 | require('time-grunt')(grunt); 19 | // load all grunt tasks 20 | require('load-grunt-tasks')(grunt); 21 | 22 | // configurable paths 23 | var yeomanConfig = { 24 | app: 'app', 25 | dist: 'dist' 26 | }; 27 | 28 | grunt.initConfig({ 29 | yeoman: yeomanConfig, 30 | watch: { 31 | options: { 32 | nospawn: true, 33 | livereload: true 34 | }, 35 | coffee: { 36 | files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], 37 | tasks: ['coffee:dist'] 38 | }, 39 | coffeeTest: { 40 | files: ['test/spec/{,*/}*.coffee'], 41 | tasks: ['coffee:test'] 42 | }, 43 | compass: { 44 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 45 | tasks: ['compass'] 46 | }, 47 | livereload: { 48 | options: { 49 | livereload: LIVERELOAD_PORT 50 | }, 51 | files: [ 52 | '<%= yeoman.app %>/*.html', 53 | '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', 54 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', 55 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', 56 | '<%= yeoman.app %>/scripts/templates/*.{ejs,mustache,hbs}', 57 | 'test/spec/**/*.js' 58 | ] 59 | }, 60 | jst: { 61 | files: [ 62 | '<%= yeoman.app %>/scripts/templates/*.ejs' 63 | ], 64 | tasks: ['jst'] 65 | }, 66 | test: { 67 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js', 'test/spec/**/*.js'], 68 | tasks: ['test'] 69 | } 70 | }, 71 | connect: { 72 | options: { 73 | port: SERVER_PORT, 74 | // change this to '0.0.0.0' to access the server from outside 75 | hostname: 'localhost' 76 | }, 77 | livereload: { 78 | options: { 79 | middleware: function (connect) { 80 | return [ 81 | lrSnippet, 82 | mountFolder(connect, '.tmp'), 83 | mountFolder(connect, yeomanConfig.app) 84 | ]; 85 | } 86 | } 87 | }, 88 | test: { 89 | options: { 90 | port: 9001, 91 | middleware: function (connect) { 92 | return [ 93 | lrSnippet, 94 | mountFolder(connect, '.tmp'), 95 | mountFolder(connect, 'test'), 96 | mountFolder(connect, yeomanConfig.app) 97 | ]; 98 | } 99 | } 100 | }, 101 | dist: { 102 | options: { 103 | middleware: function (connect) { 104 | return [ 105 | mountFolder(connect, yeomanConfig.dist) 106 | ]; 107 | } 108 | } 109 | } 110 | }, 111 | open: { 112 | server: { 113 | path: 'http://localhost:<%= connect.options.port %>' 114 | } 115 | }, 116 | clean: { 117 | dist: ['.tmp', '<%= yeoman.dist %>/*'], 118 | server: '.tmp' 119 | }, 120 | jshint: { 121 | options: { 122 | jshintrc: '.jshintrc', 123 | reporter: require('jshint-stylish') 124 | }, 125 | all: [ 126 | 'Gruntfile.js', 127 | '<%= yeoman.app %>/scripts/{,*/}*.js', 128 | '!<%= yeoman.app %>/scripts/vendor/*', 129 | 'test/spec/{,*/}*.js' 130 | ] 131 | }, 132 | mocha: { 133 | all: { 134 | options: { 135 | run: true, 136 | urls: ['http://localhost:<%= connect.test.options.port %>/index.html'] 137 | } 138 | } 139 | }, 140 | coffee: { 141 | dist: { 142 | files: [{ 143 | // rather than compiling multiple files here you should 144 | // require them into your main .coffee file 145 | expand: true, 146 | cwd: '<%= yeoman.app %>/scripts', 147 | src: '{,*/}*.coffee', 148 | dest: '.tmp/scripts', 149 | ext: '.js' 150 | }] 151 | }, 152 | test: { 153 | files: [{ 154 | expand: true, 155 | cwd: 'test/spec', 156 | src: '{,*/}*.coffee', 157 | dest: '.tmp/spec', 158 | ext: '.js' 159 | }] 160 | } 161 | }, 162 | compass: { 163 | options: { 164 | sassDir: '<%= yeoman.app %>/styles', 165 | cssDir: '.tmp/styles', 166 | imagesDir: '<%= yeoman.app %>/images', 167 | javascriptsDir: '<%= yeoman.app %>/scripts', 168 | fontsDir: '<%= yeoman.app %>/styles/fonts', 169 | importPath: '<%= yeoman.app %>/bower_components', 170 | relativeAssets: true 171 | }, 172 | dist: {}, 173 | server: { 174 | options: { 175 | debugInfo: true 176 | } 177 | } 178 | }, 179 | requirejs: { 180 | dist: { 181 | // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js 182 | options: { 183 | // `name` and `out` is set by grunt-usemin 184 | baseUrl: '<%= yeoman.app %>/scripts', 185 | optimize: 'none', 186 | paths: { 187 | 'templates': '../../.tmp/scripts/templates' 188 | }, 189 | // TODO: Figure out how to make sourcemaps work with grunt-usemin 190 | // https://github.com/yeoman/grunt-usemin/issues/30 191 | //generateSourceMaps: true, 192 | // required to support SourceMaps 193 | // http://requirejs.org/docs/errors.html#sourcemapcomments 194 | preserveLicenseComments: false, 195 | useStrict: true, 196 | wrap: true 197 | //uglify2: {} // https://github.com/mishoo/UglifyJS2 198 | } 199 | } 200 | }, 201 | useminPrepare: { 202 | html: '<%= yeoman.app %>/index.html', 203 | options: { 204 | dest: '<%= yeoman.dist %>' 205 | } 206 | }, 207 | usemin: { 208 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 209 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 210 | options: { 211 | dirs: ['<%= yeoman.dist %>'] 212 | } 213 | }, 214 | imagemin: { 215 | dist: { 216 | files: [{ 217 | expand: true, 218 | cwd: '<%= yeoman.app %>/images', 219 | src: '{,*/}*.{png,jpg,jpeg}', 220 | dest: '<%= yeoman.dist %>/images' 221 | }] 222 | } 223 | }, 224 | cssmin: { 225 | dist: { 226 | files: { 227 | '<%= yeoman.dist %>/styles/main.css': [ 228 | '.tmp/styles/{,*/}*.css', 229 | '<%= yeoman.app %>/styles/{,*/}*.css' 230 | ] 231 | } 232 | } 233 | }, 234 | htmlmin: { 235 | dist: { 236 | options: { 237 | /*removeCommentsFromCDATA: true, 238 | // https://github.com/yeoman/grunt-usemin/issues/44 239 | //collapseWhitespace: true, 240 | collapseBooleanAttributes: true, 241 | removeAttributeQuotes: true, 242 | removeRedundantAttributes: true, 243 | useShortDoctype: true, 244 | removeEmptyAttributes: true, 245 | removeOptionalTags: true*/ 246 | }, 247 | files: [{ 248 | expand: true, 249 | cwd: '<%= yeoman.app %>', 250 | src: '*.html', 251 | dest: '<%= yeoman.dist %>' 252 | }] 253 | } 254 | }, 255 | copy: { 256 | dist: { 257 | files: [{ 258 | expand: true, 259 | dot: true, 260 | cwd: '<%= yeoman.app %>', 261 | dest: '<%= yeoman.dist %>', 262 | src: [ 263 | '*.{ico,txt}', 264 | '.htaccess', 265 | 'images/{,*/}*.{webp,gif}', 266 | 'styles/fonts/{,*/}*.*', 267 | 'bower_components/sass-bootstrap/fonts/*.*' 268 | ] 269 | }] 270 | } 271 | }, 272 | bower: { 273 | all: { 274 | rjsConfig: '<%= yeoman.app %>/scripts/main.js' 275 | } 276 | }, 277 | jst: { 278 | options: { 279 | amd: true 280 | }, 281 | compile: { 282 | files: { 283 | '.tmp/scripts/templates.js': ['<%= yeoman.app %>/scripts/templates/*.ejs'] 284 | } 285 | } 286 | }, 287 | rev: { 288 | dist: { 289 | files: { 290 | src: [ 291 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 292 | '<%= yeoman.dist %>/styles/{,*/}*.css', 293 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', 294 | '/styles/fonts/{,*/}*.*', 295 | 'bower_components/sass-bootstrap/fonts/*.*' 296 | ] 297 | } 298 | } 299 | } 300 | }); 301 | 302 | grunt.registerTask('createDefaultTemplate', function () { 303 | grunt.file.write('.tmp/scripts/templates.js', 'this.JST = this.JST || {};'); 304 | }); 305 | 306 | grunt.registerTask('server', function (target) { 307 | if (target === 'dist') { 308 | return grunt.task.run(['build', 'open', 'connect:dist:keepalive']); 309 | } 310 | 311 | if (target === 'test') { 312 | return grunt.task.run([ 313 | 'clean:server', 314 | 'coffee', 315 | 'createDefaultTemplate', 316 | 'jst', 317 | 'compass:server', 318 | 'connect:test', 319 | 'watch:livereload' 320 | ]); 321 | } 322 | 323 | grunt.task.run([ 324 | 'clean:server', 325 | 'coffee:dist', 326 | 'createDefaultTemplate', 327 | 'jst', 328 | 'compass:server', 329 | 'connect:livereload', 330 | 'open', 331 | 'watch' 332 | ]); 333 | }); 334 | 335 | grunt.registerTask('test', [ 336 | 'clean:server', 337 | 'coffee', 338 | 'createDefaultTemplate', 339 | 'jst', 340 | 'compass', 341 | 'connect:test', 342 | 'mocha', 343 | 'watch:test' 344 | ]); 345 | 346 | grunt.registerTask('build', [ 347 | 'clean:dist', 348 | 'coffee', 349 | 'createDefaultTemplate', 350 | 'jst', 351 | 'compass:dist', 352 | 'useminPrepare', 353 | 'requirejs', 354 | 'imagemin', 355 | 'htmlmin', 356 | 'concat', 357 | 'cssmin', 358 | 'uglify', 359 | 'copy', 360 | 'rev', 361 | 'usemin' 362 | ]); 363 | 364 | grunt.registerTask('default', [ 365 | 'jshint', 366 | 'test', 367 | 'build' 368 | ]); 369 | }; 370 | -------------------------------------------------------------------------------- /part3/public/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.9.2 2 | 3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Backbone may be freely distributed under the MIT license. 5 | // For all details and documentation: 6 | // http://backbonejs.org 7 | (function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks= 8 | {});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g= 9 | z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent= 10 | {};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null== 11 | b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent: 12 | b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)}; 13 | a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error, 14 | h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t(); 15 | return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending= 16 | {};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length|| 17 | !this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator); 18 | this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i('