Разбор JavaScriptMVC

*Для тех, кто подумал что я пропустил пробел перед MVC, скажу: нет – JavaScriptMVC – это название фреймворка.

Информации в интернете о нем не так много, поэтому попробуем разобрать вместе как его устанавливать и использовать.

Для начала выкачаем фреймворк себе в директорию проекта либо на уровень выше. Изучаем структуру папок и файлов:

documentjs - документатор 
funcunit - приложение для тестирования 
jquery - jquery и его расширения - jQueryMX 
steal - менеджер подгрузки скриптов и зависимостей 
js,js.bat - командная строка для Linux/Mac и Windows

Разбор начнем с менеджера подгрузки скриптов steal, с которого все и начинается. Метод steal имеет довольно простой синтаксис:

steal(param1, param2, param3, ...)

В параметре мы можем передавать как путь к файлу, который нужно подгрузить, так и callback-функцию, которая выполнится после загрузки файла. Что касается пути, то можно указывать относительный путь от корня фреймворка и сократить расширение .js(он его добавит сам), т.е.:

steal('jquery/class')

– это значит что он подключит файл %framework_path%/jquery/class.js

Внимание. Так как steal работает асинхронно, то мы должны помещать код, который зависит от подключаемого модуля в callback:

steal('jquery/class', function(){ $.Class })

Идем дальше. Все классы в системе создаются вызовом метода

$.Class([name,] [classProps,] [prototypeProps])

Чтобы создать класс модели используется метод

$.Model(name, classProps, prototypeProps)

Пришло время переходить к реальным примерам. Создаем в корне папку нашего проекта, например “todos“. Помещаем в нее 2 файла: todos.html и todos.js.

todos.html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
<body>
    <ul id='todos'></ul>
    <input id='editor'/>
    <script type='text/javascript' src='../steal/steal.js?todos/todos.js'></script>
</body>
</html>

Всего лишь небольшой кусок HTML кода с подключением второго файла – todos.js через steal.js.

Теперь перейдем к более интересному – внутренностям todos.js:

steal('jquery/class',
      'jquery/model',
      'jquery/dom/fixture',
      'jquery/view/ejs',
      'jquery/controller',
      'jquery/controller/route',
       function($){
             $.Model('Todo',{
                     findAll : "GET /todos",
                     findOne : "GET /todos/{id}",
                     create  : "POST /todos",
                     update  : "PUT /todos/{id}",
                     destroy : "DELETE /todos/{id}"
            },{});
      }
)

Подключили необходимые модули, создали основную модель. Играем дальше. Чтобы создать объект нашей модели делаем:

var todo = new Todo({name: "do the dishes"});

Появился объект с атрибутом name. Чтобы получить или изменить атрибут используем метод attr.

todo.attr('name') //получит значение атрибута 
todo.attr('name', "wash the dishes" ); // перезапишет значение

Класс модели использует статические методы для создания, чтения, обновления и удаления сущностей на сервере ( findAll, findOne, create, update, and destroy ), т.е.:

Todo.findAll({})

Но, так как серверной части у нас еще нет, – методы соответственно не отработают так как нам нужно. Хорошо что в фреймворке это учли и сделали заглушки, с помощью которых мы сможем эмулировать серверные ответы. Заглушка реализуется с помощью метода fixture:

$.fixture(url, fixture(original, settings, headers) )

возвратить функция fixture(не путать callback функцию и метод fixture) должна следующую структуру:

return [ status, statusText, responses, responseHeaders ];

например:

return [ 200, "success", {json: []}, {} ];

Добавим в наш код(это который в todos.js) эти заглушки для каждого запроса:

// массив данных
var TODOS = [
{id: 1, name: "wake up"},
{id: 2, name: "take out trash"},
{id: 3, name: "do dishes"}
];

$.fixture("GET /todos", function(){
return [TODOS]
});

$.fixture("GET /todos/{id}", function(orig){
return TODOS[(+orig.data.id)-1];
})

var id= 4;
$.fixture("POST /todos", function(){
return {id: (id++)}
})

$.fixture("PUT /todos/{id}", function(){
return {};
})

$.fixture("DELETE /todos/{id}", function(){
return {};
})

Можем проверить как оно работает:

Todo.findAll({}, function( todos ) {
    console.dir( todos );
})

Для создания и обновления мы вызываем метод save:

// create 
var todo = new Todo({name: "mow lawn"}) 
todo.save(function(todo){ console.dir( todo ); }) 

// update todo.attr("name", "mow my lawn") 
todo.save(function(todo){ console.dir( todo ); })

Еще мы можем на модель вешать обработчики событий методом bind:

.bind( event, handler(ev, todo ) )

например:

var todo = new Todo({name: "mow lawn"});
todo.bind('created', function(ev, todo){
 console.log("created", todo );
})
todo.save()

При вызове todo.save()сработает обработчик события created.

Мы рассмотрели принцы организации моделей во фреймворке, теперь перейдем к представлению. Оно реализовано через метод View:

$.View( idOrUrl, data )

где id0rUrl – ID элемента(либо имя файла), который содержит шаблон, а data – данные передаваемые в этот шаблон. Вот пример шаблона:

<% for(var i = 0; i < this.length; i++ ) { %> 
    <li><%= this[i].name %></li>  
<% } %>

который мы сохранили в файл todos.ejs. Возвращает этот метод обработанный шаблон:

Todo.findAll( {}, function( todos ){
    console.log( $.View( 'todos.ejs', todos ) );
});

в данном случае мы получили элементы для модели и вывели их через шаблон. Также стоит упомянуть что фреймворк расширяет стандартные методы JQuery для работы с HTML, такие как: after, append, before, html, prepend, replaceWith и text. Теперь в них мы тоже можем использовать шаблоны:

$('#todos').html( 'todos.ejs', todos );

Теперь перейдем к организации контроллера, который также основан на вызове метода Controller:

$.Controller(name, classProps, prototypeProps)

с помощью этого метода создается класс JQuery виджета:

$.Controller("Todos", 
            { "init" : function( element , options ){ 
                   this.element.html('todos.ejs', Todo.findAll() ) 
                    } 
})

и теперь мы можем создать сам виджет:

new Todos('#todos', {});

который прикрепится к контейнеру #todos и выведет записи по запросу Todo.findAll() через шаблон todos.ejs.

Тут также не стоит путать виджет контроллера Todos и класс модели Todo.

Обязательно параллельно проверяйте все в коде и экспериментируйте с различными вариантами.

Поговорим более подробно о методе init класса контролера, который будет вызван при создании объекта и имеет следующий синтаксис:

$.Controller.prototype.init(element, options)

где element – это элемент, на который навесили виджет обернутый в JQuery, a options – это опции(как подсказывает кэп), который расширяют настройки заданные по умолчанию. Пример создания нескольких виджетов с параметрами:

//определяем класс $.Controller("Todos", { defaults : {template: 'todos.ejs'} }, { "init" : function( element , options ){ element.html(options.template, Todo.findAll() ) } }) // а теперь создаем виджеты new Todos( document.body.firstElementChild ); new Todos( $('#todos'), {template: 'specialTodos.ejs'})

Также в при создании класса контроллера мы можем сразу же прописать обработчики для событий на элементе виджета следующим образом:

$.Controller("Todos",
            { "init" : ...,
              "li click" : function(element, event){
                      console.log("You clicked", li.text() )
                      // тригерим событие чтобы дать знать другим
                      element.trigger('selected');
                    }
             })

как видно из примера мы имеем следующий синтаксис для навешивания обработчиков:

"[selector] [event]"

Т.е. селектор, причем может быть составной селектор и событие. К сожалению, данный синтаксис не поддерживает множественные события(ну или я не разобрался полностью). Но зато поддерживает динамические шаблоны для подстановки события, выглядит это следующим образом:

"li .destroy {Events.destroy}" : function(el, ev){ ... }

а теперь инициализируем событие:

Events = {destroy: "click"};

Также хорошим стилем является триггер нашего события дальше:

element.trigger('selected');

Т.е. мы отметили что состоялось событие не просто клик, а уже совершилось событие selected.

Удалять созданные виджеты мы можем 2мя способами: стандартно JQuery-style через удаление элемента – $("#todos").remove(), либо с помощью метода виджета .destroy()

Кроме контроллера виджетов в фреймворке есть еще контроллер роутов, который создается следующим образом:

$.Controller("Routing",
             { "route" : function(){//сюда попадаем при пустом хеше }, 
               "todos/:id route" : function(data){ //с параметром } 
}) 

// создаем контроллер
new Routing(document.body);

Как он работает? Он использует метод $.route, который в свою очередь представляет из себя jQuery.Observe наблюдающий за состоянием window.location.hash.

Мы можем непосредственно влиять на hash через этот метод:

$.route.attr('id','6') // location.hash = #!todos/6
// или так
var hash = $.route.url({id: 7}) // #!todos/7 
location.hash = hash;

Более сложный вариант роутера:

$.Controller("Routing",
             {init : function(){ 
                  this.editor = new Editor("#editor") 
                  new Todos("#todos"); 
              }, 
             "route" : function(){ ... }, 
             "todos/:id route" : function(data){ ... }, 
             ".todo selected" : function(el, ev, todo){ $.route.attr('id',todo.id); } 
}); 

new Routing(document.body);

Хочу обратить внимание на вариант, когда когда мы выставляем контроллер не на изменение хеша, а на действие .todo selected, но при этом меняем хеш через $.route.attr(‘id’,todo.id).

Из основного это все.