Wednesday, January 4, 2017

backbone.js first steps

1. Intro

Official web-page: http://backbonejs.org/

From wiki:
Backbone.js is a JavaScript framework with a RESTful JSON interface and is based on the model–view–presenter (MVP) application design paradigm. Backbone is known for being lightweight, as its only hard dependency is on one JavaScript library,[2] Underscore.js, plus jQuery for use of the full library.[3] It is designed for developing single-page web applications,[4] and for keeping various parts of web applications (e.g. multiple clients and the server) synchronized.[5] Backbone was created by Jeremy Ashkenas,[6] who is also known for CoffeeScript and Underscore.js.[7]


I don't know why but most popalar application for first BackBone project is Todo list. So lets's create it. For storing entered data we will be using "local storage".

2. Test application structure

BackBone application contains next parts:
1. Models - structure for entity-data. In our application we have only one model - for Todo entity with properties "title" and "completed".

2. Collections - structures for operating lists(arrays) of  Models. We will have only one collection: Todo collection.

3. Views - structures responsible for user interface and user interaction. We will have 2 views: TodoView - for just Todo itself and AppView - for combining all TodoViews into one list.



Also, we need some common for all web application stuff like:
4. html file
5. css file

3. Model

Our model is very simple, just 2 properties and one function:

app.Todo = Backbone.Model.extend({
  defaults: {
    title: '',
    completed: false  },
  toggle: function(){
    this.save({ completed: !this.get('completed')});
  }
});


4. Collection

we are using local storage for our collection, and it will use our model: 

app.TodoList = Backbone.Collection.extend({
  model: app.Todo,
  localStorage: new Store("backbone-todo")
});


5. TodoView

Views are much more complicated structures:) This view is responsible for displaying just one Todo item - one Todo model.

In this view we are defining:
- TagName - element which will be create "outside" view: view will be inside "<li>" element
- template - we can "hardcode" creation of all html elements needed for view in render function, but of course it's better to use template.
- render  - function which will be called to display view
- initialize - function which will be called once at the beginning for initialization
- events - we can subscribe for some event which can triggered by user. Format is "EVENT ELEMENT:FUNCTION_TO_CALL". For example for 'click .save-btn' : 'close'  : when user clicked element with css class ".save-btn" - we have to execute function "close".
- model events - also we can subscribe on some events from our model. By "this.model.on('change', this.render, this);" - we are subscribing to run "render()" function every time our model change.
- other functions - of course if we need we can add additional functions to view, like in this example  we defined edit, cancel, close, toggleCompleted, destroy functions

app.TodoView = Backbone.View.extend({
  tagName: 'tr',
  template: _.template($('#item-template').html()),
  render: function(){
    this.$el.html(this.template(this.model.toJSON()));
    this.input = this.$('.edit');
    return this; // enable chained calls  },
  initialize: function(){
    this.model.on('change', this.render, this);
    this.model.on('destroy', this.remove, this);
  },      
  events: {
    'click .save-btn' : 'close',
    'click .cancel-btn' : 'cancel',
    'click .toggle': 'toggleCompleted',
    'click .remove-btn': 'destroy',
    'click .edit-btn': 'edit'  },
  edit: function(){
    this.$el.addClass('editing');
    this.input.focus();
  },
  cancel: function(){
    this.$el.removeClass('editing');
  },
  close: function(){
    var value = this.input.val().trim();
    if(value) {
      this.model.save({title: value});
    }
    this.$el.removeClass('editing');
  },
  toggleCompleted: function(){
    this.model.toggle();
  },
  destroy: function(){
    this.model.destroy();
  }      
});

6. AppView

This view is responsible for combining all TodoViews into one list.
Instead of "tagName" we use another property "el" - that mean our view will be showed inside this element. In this example - inside "#todoapp" element. Other elements are similar to previous view.

app.AppView = Backbone.View.extend({
  el: '#todoapp',
  initialize: function () {
    this.input = this.$('#new-todo');
    app.todoList.on('add', this.addAll, this);
    app.todoList.on('reset', this.addAll, this);
    app.todoList.fetch(); // Loads list from local storage  },
  events: {
    'click #add-btn': 'btnAddClick',
  },
  btnAddClick: function(e){
    app.todoList.create(
        {
          title: this.input.val().trim(),
          completed: false        }
    );
    this.input.val(''); // clean input box  },
  addOne: function(todo){
    var view = new app.TodoView({model: todo});
    $('#todo-list').append(view.render().el);
  },
  addAll: function(){
    this.$('#todo-list').html(''); // clean the todo list    app.todoList.each(this.addOne, this);
  }
});

7. Main js file

All our logic are in views, so main application file is very short:
app.todoList = new app.TodoList();
appView = new app.AppView(); 


8. Main css file 

We need to hide and show elements depending on current mode:
- in "view" mode buttons "save" and "cancel" have to hidden. In contrary, buttons "edit" and "remove" have to be visible
-  and visewersa, in "edit" mode buttons "edit" and "remove" have to hidden. But, buttons "save" and "cancel" have to be visible.

   #todoapp ul {
     list-style-type: none;
   }
   #todo-list input.edit {
     display: none;
   }

   #todo-list button.edit-btn {
       display: inline;
   }


   #todo-list button.save-btn {
       display: none;
   }

   #todo-list button.cancel-btn {
       display: none;
   }


/* editing  */
   #todo-list .editing label {
       display: none;
   }
   #todo-list .editing input.edit {
       display: inline;
   }

   #todo-list .editing button.save-btn {
       display: inline;
   }

   #todo-list .editing button.edit-btn {
       display: none;
   }

   #todo-list .editing button.cancel-btn {
       display: inline;
   }

   #todo-list .editing button.remove-btn {
       display: none; 
   }

9. Main Html file. 

In main HTML file we have to define:
- placeholder for our main view: table with id "todo-list".
- template for todo view
- all needed libraries

<!DOCTYPE html>
<html>
<head>
    <title>Simple Backbone example</title>
    <link rel="stylesheet" type="text/css" href="css/main.css"/>
</head>
<body>
<section id="todoapp">
    <header id="header">
        <h1>New TODO</h1>
        <input id="new-todo" placeholder="Enter new TODO here" autofocus>
        <button id="add-btn">add</button>
    </header>
    <section id="main">
        <h1>List of TODOs:</h1>
        <table id="todo-list" border="1"></table>
    </section>
</section>

<!-- Templates --><script type="text/template" id="item-template">
    <div class="view">
        <td>
            <input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>>
        </td>
        <td>
            <label><%- title %></label>
            <input class="edit" value="<%- title %>">
        </td>
        <td>
            <button class="edit-btn">edit</button>
            <button class="save-btn">save</button>
        </td>
        <td>
            <button class="remove-btn">remove</button>
            <button class="cancel-btn">cancel</button>
        </td>
    </div>
</script>

<!-- ========= --><!-- Libraries --><!-- ========= --><script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"        type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone-localstorage.js/1.0/backbone.localStorage-min.js"        type="text/javascript"></script>

<!-- =============== --><!-- Javascript code --><!-- =============== --><script type="text/javascript">
    'use strict';

    var app = {}; // create namespace for our app</script>

<script src="js/models/Todo.js" type="text/javascript"></script>
<script src="js/collections/TodoList.js" type="text/javascript"></script>
<script src="js/views/TodoView.js" type="text/javascript"></script>
<script src="js/views/AppView.js" type="text/javascript"></script>

<script src="js/app.js" type="text/javascript"></script>


</body>
</html>



10. How it works

Everything works pretty good. We can add new todos, edit existing or delete them: