PageRenderTime 71ms CodeModel.GetById 2ms app.highlight 61ms RepoModel.GetById 2ms app.codeStats 0ms

/tutorial/backbone.rst

https://github.com/htulipe/resthub.org
ReStructuredText | 1161 lines | 783 code | 378 blank | 0 comment | 0 complexity | 8c1ebe53bd7e0e8925093c7d452a7507 MD5 | raw file
   1RESThub Backbone tutorial
   2=========================
   3
   4This tutorial will help you to get an overview of resthub-backbone-stack.
   5
   6If you want to use this tutorial in a training mode, `a version without answers is also available <backbone-without-answer.html>`_.
   7
   8**Code**: you can find the code of the sample application at `<https://github.com/resthub/resthub-backbone-tutorial>`_ (Have a look to branches for each step).
   9
  10Step 1: Model and View
  11-----------------------
  12
  13Find:
  14+++++
  15
  161. **Backbone documentation and description of Backbone.Events, Model, Collection, Router, Sync, View**
  17
  18    see `<http://backbonejs.org/>`_
  19    
  202. **RequireJS documentation:** 
  21    
  22    - requirejs usage
  23
  24        RequireJS allows to define modules and dependencies between modules in order to load your js files
  25
  26    - how to define a module as function
  27
  28        .. code-block:: javascript
  29
  30            define(['backbone'], function(Backbone) { ... });
  31
  32    - how to use it
  33
  34        .. code-block:: javascript
  35
  36            require(['models/task', 'views/task-view'], function(Task, TaskView) { ... });
  37
  38    - usage for config options shims and path
  39
  40        see `<http://requirejs.org/>`_
  41
  423. Description of resthub-backbone `standard project layout <../backbone-stack.html#project-layout>`_ based on requireJS
  43
  44Do:
  45+++
  46
  471. Get an empty resthub-backbone project via an `archetype <../spring-stack.html#bootstrap-your-project>`_ and discover the base application layout
  48          
  492. **Create a Task model**
  50
  51    .. code-block:: javascript
  52
  53        define(['backbone'], function(Backbone) {
  54
  55          var Task = Backbone.Model.extend();
  56
  57          return Task;
  58
  59        });
  60
  613. **Instantiate a task in app.js with attributes title and description**
  62
  63    .. code-block:: javascript
  64
  65        var task = new Task({
  66            title: 'Learn Backbone',
  67            description: 'To write great Rich Internet Applications.'
  68          });
  69
  704. **Try to see your task object in the console. Make it work**
  71
  72    attach task to window with ``window.task = new Task(...)``
  73
  745. **Try to access to title and description. Is task.title working?**
  75
  76    task.title does not work.
  77
  786. **Inspect task and find where attributes are stored**
  79
  80    In *attributes*.
  81
  827. **Access title attribute value**
  83    
  84    .. code-block:: javascript
  85    
  86        task.get("title")
  87
  888. **Change description attribute. What operation does backbone perform whena a model attrbute is modified?** 
  89
  90    .. code-block:: javascript
  91    
  92        task.set("description", "newDesc");
  93        
  94    Backbone raise events on attribute modification ("change", etc.) so we have to use getters / setters to manipulate attributes
  95    
  969. **Create a TaskView and implement render with a function that simply logs "rendered"**
  97
  98    .. code-block:: javascript
  99
 100        define(['backbone'], function(Backbone) {
 101
 102          var TaskView = Backbone.View.extend({
 103            render: function() {
 104              console.log("rendered");
 105              return this;
 106            }
 107          });
 108
 109          return TaskView;
 110        });
 111
 11210. **Instantiate view in app and render it. Verify that "rendered" is logged. Try to render view multiple times in console**
 113
 114        .. code-block:: javascript
 115
 116            window.taskView = new TaskView();
 117            taskView.render();
 118        
 119        **Output:**
 120        
 121        .. code-block:: javascript
 122
 123            rendered
 124
 125            >>> taskView.render()
 126            rendered
 127            Object { cid="view1", options={...}, $el=[1], more...}
 128
 129            >>> taskView.render()
 130            rendered
 131            Object { cid="view1", options={...}, $el=[1], more...}
 132
 13311. **Instantiate the view with a task model in app. Modify TaskView render to log the title of the task. No other modification should be made on TaskView**
 134
 135        app.js: 
 136
 137        .. code-block:: javascript
 138        
 139              window.task = new Task({
 140                title: 'Learn Backbone',
 141                description: 'To write great Rich Internet Applications.'
 142              });
 143
 144              window.taskView = new TaskView({model: task});
 145              taskView.render();
 146              
 147        views/task.js: 
 148
 149        .. code-block:: javascript
 150        
 151              render: function() {
 152                console.log(this.model.get("title"));
 153                return this;
 154              }
 155
 156        **Output:**
 157
 158        .. code-block:: javascript
 159
 160            Learn Backbone
 161
 162            >>> taskView.render()
 163            Learn Backbone
 164            Object { cid="view1", options={...}, $el=[1], more...}
 165
 166Write in DOM
 167++++++++++++
 168
 169View rendering is done in view relative el element that could be attached anywhere in DOM with jQuery DOM insertion API
 170
 171Find:
 172##### 
 173
 1741. **backbone view's DOM element documentation**
 175
 176    see `<http://backbonejs.org/#View-el>`_
 177
 1782. **jquery documentation and search for $(), html(), append() methods**
 179
 180    see `<http://api.jquery.com/category/manipulation/dom-insertion-inside/>`_
 181    
 182Do:
 183###
 184            
 1851. **Modify render to display a task inside a div with class='task' containing title in a h1 and description in a p**
 186
 187    .. code-block:: javascript
 188    
 189        render: function() {
 190          this.$el.html("<div class='task'><h1>" + this.model.get("title") + "</h1><p>" + this.model.get("description") + "</p></div>");
 191          return this;
 192        }
 193
 1942. **render the view and attach $el to the DOM 'tasks' element (in app.js)**
 195
 196    .. code-block:: javascript
 197    
 198        $('#tasks').html(taskView.render().el);
 199
 200Templating
 201++++++++++
 202        
 203Let's render our task in DOM with a template engine: Handlebars
 204
 205Find:
 206######
 207
 2081. **Handlebars documentation**
 209
 210    see `<http://handlebarsjs.com/>`_
 211    
 2122. **How to pass a full model instance as render context in backbone**
 213
 214    see `<http://backbonejs.org/#View-render>`_
 215    
 216Do:
 217####
 218
 2191. **Create Task handlebars template to display task. Template should start with a div with class='task'**
 220
 221    .. code-block:: html
 222    
 223        <div class="task">
 224          <h1>{{title}}</h1>
 225          <p>{{description}}</p>
 226        </div>
 227
 2282. **Load (with requirejs text plugin), compile template in view and render it (pass all model to template)**
 229
 230    .. code-block:: javascript
 231    
 232        define(['backbone', 'text!template/task', 'handlebars'], function(Backbone, taskTemplate, Handlebars) {
 233
 234          var TaskView = Backbone.View.extend({
 235          
 236            template: Handlebars.compile(taskTemplate),
 237          
 238            render: function() {
 239              this.$el.html(this.template(this.model.toJSON()));
 240              return this;
 241            }
 242          });
 243
 244          return TaskView;
 245        });
 246    
 2473. Resthub comes with a `hbs RequireJS extension <../backbone-stack.html#templating>`_ to replace Handlebars.compile.
 248   **Change TaskView to use this extension. Remove Handlebars requirement**
 249   
 250       .. code-block:: javascript
 251       
 252            define(['backbone', 'hbs!template/task'], function(Backbone, taskTemplate) {
 253
 254              var TaskView = Backbone.View.extend({
 255                render: function() {
 256                  this.$el.html(taskTemplate(this.model.toJSON()));
 257                  return this;
 258                }
 259              });
 260
 261              return TaskView;
 262            });
 263
 264Model events
 265++++++++++++
 266
 267Find:
 268######
 269
 2701. **Backbone events documentation and model events catalog**
 271
 272    see `<http://backbonejs.org/#Events>`_ and `<http://backbonejs.org/#Events-catalog>`_ 
 273      
 274      
 275Do:
 276####
 277        
 2781. **Update task in the console -> does not update the HTML**
 279
 2802. **Bind model's change event in the view to render. Update task in console: HTML is magically updated!**
 281
 282       .. code-block:: javascript
 283
 284          var TaskView = Backbone.View.extend({
 285            initialize: function() {
 286              this.model.on('change', this.render, this);
 287            },
 288            render: function() {
 289              this.$el.html(taskTemplate(this.model.toJSON()));
 290              return this;
 291            }
 292          });
 293
 294Step 2: Collections
 295-------------------
 296
 2971. **Create a Tasks collection in** ``collection`` **directory**
 298
 299    .. code-block:: javascript
 300    
 301        define(['backbone'], function(Backbone) {
 302
 303          var Tasks = Backbone.Collection.extend();
 304
 305          return Tasks;
 306
 307        });
 308
 3092. **Create a TasksView** in ``views`` **and a tasks template in** ``templates``.
 3103. **Implement rendering in TasksView**
 3114. **Pass the collection as context**
 3125. **Iterate through the items in the collection in the template**. **Template should start with an** ``ul``
 313   **element with class='task-list'**
 314
 315    .. code-block:: javascript
 316    
 317        // view
 318        define(['backbone', 'hbs!template/tasks'], function(Backbone, tasksTemplate) {
 319
 320          var TasksView = Backbone.View.extend({
 321            render: function() {
 322              this.$el.html(tasksTemplate(this.collection.toJSON()));
 323              return this;
 324            }
 325          });
 326
 327          return TasksView;
 328
 329        });
 330        
 331    .. code-block:: html
 332        
 333        // template
 334        <ul class="task-list">
 335          {{#each this}}
 336            <li class="task">{{title}}</li>
 337          {{/each}}
 338        </ul>
 339 
 3406. **In app: instanciate two task and add them into a new tasks collections. Instantiate View and render it and attach $el to '#tasks' div**
 341
 342    .. code-block:: javascript
 343    
 344        require(['models/task', 'collections/tasks', 'views/tasks'], function(Task, Tasks, TasksView) {
 345
 346          var tasks = new Tasks();
 347
 348          var task1 = new Task({
 349            title: 'Learn Backbone',
 350            description: 'To write great Rich Internet Applications.'
 351          });
 352
 353          var task2 = new Task({
 354            title: 'Learn RESThub',
 355            description: 'Use rethub.org.'
 356          });
 357
 358          tasks.add(task1);
 359          tasks.add(task2);
 360
 361          var tasksView = new TasksView({collection: tasks});
 362          $('#tasks').html(tasksView.render().el);
 363
 364        });
 365
 3667. **try adding an item to the collection in the console**
 367
 368    .. code-block:: javascript
 369    
 370        require(['models/task', 'collections/tasks', 'views/tasks'], function(Task, Tasks, TasksView) {
 371
 372          window.Task = Task;
 373          window.tasks = new Tasks();
 374
 375          ...
 376
 377        });
 378        
 379    **Output:**
 380    
 381    .. code-block:: javascript
 382
 383        >>> task3 = new Task()
 384        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 385
 386        >>> task3.set("title", "Learn again");
 387        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 388
 389        >>> task3.set("description", "A new learning");
 390        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 391
 392        >>> tasks.add(task3);
 393        Object { length=3, models=[3], _byId={...}, more...}
 394            
 395    HTML was not updated.
 396        
 3978. **Bind collection's add event in the view to render**
 398
 399    .. code-block:: javascript
 400    
 401        define(['backbone', 'hbs!template/tasks'], function(Backbone, tasksTemplate) {
 402
 403          var TasksView = Backbone.View.extend({
 404            initialize: function() {
 405              this.collection.on('add', this.render, this);
 406            },
 407            render: function() {
 408                this.$el.html(tasksTemplate(this.collection.toJSON()));
 409              return this;
 410            }
 411          });
 412
 413          return TasksView;
 414
 415        });
 416        
 417    **Output:**
 418    
 419    .. code-block:: javascript
 420
 421        >>> task3 = new Task()
 422        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 423
 424        >>> task3.set("title", "Learn again");
 425        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 426
 427        >>> task3.set("description", "A new learning");
 428        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 429
 430        >>> tasks.add(task3);
 431        Object { length=3, models=[3], _byId={...}, more...}
 432        
 433    HTML is updated with the new task in collection.
 434    
 4359. **Add a nice fade effect**
 436
 437    .. code-block:: javascript
 438    
 439        define(['backbone', 'hbs!template/tasks'], function(Backbone, tasksTemplate) {
 440
 441          var TasksView = Backbone.View.extend({
 442            initialize: function() {
 443              this.collection.on('add', this.render, this);
 444            },
 445            render: function() {
 446              this.$el.fadeOut(function() {
 447                this.$el.html(tasksTemplate(this.collection.toJSON()));
 448                this.$el.fadeIn();
 449              }.bind(this));
 450              return this;
 451            }
 452          });
 453
 454          return TasksView;
 455
 456        });
 457
 458
 45910. **Add a task to the collection in the console** -> the *whole* collection in rerendered.
 460
 461    .. code-block:: javascript
 462    
 463        >>> task3 = new Task()
 464        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 465
 466        >>> task3.set("title", "Learn again");
 467        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 468
 469        >>> task3.set("description", "A new learning");
 470        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 471
 472        >>> tasks.add(task3);
 473        Object { length=3, models=[3], _byId={...}, more...}
 474
 475Step 3: Nested Views
 476--------------------
 477
 4781. Remove the each block in template.
 479
 480    .. code-block:: html
 481    
 482       <ul class="task-list"></ul>
 483       
 4842. Use TaskView in TasksView to render each tasks.
 485
 486    .. code-block:: javascript
 487    
 488        // views/tasks.js
 489        render: function() {
 490          this.$el.fadeOut(function() {
 491            this.$el.html(tasksTemplate(this.collection.toJSON()));
 492            this.collection.forEach(this.add, this);
 493            this.$el.fadeIn();
 494          }.bind(this));
 495          return this;
 496        },
 497
 4983. Update a task in the console -> the HTML for the task is automatically updated.
 499
 500    .. code-block:: javascript
 501    
 502        // app.js
 503        
 504        ...
 505        
 506        window.task1 = new Task({
 507          title: 'Learn Backbone',
 508          description: 'To write great Rich Internet Applications.'
 509        });
 510        
 511    **output:**
 512    
 513    .. code-block:: javascript
 514
 515        >>> task1.set("title", "new Title");
 516        Object { attributes={...}, _escapedAttributes={...}, cid="c0", more...}
 517
 5184. Add tasks to the collection in the console -> the *whole* list is still rerendered.
 519
 520    .. code-block:: javascript
 521    
 522        >>> task3 = new Task()
 523        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 524
 525        >>> task3.set("title", "Learn again");
 526        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 527
 528        >>> task3.set("description", "A new learning");
 529        Object { attributes={...}, _escapedAttributes={...}, cid="c3", more...}
 530
 531        >>> tasks.add(task3);
 532        Object { length=3, models=[3], _byId={...}, more...}
 533
 5345. Update TasksView to only append one task when added to the collection instead of rendering the whole list again.
 535
 536    .. code-block:: javascript
 537    
 538        initialize: function() {
 539          this.collection.on('add', this.add, this);
 540        },
 541
 5426. Add a nice fade effect to TaskView.
 543
 544    .. code-block:: javascript
 545    
 546        // view/task.js
 547        render: function() {
 548            this.$el.fadeOut(function() {
 549              this.$el.html(taskTemplate(this.model.toJSON()));
 550              this.$el.fadeIn();
 551            }.bind(this));
 552            return this;
 553        }
 554        
 5557. Test in the console.
 5568. Remove automatic generated divs and replace them with lis
 557   
 558   goal is to have:
 559   
 560   .. code-block:: html
 561   
 562        <ul>
 563            <li class='task'></li>
 564            <li class='task'></li>
 565        </ul>
 566        
 567   instead of:
 568   
 569   .. code-block:: html
 570   
 571        <ul>
 572            <div><li class='task'></li></div>
 573            <div><li class='task'></li></div>
 574        </ul>
 575        
 576   example: 
 577    
 578        .. code-block:: javascript
 579        
 580            // views/task.js
 581            var TaskView = Backbone.View.extend({
 582                
 583              tagName:'li',
 584              className: 'task',
 585              
 586              ...
 587            
 588    
 5899. Manage click in TaskView to toggle task's details visibility.
 590
 591    .. code-block:: javascript
 592    
 593        events: {
 594          click: 'toggleDetails'
 595        },
 596        
 597        ...
 598        
 599        toggleDetails: function() {
 600          this.$('p').slideToggle();
 601        }
 602
 603Step 4: Rendering strategy
 604--------------------------
 605
 606Find: 
 607+++++
 608
 6091. **Resthub documentation for default rendering strategy**
 610    
 611    see `<../backbone-stack.html#root-attribute>`_
 612    
 613Do:
 614+++
 615
 6161. **Use Resthub.View for managing rendering in TaskView. Remove render method in TaskView and modify add method in TasksView to set root element**
 617
 618    .. code-block:: javascript
 619    
 620        // views/task.js
 621        define(['backbone', 'resthub', hbs!template/task'], function(Backbone, Resthub, taskTemplate) {
 622
 623          var TaskView = Resthub.View.extend({
 624            template: taskTemplate,
 625            tagName: 'li',
 626            className: 'task',
 627            strategy: 'append',
 628            
 629            events: {
 630              click: 'toggleDetails'
 631            },
 632            
 633            initialize: function() {
 634              this.model.on('change', this.render, this);
 635            },
 636            
 637            toggleDetails: function() {
 638              this.$('p').slideToggle();
 639            }
 640            
 641          });
 642
 643          return TaskView;
 644        });
 645        
 646        // views/tasks.js
 647        ...
 648        add: function(task) {
 649          var taskView = new TaskView({root: this.$('.task-list'), model: task});
 650          taskView.render();
 651        }
 652        ...
 653        
 6542. **Re-implement render to get back the fade effect by extending it calling parent function**
 655
 656    .. code-block:: javascript
 657    
 658        render: function() {
 659          this.$el.fadeOut(function() {
 660            TaskView.__super__.render.apply(this);
 661            this.$el.fadeIn();
 662          }.bind(this));
 663          return this;
 664        },
 665
 6663. **Use Resthub.View for managing rendering in TasksView. Call the parent render function.**
 667
 668    .. code-block:: javascript
 669    
 670        define(['backbone', 'resthub', 'view/task-view', 'hbs!template/tasks'], function(Backbone, Resthub, TaskView, tasksTemplate) {
 671
 672          var TasksView = Resthub.View.extend({
 673            template: tasksTemplate,
 674            initialize: function() {
 675              this.collection.on('add', this.add, this);
 676            },
 677            render: function() {
 678              TasksView.__super__.render.apply(this);
 679              this.collection.forEach(this.add, this);
 680              return this;
 681            },
 682            add: function(task) {
 683              var taskView = new TaskView({root: this.$('.task-list'), model: task});
 684              taskView.render();
 685            }
 686          });
 687
 688          return TasksView;
 689
 690        });
 691
 6924. **In the console try adding a Task: thanks to the effect we can see that only one more Task is rendered and not the entirely list**
 693
 694    .. code-block:: javascript
 695    
 696        >>> task3 = new Task()
 697        Object { attributes={...}, _escapedAttributes={...}, cid="c5", more...}
 698
 699        >>> task3.set("title", "Learn again");
 700        Object { attributes={...}, _escapedAttributes={...}, cid="c5", more...}
 701
 702        >>> task3.set("description", "A new learning");
 703        Object { attributes={...}, _escapedAttributes={...}, cid="c5", more...}
 704
 705        >>> tasks.add(task3);
 706        Object { length=3, models=[3], _byId={...}, more...}
 707
 7085. **In the console, update an existing Task: thanks to the effect we can see that just this task is updated**
 709
 710    .. code-block:: javascript
 711
 712        >>> task3.set("title", "new Title");
 713        Object { attributes={...}, _escapedAttributes={...}, cid="c5", more...}
 714
 715Step 5: Forms
 716-------------
 717
 718Do:
 719+++
 720
 7211. **Create TaskFormView which is rendered in place when double clicking on a TaskView. Wrap your each form field in a div with** ``class='control-group'`` **. Add**
 722   ``class='btn btn-success'`` **on your input submit**
 723
 724    .. code-block:: javascript
 725    
 726        // views/task.js
 727        define(['backbone', 'resthub', 'view/taskform-view', 'hbs!template/task'], function(Backbone, Resthub, TaskFormView, taskTemplate) {
 728
 729          var TaskView = Resthub.View.extend({
 730            ...
 731            
 732            events: {
 733              click: 'toggleDetails',
 734              dblclick: 'edit'
 735            },
 736            
 737            ...
 738            
 739            edit: function() {
 740              var taskFormView = new TaskFormView({root: this.$el, model: this.model});
 741              taskFormView.render();
 742            },
 743            
 744            ...
 745            
 746          });
 747
 748          return TaskView;
 749        });
 750        
 751        // views/taskform.js
 752        define(['backbone', 'resthub', 'hbs!template/taskform'], function(Backbone, Resthub, ,taskFormTemplate) {
 753
 754          var TaskFormView = Resthub.View.extend({
 755            template: taskFormTemplate,
 756            tagName: 'form',
 757          });
 758
 759          return TaskFormView;
 760
 761        });
 762        
 763    .. code-block:: html
 764    
 765        <div class="control-group">
 766          <input class="title" type="text" placeholder="Title" value="{{model.title}}" />
 767        </div>
 768        <div class="control-group">
 769          <textarea class="description" rows="3" placeholder="Description">{{model.description}}</textarea>
 770        </div>
 771        <input type="submit" class="btn btn-success" value="Save" />
 772
 7732. **When the form is submitted, update the task with the changes and display it
 774   again -> note that the change event is not triggered when there was no
 775   changes at all.**
 776  
 777    .. code-block:: javascript
 778    
 779        // views/taskform.js
 780        
 781        ...
 782        save: function() {
 783          this.model.set({
 784            title: this.$('.title').val(),
 785            description: this.$('.description').val(),
 786          });
 787          return false;
 788        }
 789        ...
 790  
 7913. **Force change event to be raised once and only once**
 792
 793    .. code-block:: javascript
 794    
 795        // views/taskform.js
 796        
 797        ...
 798        save: function() {
 799          this.model.set({
 800            title: this.$('.title').val(),
 801            description: this.$('.description').val(),
 802          }, {silent: true});
 803          this.model.trigger('change', this.model);
 804          return false;
 805        }
 806        ...  
 807  
 8084. **Add a button to create a new empty task. In TasksView, bind its click event
 809   to a create method which instantiate a new empty task with a TaskView which
 810   is directly editable. Add** ``class="btn btn-primary"`` **to this button**
 811  
 812    .. code-block:: html
 813
 814        <!-- template/tasks.hbs -->
 815        <ul class="task-list"></ul>
 816        <p>
 817          <button id="create" class="btn btn-primary" type="button">New Task</button>
 818        </p>
 819        
 820    .. code-block:: javascript
 821
 822        var TasksView = Resthub.View.extend({
 823          template: tasksTemplate,
 824       
 825          events: {
 826            'click #create': 'create'
 827          },
 828       
 829          ...
 830       
 831          create: function() {
 832            var taskView = new TaskView({root: this.$('.task-list'), model: new Task()});
 833            taskView.edit();
 834          }
 835        });
 836  
 8375. **Note that you have to add the task to the collection otherwise when you
 838   render the whole collection again, the created tasks disappear. Try by attach
 839   tasksView to windows and call render() from console**
 840   
 841   .. code-block:: javascript
 842   
 843        create: function() {
 844          var task = new Task();
 845          this.collection.add(task, {silent: true});
 846          var taskView = new TaskView({root: this.$('.task-list'), model: task});
 847          taskView.edit();
 848        }
 849
 8506. **Add a cancel button in TaskFormView to cancel task edition. Add a**
 851   ``class="btn cancel"`` **to this button**
 852   
 853    .. code-block:: html
 854
 855        <!-- templates/taskform.hbs -->
 856        ...
 857        <input type="button" class="btn cancel" value="Cancel" />
 858        
 859    .. code-block:: javascript
 860    
 861        var TaskFormView = Resthub.View.extend({
 862          ...
 863          events: {
 864            submit: 'save',
 865            'click .cancel': 'cancel'
 866          },
 867          ...
 868          cancel: function() {
 869            this.model.trigger('change');
 870          }
 871        });
 872        
 8737. **Add a delete button which delete a task. Add** ``class="btn btn-danger delete"`` 
 874   **to this button. Remove the view associated to this task on delete click and remove 
 875   the task from the collection**
 876    
 877   Note that we can't directly remove it from the collection cause the
 878   TaskFormView is not responsible for the collection management and does not
 879   have access to this one.
 880   
 881   **Then use the model's destroy method and note that Backbone will automatically
 882   remove the destroyed object from the collection on a destroy event**
 883   
 884       .. code-block:: javascript
 885       
 886            // views/taskform.js
 887            var TaskFormView = Resthub.View.extend({
 888              ...
 889              events: {
 890                submit: 'save',
 891                'click .cancel': 'cancel',
 892                'click .delete': 'delete'
 893              },
 894              ...
 895              delete: function() {
 896                this.model.destroy();
 897              }
 898            });
 899            
 900            // views/task.js
 901            ...
 902            initialize: function() {
 903              this.model.on('change', this.render, this);
 904              this.model.on('destroy', this.remove, this);
 905            },
 906            ...
 907       
 908       **output:**
 909       
 910       .. code-block:: javascript
 911        
 912            // no click on delete
 913            >>> tasks
 914            Object { length=2, models=[2], _byId={...}, more...}
 915
 916            // on click on delete
 917            >>> tasks
 918            Object { length=1, models=[1], _byId={...}, more...}
 919
 920            // two clicks on delete
 921            >>> tasks
 922            Object { length=0, models=[0], _byId={...}, more...}
 923   
 9248. **Note in the console that when removing a task manually in the collection, it
 925   does not disappear**
 926       
 927       .. code-block:: javascript
 928       
 929            >>> tasks
 930            Object { length=2, models=[2], _byId={...}, more...}
 931
 932            >>> tasks.remove(tasks.models[0]);
 933            Object { length=1, models=[1], _byId={...}, more...}
 934            
 935       But task is still displayed
 936   
 9379. **Bind remove event on the collection to call** ``task.destroy()`` **in TasksView**
 938
 939    .. code-block:: javascript
 940    
 941        ...
 942        initialize: function() {
 943          this.collection.on('add', this.add, this);
 944          this.collection.on('remove', this.destroyTask, this);
 945        },
 946        
 947        ...
 948        
 949        destroyTask: function(task) {
 950          task.destroy();
 951        }
 952
 95310. **Test again in the console**
 954
 955    .. code-block:: javascript
 956
 957        >>> tasks
 958        Object { length=2, models=[2], _byId={...}, more...}
 959
 960        >>> tasks.remove(tasks.models[0]);
 961        Object { length=1, models=[1], _byId={...}, more...}
 962        
 963    And task disapeared
 964
 965Step 6: Validation
 966------------------
 967
 968Find:
 969+++++
 970
 9711. **Backbone documentation about model validation**
 972
 973    see `<http://backbonejs.org/#Model-validate>`_
 974    
 9752. **Resthub documentation for populateModel**
 976
 977    see `<../backbone-stack.html#automatic-population-of-view-model-from-a-form>`_
 978
 979Do:
 980+++
 981
 9821. **Implement validate function in Task model: make sure that the title is not
 983   blank**
 984   
 985    .. code-block:: javascript
 986    
 987        define(['backbone'], function(Backbone) {
 988
 989          var Task = Backbone.Model.extend({
 990            validate: function(attrs) {
 991              if (/^\s*$/.test(attrs.title)) {
 992                return 'Title cannot be blank.';
 993              }
 994            }
 995          });
 996
 997          return Task;
 998        });
 999        
10002. **In TaskFormView, on save method, get the result of set method call on attributes and 
1001   trigger "change" event only if validation passes**
1002   
1003    .. code-block:: javascript
1004    
1005        save: function() {
1006
1007          var success = this.model.set({
1008            title: this.$('.title').val(),
1009            description: this.$('.desc').val(),
1010          });
1011
1012          // If validation passed, manually force trigger
1013          // change event even if there were no actual
1014          // changes to the fields.
1015          if (success) {
1016            this.model.trigger('change');
1017          }
1018
1019          return false;
1020        },
1021   
10223. **Update TaskForm template to add a span with class** ``help-inline`` **immediately after title input**
1023
1024    .. code-block:: html
1025    
1026        <div class="control-group">
1027          <input class="title" type="text" placeholder="Title" value="{{model.title}}" />
1028          <span class="help-inline"></span>
1029        </div>
1030        
10314. **In TaskFormView bind model's error event on a function which renders
1032   validation errors. On error, add class "error" on title input and display error in span "help-inline"**
1033   
1034    .. code-block:: javascript
1035    
1036        initialize: function() {
1037          this.model.on('error', this.error, this);
1038        },
1039        
1040        ...
1041        
1042        error: function(model, error) {
1043          this.$('.control-group:first-child').addClass('error');
1044          this.$('.help-inline').html(error);
1045        }
1046   
1047        
10485. **Use Backbone.Validation for easy validation management**
1049
1050    .. code-block:: javascript
1051    
1052        // models/task.js
1053        define(['backbone'], function(Backbone) {
1054
1055          var Task = Backbone.Model.extend({
1056            validation: {
1057              title: {
1058                required: true,
1059                msg: 'A title is required.'
1060              }
1061            }
1062          });
1063
1064          return Task;
1065
1066        });
1067        
1068        // views/taskform.js
1069        define(['backbone', 'hbs!template/taskform'], function(Backbone, taskFormTemplate) {
1070          ...
1071          initialize: function() {
1072            this.model.on('error', this.error, this);
1073            Backbone.Validation.bind(this);
1074          },
1075          ...
1076        });
1077
10786. **Note that Backbone.Validation can handle for you error displaying in your
1079   views: remove error bindings and method and ensure that you form input have
1080   a name attribute equals to the model attribute name**
1081   
1082    .. code-block:: html
1083    
1084        <div class="control-group">
1085          <input class="title" type="text" name="title" placeholder="Title" value="{{model.title}}" />
1086          <span class="help-inline"></span>
1087        </div>
1088        <div class="control-group">
1089          <textarea class="description" rows="3" name="description" placeholder="Description">{{model.description}}</textarea>
1090        </div>
1091        
1092    .. code-block:: javascript
1093        
1094        // views/taskform.js
1095        ...
1096        initialize: function() {
1097          Backbone.Validation.bind(this);
1098        },
1099        ...
1100        
11017. **Rewrite save method using resthub** ``populateModel`` and backbone ``isValid``
1102
1103    .. code-block:: javascript
1104    
1105        save: function() {
1106
1107          this.populateModel(this.$el);
1108
1109          // If validation passed, manually force trigger
1110          // change event even if there were no actual
1111          // changes to the fields.
1112          if (this.model.isValid()) {
1113            this.model.trigger('change');
1114          }
1115
1116          return false;
1117        },
1118
1119
1120Step 7: Persist & Sync
1121----------------------
1122
1123* Our data are not persisted, after a refresh, our task collection will be
1124  reinitialized.
1125* Use Backbone local storage extension to persist our tasks into the local
1126  storage.
1127* Bind the collection's reset event on TasksView.render to render the
1128  collection once synced with the local storage.
1129* Warning: you need to specify the model attribute in the Tasks collection to
1130  tell the collection which model object is gonna be used internally.
1131  Otherwise, when fetching, the returned JSON object will be added directly to
1132  the collection without instantiating a Task. As a consequence every specific
1133  attributes (like validation hash), would be unavailable in the model. At this
1134  step, if validation does not work anymore after fetching the tasks through
1135  Backbone.sync, check that the model attribute is correctly set in the
1136  collection.
1137
1138Step 8
1139------
1140
1141* Download `RESThub Spring tutorial sample project <https://github.com/resthub/resthub-spring-tutorial/zipball/step5-solution>`_ and extract it
1142* Create jpa-webservice/src/main/webapp directory, and move your JS application into it
1143* Run the jpa-webservice webapp thanks to Maven Jetty plugin
1144* Remove backbone-localstorage.js file and usage in JS application
1145* Make your application retreiving tasks from api/task?page=no URL
1146
1147.. code-block:: javascript
1148
1149    // collections/tasks.js
1150    define(['backbone', 'models/task'], function(Backbone, Task) {
1151      var Tasks = Backbone.Collection.extend({
1152        url: 'api/task',
1153        model: Task
1154      });
1155      return Tasks;
1156    });
1157
1158    // app.js
1159    tasks.fetch({ data: { page: 'no'} });
1160
1161* Validate that retreive, delete, create and update actions work as expected with this whole new jpa-webservice backend