И опять про MVC

Я нашел в сети очень мало реализаций MVC для JavaScript, поэтому хотел бы раскрыть преимущества данного подхода именно в реализации JavaScript.

Заранее скажу, что использование MVC больше подходит к бизнес приложениям, играм, интерфейсам типа админки или что-то подобное Gmail/Google docs. Если у вас промо-сайт с тысячей баннеров и летающими снежинками по экрану, использование MVC бессмысленно и даже вредно.

Немного истории:
MVC (Model View Controller) описан в 1979 году, еще для языка SmallTalk.
С тех пор ничего нового в интерфейсах так и не изобрели. Шаблон улучшался, дополнялся и расширялся. Появлялись последователи в зависимости от возможностей платформы и языка на котором пишется приложение, такие как HMVC (Hierarhical Model View Controller), MVP (Model View Presenter), MVVM (Model View ViewModel), но суть осталась прежняя — отделение источника данных от изменяющих и командующих функций и их слабое связывание.
Заранее скажу что с самого появления шаблона в 1979 году первый отзыв был примерно такой: "Зачем это нужно? И так всё работает хорошо.", второй отзыв: "Ба! Да тут же данные дуплицируются в Виде и в Модели. Всё будет тормозить!" и, наконец, третий :"При каждом изменении модели вид обновляется. Всё будет тормозить!". Поэтому не удивляйтесь что при прочтенни данной статьи у вас возникнут похожие вопросы. Несмотря на всё, MVC и его последователи очень стали очень удачными и проверенными временем решениями для довольно многих задач. Надеюсь вы тоже в этом убедитесь.

Задача: создать простейшую интерактивную страничку — список значений, и две кнопки — для добавления и удаления значений. Традиционный подход довольно простой — создаём html файл. Для списка используется select, для манипуляций — button. В атрибуте onClick у кнопок прописываем вызов JavaScript функции. Для удаления значения из select обращаемся к DOM объекту select, спрашиваем свойство selectedIndex и удаляем соответствующий объект .
<html><head><script>
// <![CDATA[
function addItem (value) {
    if (!value) {
        return;
    }
    var myselect = document.getElementById('myselect');
    var newoption = myselect.appendChild(document.createElement('option'));
    newoption.value = value;
    newoption.innerHTML=value;  
}

function removeCurrentItem () {
    var myselect = document.getElementById('myselect');
    if (myselect.selectedIndex === -1) {
        return;
    }
    var selectedOption = myselect.options[myselect.selectedIndex];
    selectedOption.parentNode.removeChild(selectedOption);
}

// ]]>
</script>
</head>
<body>
    <select id="myselect" size="4"></select>
    <button onClick="addItem(prompt('enter value'))">+</button>
    <button onClick="removeCurrentItem()" />-</button>
</body>
</html>

Всё просто и отлично работает. Зачем же нужно что-то еще? Для данного приложения проблем не ожидается, однако, по статистике, собственно, написание кода занимает около 25% времени, а остальные 75% программист занимается развитием, поддержкой проекта, добавление новых функций. И по мере усложнения приложения и увеличения количества поддерживать код становится всё сложнее и сложнее, количество багов увеличивается.

«Я сделаю через Jquery/Dojo/MooTools/Моя_любимая_аякс_библиотека» скажете вы. Например так:
<html><head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script>
// <![CDATA[
$(document).ready(function(){
    $('#plus').bind('click', function(){
        var value = prompt('add value');
        if (!value) {
            return;
        }
        $('<option>').html(value).appendTo($('#myselect'));
    });
    $('#minus').bind('click', function(){
        var myselect = $('#myselect');
        if (myselect.attr('selectedIndex') === -1) { 
            return;
        }
        myselect.children().remove(':selected');
    });
});
// ]]>
</script>
</head>
<body>
    <select id="myselect" size="4"></select>
    <button id="plus">+</button>
    <button id="minus"/>-</button>
</body>
</html>

Стало еще лучше, код стал более компактный и он выглядит очень круто. Однако это не решило основную проблему: данные (список) хранится внутри пользовательского интерфейса, внутри html элемента select. Второй недостаток — у нас мешанина из html и javascript кода. Несмотря на простоту, он плохо развивается. К примеру к нам подходит начальник и говорит простейшую просьбу: «хочу чтобы кнопка 'минус' появлялась только когда что-то выделено в списке.». Сразу у вас, как у программиста, возникает вопрос: где делать это изменение — в html или в JavaScript. «Хм, пожалуй выключу кнопку в html» — скажете вы. А ваш коллега, пока вы в отпуске, сделает похожую операцию внутри JavaScript.
Получается что, казалось бы, простое изменение начинает очень сильно запутывать код, этого хотелось бы избежать.

В классической реализации MVC основные принципы такие:
  1. Слабое связывание
  2. Модель ничего не знает ни о ком. Модель рассылает оповещения, которые может слушать, например, Вид, если хочет.
  3. Вид знает о модели но не может её менять, вид может манипулировать контроллером
  4. Контроллер знает о Модели и может её менять, а также знает о Виде (или Видах) и может его (их) менять.

В данном случае отличия следующие:
  1. Модель ничего не знает ни о ком. Может рассылать оповещения
  2. Вид знает только о модели
  3. Контроллер знает и о Виде и о Модели

Как видим, в отличие от классического шаблона, данный шаблон более жёсткий, т.к. вид ничего не знает о контроллере, мне не нравится идея перекрестной линковки объектов (Вида и Контроллера), и пока можно я попытаюсь без такой линковки обойтись. Назовем его, к примеру, Ортодоксальный MVC (Orthodox MVC)

Модель

Модель в нашем случае хранит просто массив объектов (строк) которые мы ввели. Также хранится порядковый номер «текущего» объекта. Также здесь есть стандартные методы добавления и удаления объектов: addItem, removeCurrentItem, без них не обойтись, т.к. модели необходимо рассылать оповещения о своём изменении.
Для рассылки оповещения используется объект типа сабджект (modelChangedSubject), можно сделать несколько таких объектов и рассылать подписчикам не просто извещения «модель изменилась» а «свойство X у модели изменилось». Это поможет Виду обновляться не целиком а только тем участкам которые реально изменились в модели.
Я не нашёл в jquery стандартную функцию создания сабджектов, поэтому взял готовую makeObservableSubject из отличной статьи по mvc одного Канадского программиста.

...
OMVC.Model = function () {
    var that = this;
    var items = [];
    this.modelChangedSubject = OMVC.makeObservableSubject();
    this.addItem = function (value) {
        if (!value) {
            return;
        }
        items.push(value);
        that.modelChangedSubject.notifyObservers();
    };
    this.removeCurrentItem = function () {
        if (that.selectedIndex === -1) {
             return;
        }
        items.splice(that.selectedIndex, 1);
        that.modelChangedSubject.notifyObservers();
    };
    this.getItems = function () {
        return items;
    };
    this.selectedIndex = -1;
    this.getSelectedIndex = function () {
        return that.selectedIndex;
    }
    this.setSelectedIndex = function (value) {
        that.selectedIndex = value;
        that.modelChangedSubject.notifyObservers();
    }
};
...


Вид

Как видим, у нашего html тело (body) «пустое», все элементы рисуются «вручную» т.е. с помощью javascript. Посмотрите Gmail — там то же самое. Все элементы: кнопочки, списки рисуются яваскриптом.
Вид знает о модели, но в идеале Вид должен иметь доступ к модели в режиме read-only, т.е. нельзя разрешить виду вызывать removeCurrentItem даже если это очень хочется.
К сожалению я пока не придумал как можно в JavaScript одним функциям разрешить изменения другого объекта, а другим функциям оставить только доступ в режиме чтения. Если у вас есть идеи как этого добиться, поделитесь пожалуйста.
...
OMVC.View = function (model, rootObject) {
    var that = this;
    that.select = $('<select/>').appendTo(rootObject);
    that.select.attr('size', '4');
    that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20);
    that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20);
    model.modelChangedSubject.addObserver(function () {
        var items = model.getItems();
        var innerHTML = '';
        for (var i = 0; i<items.length; i += 1) {
             innerHTML += "<option>"+items[i]+"</option>";
        }
        that.select.html(innerHTML);
    });
};
...


Контроллер

В данном контроллере мы просто вешаем эвенты на объекты вида, связывая модель и вид.
...
OMVC.Controller = function (model, view) {
    view.buttonAdd.bind('click', function () {
        model.addItem(prompt('addvalue'));
    });
    view.buttonRemove.bind('click', function () {
        model.removeCurrentItem();
    });
    view.select.bind('click', function () {
       model.setSelectedIndex(view.select[0].selectedIndex);
    });
};  
...


Полный текст страницы:
<!doctype html>
<html>
  <body>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script>
// <![CDATA[
var OMVC = {};

OMVC.makeObservableSubject = function () {
    var observers = [];
    var addObserver = function (o) {
        if (typeof o !== 'function') {
            throw new Error('observer must be a function');
        }
        for (var i = 0, ilen = observers.length; i < ilen; i += 1) {
            var observer = observers[i];
            if (observer === o) {
                throw new Error('observer already in the list');
            }
        }
        observers.push(o);
    };
    var removeObserver = function (o) {
        for (var i = 0, ilen = observers.length; i < ilen; i += 1) {
            var observer = observers[i];
            if (observer === o) {
                observers.splice(i, 1);
                return;
            }
        }
        throw new Error('could not find observer in list of observers');
    };
    var notifyObservers = function (data) {
        // Make a copy of observer list in case the list
        // is mutated during the notifications.
        var observersSnapshot = observers.slice(0);
        for (var i = 0, ilen = observersSnapshot.length; i < ilen; i += 1) {
            observersSnapshot[i](data);
        }
    };
    return {
        addObserver: addObserver,
        removeObserver: removeObserver,
        notifyObservers: notifyObservers,
        notify: notifyObservers
    };
};


OMVC.Model = function () {
    var that = this;
    var items = [];
    this.modelChangedSubject = OMVC.makeObservableSubject();
    this.addItem = function (value) {
        if (!value) {
            return;
        }
        items.push(value);
        that.modelChangedSubject.notifyObservers();
    };
    this.removeCurrentItem = function () {
        if (that.selectedIndex === -1) {
             return;
        }
        items.splice(that.selectedIndex, 1);
        that.modelChangedSubject.notifyObservers();
    };
    this.getItems = function () {
        return items;
    };
    this.selectedIndex = -1;
    this.getSelectedIndex = function () {
        return that.selectedIndex;
    }
    this.setSelectedIndex = function (value) {
        that.selectedIndex = value;
        that.modelChangedSubject.notifyObservers();
    }
};

OMVC.View = function (model, rootObject) {
    var that = this;
    that.select = $('<select/>').appendTo(rootObject);
    that.select.attr('size', '4');
    that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20);
    that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20);
    model.modelChangedSubject.addObserver(function () {
        var items = model.getItems();
        var innerHTML = '';
        for (var i = 0; i<items.length; i += 1) {
             innerHTML += "<option>"+items[i]+"</option>";
        }
        that.select.html(innerHTML);
    });
};

OMVC.Controller = function (model, view) {
    view.buttonAdd.bind('click', function () {
        model.addItem(prompt('addvalue'));
    });
    view.buttonRemove.bind('click', function () {
        model.removeCurrentItem();
    });
    view.select.bind('click', function () {
       model.setSelectedIndex(view.select[0].selectedIndex);
    });
};        

$(document).ready(function () {
    var model = new OMVC.Model();
    var view = new OMVC.View(model, $('<div/>').appendTo($("body")));
    var controller = new OMVC.Controller(model, view);
});
// ]]>
    </script>
  </body>
</html>

Вывод

Напоследок, вопрос: какие же преимущества нам даёт реализация в виде MVC перед первой, стандартной или улучшенной jQuery-евской? Мы написали в три раза больше кода и ради чего?
Ответ: Даже на таком простом примере, потихоньку начинают становиться заметными преимущества шаблона MVC:
  1. Нет нужды присваивать элементам интерфейса id, чтобы получать их потом по document.getElementById и им подобным функциям, ведь ссылки на все элементы уже хранятся в Виде, их искать по html документу не нужно.
  2. Все объекты рисуются внутри элемента rootObject, Вид можно легко использовать много раз для любого rootObject.
  3. В html body ничего не хранится, т.е. конечно что-то там есть, но нас это совершенно не интересует — раньше изменения нам нужно было искать и делать в двух местах: в html и в функциях JavaScript. Теперь всё делается только в JavaScript и более того, дизайн хранится только в объекте Вид.
  4. Изменения очень легки. К примеру, упомянутая выше просьба «начальника» выполняется следующим образом:
    • создаём сабджект по изменению selectedIndex
      this.selectedIndexChangedSubject = OMVC.makeObservableSubject();
    • при изменении оповещаем that.selectedIndexChangedSubject.notifyObservers();
    • и в Виде подписываемся на оповещение selectedIndexChangedSubject

Полная, финальная версия с добавлением фичи с подсветкой:
<!doctype html>
<html>
  <body>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script>
// <![CDATA[
var OMVC = {};
OMVC.makeObservableSubject = function () {
    var observers = [];   
    var addObserver = function (o) {
        if (typeof o !== 'function') {
            throw new Error('observer must be a function');
        }
        for (var i = 0, ilen = observers.length; i < ilen; i += 1) {
            var observer = observers[i];
            if (observer === o) {
                throw new Error('observer already in the list');
            }
        }
        observers.push(o);
    };    
    var removeObserver = function (o) {
        for (var i = 0, ilen = observers.length; i < ilen; i += 1) {
            var observer = observers[i];
            if (observer === o) {
                observers.splice(i, 1);
                return;
            }
        }
        throw new Error('could not find observer in list of observers');
    };    
    var notifyObservers = function (data) {
        // Make a copy of observer list in case the list
        // is mutated during the notifications.
        var observersSnapshot = observers.slice(0);
        for (var i = 0, ilen = observersSnapshot.length; i < ilen; i += 1) {
            observersSnapshot[i](data);
        }
    };    
    return {
        addObserver: addObserver,
        removeObserver: removeObserver,
        notifyObservers: notifyObservers,
        notify: notifyObservers
    };
};


OMVC.Model = function () {
    var that = this;
    var items = [];
    this.modelChangedSubject = OMVC.makeObservableSubject();
    this.addItem = function (value) {
        if (!value) {
            return;
        }
        items.push(value);
        that.modelChangedSubject.notifyObservers();
    };
    this.removeCurrentItem = function () {
        if (that.selectedIndex === -1) {
             return;
        }
        items.splice(that.selectedIndex, 1);
        if (items.length === 0) {
             that.setSelectedIndex(-1);
        }
        that.modelChangedSubject.notifyObservers();
    };
    this.getItems = function () {
        return items;
    };
    this.selectedIndex = -1;
    this.getSelectedIndex = function () {
        return that.selectedIndex;
    }
    this.selectedIndexChangedSubject = OMVC.makeObservableSubject();
    this.setSelectedIndex = function (value) {
        that.selectedIndex = value;
        that.selectedIndexChangedSubject.notifyObservers();
    }
};

OMVC.View = function (model, rootObject) {
    var that = this;
    that.select = $('<select/>').appendTo(rootObject);
    that.select.attr('size', '4');
    that.buttonAdd = $('<button>+</button>').appendTo(rootObject).height(20);
    that.buttonRemove = $('<button>-</button>').appendTo(rootObject).height(20).fadeOut();
    model.modelChangedSubject.addObserver(function () {
        var items = model.getItems();
        var innerHTML = '';
        for (var i = 0; i<items.length; i += 1) {
             innerHTML += "<option>"+items[i]+"</option>";
        }
        that.select.html(innerHTML);
    });
    model.selectedIndexChangedSubject.addObserver(function () {
        if(model.getSelectedIndex() === -1) {
            that.buttonRemove.fadeOut();
        } else {
            that.buttonRemove.fadeIn();
        }
    });
};

OMVC.Controller = function (model, view) {
    view.buttonAdd.bind('click', function () {
        model.addItem(prompt('addvalue'));
    });
    view.buttonRemove.bind('click', function () {
        model.removeCurrentItem();
    });
    view.select.bind('click', function () {
       model.setSelectedIndex(view.select[0].selectedIndex);
    });
};        

$(document).ready(function () {
    var model = new OMVC.Model();
    var view = new OMVC.View(model, $('<div/>').appendTo($("body")));
    var controller = new OMVC.Controller(model, view);
});
// ]]>
    </script>
  </body>
</html>

Раньше у нас была интерактивная html страничка — а теперь стало приложение на языке javascript с графической оболочкой на HTML. Почувствуйте разницу.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 76

    0
    MVC на яваскрипте. Хм. Красиво. Извращённо. Со вкусом. Очевидно, что в наши дни такого назовут «Привези мне папенька чудище заморское. Для сексуальных утёх и извращений».

    Хотя, выглядит на самом деле интересно. Думаю, развитие будет. Линукс на яваскрипте уже сделали. Было прикольно.

    Но, лично по мне — это уже обкачивание милого явика среройдами.
      0
      На яваскрипте сделали не сам линукс, а виртуальную машину, в которой он запускается. А применять можно в том же node.js.
        0
        Хм. Естественно, для node.js придется модифицировать код, но суть все равно та же :-)
    +7
    Лучшая реализацию MVC на JavaScript с которой работал — в ExtJS.
      0
      Model уровень у них точно самый продуманный. Особенно DataSource, особенно в ExtJS 4 :)
        +1
        А вы с SproutCore работали?
          0
          Спасибо за совет. Посмотрел, очень достойная вещь.
            +1
            Можете закрыть, там все супер по-наркомански и похоже кроме разработчиков никто не знает как это чудо работает. Там конечно удоно сделана Model и Controller, но View там ад (а если покопаться то даже 2 ада).
      0
      Если интересен MVC в яваскрипте, рекомендую книгу AJAX in Action, авторы: Crane D., Pascarello E.

      Мое изучение принципов MVC началось именно с нее. Сейчас понимаю, что немного извращенно, но все же первый учитель — всегда первый :)
        –5
        Писец, я отстал от жизни. Оказывается их ещё и несколько реализаций. Пойду читать.
          +1
          Дык!

          Как минимум, Backbone.js (http://documentcloud.github.com/backbone/) и Knockout (http://knockoutjs.com/).

          Сейчас копаюсь с ними. В первом, на первый взгляд, лучше проработана работа с моделью (синхронизация данных с сервером, валидация), во втором — с моделью все попроще, но зато синхронизация модели и вьюхи делается не в пример меньшим числом строк кода. В целом, у обоих фреймворков много сторонников.
            0
            knockout — это не mvc, это mvvm.
              0
              Да, само собой.
                0
                knockout — под нод?
                • UFO just landed and posted this here
                • UFO just landed and posted this here
                  0
                  Разрабатываем проект на javascript — юзаем backbone.
                +1
                Действительно, отличная книга, хоть и староватая(2006)
                Есть на русском «Ajax в действии. Дейв Крейн»
                В сети можно найти рус. и англ. варианты.
                +17
                Самое главное — после прочтения сделайте правильные выводы:

                1. Если Вам нужен только селект с кнопками плюс минус — прочь от MVC, пишите в лоб — можно с помощью «Jquery/Dojo/MooTools/Моя_любимая_аякс_библиотека».

                2. Если Вам нужно 20 таких селектов с кнопками — пишите плагин для «Jquery/Dojo/MooTools/Моя_любимая_аякс_библиотека» и используйте в свое удовольствие.

                3. Если Вам нужно написать интерфейс к «Gmail/Google docs/Facebook/еще какая то крутая жесть» — смотрите в сторону MVC, Observer и т.д.

                Просто самое главное — калибр решения должен соответствовать калибру решения. Для воробьев рогатки вполне достаточно, даже если вдруг они станут голубями.
                  0
                  >калибр решения должен соответствовать калибру решения

                  Наверное, калибру задачи всё же, но не суть. Суть в том, что решения тут «в лоб», там «в лоб» имеют свойство разрастаться, обрастать жёсткими связями и зависимостями (хорошо, если явными или, хотя бы, документированными), да так, что переход от вашего «1» к «2» или «3» (или от «2» к «3») становится сложнее, чем сразу сделать «3». Почему бы сразу не сделать «по уму», если трудоемкость (при использовании библиотек и/или фреймворков, конечно) если и возрастает, то незначительно, а читаемость кода повышается?
                    +2
                    Если сразу делать «по уму» то читаемость кода будет падать. Смотрите хотя бы на код в посте, насколько он больше с использованием mvc. И:
                    1) разрастётся или нет ещё неизвестно
                    2) заранее всё не предусмотришь поэтому дописывать/переписывать придётся
                    3) не надо решать проблему пока она не возникла
                      0
                      Может потому он больше, что фреймворки не используются? То есть это решение в лоб с инфраструктурным кодом для mvc. Выделить этот код и оформить его для повторного использования — объём намного сократится. Плюс код этого view я не считаю оптимальным ни для чтения, ни для написания — «спагетти» из html и преобразовали в js и html.

                  0
                  Не подскажите хорошую статью про MVC, так как никак не могу въехать в этот принцип, описания читал (и на википедии в том числе), сам смысл понимаю но реализация не очень — особенно интересует применение для XCODE при кодинге для iOs, может кто поделиться нормальной ссылке?
                    0
                    Он очень хорошо себя проявил в web-фреймворках. Возьмите любой mvc фреймворк для языка, который знаете (а он есть для большого количества) и разберитесь в конкретной реализации. Если у вас мак, то советую ruby on rails, по нему и мануалов тонны.
                        0
                        Кучу информации можно найти на Stackoverflow, ну или как вам посоветовали — посмотрите веб фреймворки — RoR, Django, Yii, если охота разбираться :-)
                        +4
                        Не нравится мне хардкод html элементов в виде. Ничем не лучше, по-моему, первого варианта. Не есть ли это смешение логики и отображения, просто на другом уровне абстракции? Может лучше оставить скриптам скриптовое (логику, включая логику отображения), а хэтээмэлу хэтээмэлово (собственно отображение)?
                          0
                          В качестве вью здесь так и напрашиваются шаблоны
                            0
                            Отчасти их и имел в виду :)
                            –1
                            «хэтээмэлу хэтээмэлово» — ведь так все и делают, не так ли? Если вас всё устраивает, то, конечно, нужно так и делать.

                            А как события вешать на html? Получатся что мы вернулись туда где начали.

                            Смысл именно данного подхода чтобы все изменения html были замкнуты только внутри Вида (33% кода).
                            Т.е. изменения нужно вносить не ища нужный html объект «где же лучше сделать фикс?» по всему объёму javascript кода плюс по всему html коду (100% javascript + 100% html) а только по 33% javascript-а.
                              0
                              «Все» делают не так. Что у «всех», что у вас получается мешанина html и js, только изменяется что во что встраивается. Имхо их надо максимально отделить друг от друга.

                              Пользовательские события вешать по id и другим селекторам, и вешать их в виде, а не в контроллере. Контроллер знает о виде, но не так много как у вас получилось.

                              Имхо, контроллер должен знать какие сообщения генерирует и принимает модель, а какие вид, а функция контроллера — трансляция их друг в друга. Создать вид (как объект) контроллер (или фронт контроллер, типа как у вас) должен, но вот формировать его «начинку» в виде buttonAdd не должен — имхо, это слишком сильное связывание между контроллером и видом. Этим должен заниматься конструктор вида, максимально инкапсулируя своё устройство Вид должен предоставлять интерфейс контроллеру в виде приёма сообщений типа «модель изменилась» и генерируя сообщения типа «пользователь хочет добавить/удалить элемент» (возможно прямыми сообщениями модели, возможно только через контроллер), но будет это баттон, хоткей, драг-н-дроп или жест мышью контроллер знать не должен, эти знания модель должна инкапсулировать и, по возможности, максимально их скрывать.
                                0
                                Если вешать эвенты в Виде, то чем тогда будет заниматься контроллер?
                                  0
                                  Биндить события модели на вид, а события вида на модель.

                                  Например, ваш контроллер из вызовов типа
                                  view.buttonAdd.bind('click', function () {
                                          model.addItem(prompt('addvalue'));
                                      });
                                  превратится в
                                  view.bind('AddItem', function () {
                                          model.addItem(prompt('addvalue'));
                                      });

                                  хотя и prompt('addvalue') место в виде по хорошему.
                                    0
                                    Про prompt согласен,
                                    а про AddItem, не очень понял, в чём принципиальная разница между
                                    view.buttonAdd.bind
                                    
                                    и
                                    view.bind('AddItem'
                                    
                                    Метод view.bind по массиву AddItem что получит? Тот же объект?
                                      0
                                      Неполностью свой код процитировали, потому и не понятно. Между
                                      view.buttonAdd.bind('click', ...)

                                      и
                                      view.bind('AddItem', ...)

                                      принципиальная разница в том, что в первом случае вы привязываетесь к событию click свойства buttonAdd объекта view, а во втором к событию AddItem объекта view, как бы он внутри вида не генерировался — хоть по нажатию кнопки, хоть по ссылке, хоть по таймеру. По-моему, очевидно, что во втором случае связывание контроллера с видом слабее, детали реализации спрятаны внутри вида, контроллер знает только то, что вид хочет добавить пункт в список, а почему он решил добавить ему не важно, а главное и не должно быть важно.
                                        +1
                                        понял, интересно,
                                        надо попробовать чтобы понять удобнее так или нет
                                          0
                                          Часто использование универсальных решений, введение дополнительных уровней абстракции и т. п. не удобно при разработке :( но очень удобно при развитии и поддержке :)

                                          В частности, писать решение столь же монструозное как ваше третье (по сравнению с первыми двумя) явно не удобно. По крайней мере пока его приходится писать каждый раз с нуля. Но вот выделить инфраструктуру таких решений в повторно используемый код и может и писать станет удобнее ;)
                                            0
                                            Первый вариант — 818 символов
                                            MVC — 3641, но, разумеется, makeObservableSubject надо отделить, он здесь показан для наглядности.
                                            Без makeObservableSubject — 1387 символов, вполне приемлемо.

                                            Также вид можно немного шаблонизировать
                                            Плюс я делал длинные выражения типа selectedIndexChangedSubject, они более понятные, но можно делать и аббревиатуры
                                              0
                                              Дело не в том, что можно ли разбить по отдельным файлам, а в том можно ли их повторно использовать и будут ли они повторно использоваться :)
                        • UFO just landed and posted this here
                          • UFO just landed and posted this here
                              0
                              Имхо, бизнес-логика (логика предметной области) должна быть представлена только в модели («совершить операцию такую-то, соблюдая такие-то ограничения целостности»). Контроллер контролирует :) логику приложения («может-ли текущий пользователь сделать эту операцию»), вид — логику представления («показать этому пользователю кнопку для этой операции»)
                              • UFO just landed and posted this here
                                  0
                                  Я тоже думаю, что ничего не изменилось :) Вы считаете, что область ответственности модели — это обеспечение хранение данных? Я так не считаю, я считаю, что область ответственности модели это целостное представление и управление предметной областью. Модель может включать в себя более низкий уровень хранения данных, но не обязана. Как раз хранением объектов модели может заниматься контроллер. Он предназначен для логики приложения, а не для бизнес-логики.

                                  Кстати, не только я так думаю. Например, цитата из вики
                                  Наиболее частые ошибки

                                  Начинающие программисты (особенно в веб-программировании) очень часто трактуют архитектурную модель MVC совершенно неправильно. Они рассматривают Модель (Model) исключительно как совокупность функций и/или методов для доступа к данным, а Контроллер (Controller) — как элемент системы, содержащий бизнес-логику. В результате код Моделей по факту является средством получения данных из СУБД, а Контроллер представляет собой типичный модуль, наполненный бизнес-логикой или скрипт в терминологии веб-программирования. В результате такого понимания MVC разработчики стали писать код, который известный в кругах Zend Framework сообщества разработчик Pádraic Brady охарактеризовал как ТТУК — «Толстые тупые уродливые контроллеры» (Fat Stupid Ugly Controllers)

                                  Ещё нужны цитаты?
                                  • UFO just landed and posted this here
                                      0
                                      Бизнес-логика оперирует терминами предметной области: для бухгалтерии — счета, операции, проводки и т. п., для какой-нить MMORPG — игроки, скилы, эквип, карта и т. д.

                                      Логика приложения оперирует, такими вещами как создание и уничтожение объектов модели и вида, обеспечение их персистентности, диспетчеризация (возможно, с конвертацией) сообщений от модели к виду и обратно, аутентификация, сессии и прочий мидлвар и прочим о чём, грубо говоря, заказчик и не догадывается, оперируя терминами своего бизнеса (бизнес-логика) и того, что он хочет увидеть на экране (вид и его логика)

                                      • UFO just landed and posted this here
                                          0
                                          Не вижу противоречия межу вашей ссылкой и моими словами. Или вы про себя так самокритично? ;)
                                          • UFO just landed and posted this here
                                              0
                                              Ну укажите, пожалуйста, где. А то так и буду насаживать «анти-MVC» на хабре, а вы мой каждый коммент не успеете поправить ;) Плюс не только хабром единым…
                                    • UFO just landed and posted this here
                                        0
                                        Ну если контроллер реализует бизнес-логику, то что остаётся модели? Только хранить данные и подавать их вовремя контроллеру и виду.
                                        • UFO just landed and posted this here
                                            0
                                            Хм… для меня объект модели это представление сущности (материальной или абстрактной — не важно) в приложении. Для приложения по сути объект модели и есть сущность. Он обслуживает сам себя?
                                            • UFO just landed and posted this here
                                                0
                                                Так и не понял, что значит «обслуживает сущность» и «логика сущности». Логика «всего» приложения = бизнес-логика (модель) + логика приложения (контроллер) + логика представления (вид).
                                                • UFO just landed and posted this here
                                                    0
                                                    Как бы только им и занимаюсь.
                                                    • UFO just landed and posted this here
                                                      0
                                                      а зачем оскорбительные намеки?
                                                      • UFO just landed and posted this here
                                            • UFO just landed and posted this here
                                      • UFO just landed and posted this here
                                    0
                                    Для полного «скопировал и запустил» вам в пример кода надо добавить
                                    <head>
                                      <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
                                    </head>
                                      +1
                                      спасибо, исправил
                                      0
                                      Я бы добавил к преимуществам, ещё и то, что можно использовать разные view для одной и той же модели, что для веба очень полезно. У нас на портале используется немного извращенный MVP =), и для десктопных пользователей используется один view, а если портал используется в мобильном браузере, то другой view, а жабаскриптовая модель и презентер остаются теми же самыми.
                                        0
                                        А тут этого нет по факту. Легко вы в этом примере не смените кнопку для десктопов на ссылку для мобильных.
                                        0
                                        Для selectedIndex все-таки место в контроллере, а не модели. Это состояние имеющее отношение к UI/view.
                                          0
                                          Это зависит от того, как дальше хотите развивать код, вы царь в своём коде.

                                          Если хочется функцию removeCurrentItem в модели, как здесь, то обязательно selectedIndex хранить там же, ведь модель о контроллере ничего не знает. Если функция модели будет removeItem(i) то, пожалуй. можно.
                                          Как сейчас — можно будет добавить дополнительный, read-only вид и он будет отображать актуальную информацию о том что выбрано в select.
                                          А можно сделать этот дополнительный вид тоже редактируемым, тогда будет как вы сказали, и состояние нужно будет хранить в виде или контре.
                                          Думаю напишу продолжение об этом попозже.
                                            0
                                            В вашей модели CurrentItem должен быть точно в виде. Или нужно вызывать that.modelChangedSubject.notifyObservers() и в this.setSelectedIndex, раз уж в вашем коде есть возможность вешать несколько слушателей на изменение состояния модели. А то получается, что состояние изменяется, а никто об этом не знает, кроме вызвывашего это изменение. Будет у вас два вида и в одном вы измените выбранный пункт, а в другом нажмёте «удалить» — удалится пункт выбранный в первой, а не во второй.
                                              0
                                              Ваша правда, исправил.
                                              У Модели в setSelectedIndex теперь нотификатор
                                                0
                                                А не зацикливается (либо еще какие побочные эффекты не возникают) от того, что сначала браузер в DOM'е ставит у одной option свойство selected, вид информирует об этом модель, а потом модель информирует об этом вид, а тот отрисовывает select без учёта selectedIndex модели.
                                          0
                                          Без MVC в крупном проекте никуда… будь то даже и JavaScript

                                          Сам был вдохновлён в своё время Action Script 3 и написал некий аналог на JavaScript
                                          пока полёт нормальный:)
                                            0
                                            Что, Action Script реализует MVC в терминах языка?

                                          Only users with full accounts can post comments. Log in, please.