MVVM与Backbone demo
MVVM
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel
Model–view–view-model (MVVM) is a software architectural pattern.
MVVM facilitates a separation of development of the graphical user interface – be it via a markup language or GUI code – from development of the business logic or back-end logic (the data model). The view model of MVVM is a value converter;[1] meaning the view model is responsible for exposing (converting) the data objects from the model in such a way that objects are easily managed and presented. In this respect, the view model is more model than view, and handles most if not all of the view's display logic.[1] The view model may implement a mediator pattern, organizing access to the back-end logic around the set of use cases supported by the view.
- View model
- The view model is an abstraction of the view exposing public properties and commands. Instead of the controller of the MVC pattern, or the presenter of the MVP pattern, MVVM has a binder. In the view model, the binder mediates communication between the view and the data binder.[clarification needed] The view model has been described as a state of the data in the model.[7]
- Binder
- Declarative data- and command-binding are implicit in the MVVM pattern. In the Microsoft solution stack, the binder is a markup language called XAML.[8] The binder frees the developer from being obliged to write boiler-plate logic to synchronize the view model and view. When implemented outside of the Microsoft stack the presence of a declarative databinding technology is a key enabler of the pattern.[4][9][clarification needed]
国内权威解释:
https://linux.cn/article-6481-1.html?utm_medium=weibo
MVVM代表的是Model-View-ViewModel,这里需要解释一下什么是ViewModel。ViewModel的含义就是 "Model of View",视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。 在图形界面应用程序当中,界面所提供的信息可能不仅仅包含应用程序的领域模型。还可能包含一些领域模型不包含的视图状态,例如电子表格程序上需要显示当前排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。
可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。
MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。
MVC 和 MVP
https://linux.cn/article-6481-1.html?utm_medium=weibo
用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。如下图:
MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。
关键点:
- View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有应用程序逻辑也有同步逻辑。
- View需要提供操作界面的接口给Presenter进行调用。(关键)
Segment解释 ViewModel
https://segmentfault.com/q/1010000000534091
而MVVM派的看法是,我给view里面的各种控件也定义一个对应的数据对象,这样,只要修改这个数据对象,view里面显示的内容就自动跟着刷新,而在view 里做了任何操作,这个数据对象也跟着自动更新,这样多美。所以:
ViewModel:就是与界面(view)对应的Model。因为,数据库结构往往是不能直接跟界面控件一一对应上的,所以,需要再定义一个数据对象专门对应view上的控件。而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象。
至于viewmodel的数据随着view自动刷新,并且同步到model里去,这部分代码可以写成公用的框架,不用程序员自己操心了。
想好说清楚,最好就是使用用例来解释MVVM是如何工作的,下图以AngularJS为例。
如图所示,
App.html
是与app.js
中的ViewModel
绑定的View
。如果有人想要修改app.js
中的$scope.user
的name
属性,那么它会自动更新app.html
中对应的name
属性。反之也成立,如果有人想要修改app.html
中的name
属性值,那么也会自动更新$scope.user
对象。App.html
同时也可以对app.js
发出命令,如调用$scope.save
函数从而让app.js
执行一些逻辑操作,如保存$scope.user
至users.js
模型中并与后端同步,那么它就可以与整个应用程序共享。
MVVM
模式对于RIA
应用来说是非常必要的,因为View
被绑定至ViewModel
的,并且当ViewModel
的状态变化时,View
会自动更新,它有效的隔离了View
和它背后的业务逻辑。这也是为什么AngularJS需要双向绑定的原因。
知乎解释
https://www.zhihu.com/question/28893576
去年暑假实习一直在做MVVM的开发。就我所知所有的后端和业务逻辑都应该是在M端的,写界面XAML是V端,前后端之间可以互不干扰完成设计/开发,最后再加一个VM,而VM做的事就是衔接V和M。Wiki上这一段很清楚地解释了view model的作用,也就是把model中的数据对象转换成view可以直接使用的形式(比如string或者collection等等)。
视图层 V,展现数据,绑定事件,
视图模型层 VM, 从数据模型层变换而来, 除了数据或状态flag,还有许多事件回调,$watch回调(当某个数据变动时会执行的函数), widget的配置对象
模型层 M, 这是通过AJAX请求得到,或者通过后端输出时就打印在页面某个script标签内, 都是数据(JSON),没有函数
业务逻辑层 BS,这不归为MVVM之内,但它们应该使用类进行组织,在VM中的某个函数里面被调用。
Backbone
http://backbonejs.org/#Getting-started
Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
When working on a web application that involves a lot of JavaScript, one of the first things you learn is to stop tying your data to the DOM. It's all too easy to create JavaScript applications that end up as tangled piles of jQuery selectors and callbacks, all trying frantically to keep data in sync between the HTML UI, your JavaScript logic, and the database on your server. For rich client-side applications, a more structured approach is often helpful.
With Backbone, you represent your data as Models, which can be created, validated, destroyed, and saved to the server. Whenever a UI action causes an attribute of a model to change, the model triggers a "change" event; all the Views that display the model's state can be notified of the change, so that they are able to respond accordingly, re-rendering themselves with the new information. In a finished Backbone app, you don't have to write the glue code that looks into the DOM to find an element with a specific id, and update the HTML manually — when the model changes, the views simply update themselves.
Philosophically, Backbone is an attempt to discover the minimal set of data-structuring (models and collections) and user interface (views and URLs) primitives that are generally useful when building web applications with JavaScript. In an ecosystem where overarching, decides-everything-for-you frameworks are commonplace, and many libraries require your site to be reorganized to suit their look, feel, and default behavior — Backbone should continue to be a tool that gives you the freedom to design the full experience of your web application.
Backbone Demo
todolist
http://backbonejs.org/examples/todos/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Backbone.js Todos</title> <link rel="stylesheet" href="todos.css"/> </head> <body> <div id="todoapp"> <header> <h1>Todos</h1> <input id="new-todo" type="text" placeholder="What needs to be done?"> </header> <section id="main"> <input id="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul id="todo-list"></ul> </section> <footer> <a id="clear-completed">Clear completed</a> <div id="todo-count"></div> </footer> </div> <div id="instructions"> Double-click to edit a todo. </div> <div id="credits"> Created by <br /> <a href="http://jgn.me/">Jérôme Gravel-Niquet</a>. <br />Rewritten by: <a href="https://github.com/tastejs/todomvc">TodoMVC</a>. </div> <script src="../../test/vendor/json2.js"></script> <script src="../../test/vendor/jquery.js"></script> <script src="../../test/vendor/underscore.js"></script> <script src="../../backbone.js"></script> <script src="../backbone.localStorage.js"></script> <script src="todos.js"></script> <!-- Templates --> <script type="text/template" id="item-template"> <div class="view"> <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> /> <label><%- title %></label> <a class="destroy"></a> </div> <input class="edit" type="text" value="<%- title %>" /> </script> <script type="text/template" id="stats-template"> <% if (done) { %> <a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a> <% } %> <div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div> </script> </body> </html>
// An example Backbone application contributed by // [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple // [LocalStorage adapter](backbone.localStorage.html) // to persist Backbone models within your browser. // Load the application once the DOM is ready, using `jQuery.ready`: $(function(){ // Todo Model // ---------- // Our basic **Todo** model has `title`, `order`, and `done` attributes. var Todo = Backbone.Model.extend({ // Default attributes for the todo item. defaults: function() { return { title: "empty todo...", order: Todos.nextOrder(), done: false }; }, // Toggle the `done` state of this todo item. toggle: function() { this.save({done: !this.get("done")}); } }); // Todo Collection // --------------- // The collection of todos is backed by *localStorage* instead of a remote // server. var TodoList = Backbone.Collection.extend({ // Reference to this collection's model. model: Todo, // Save all of the todo items under the `"todos-backbone"` namespace. localStorage: new Backbone.LocalStorage("todos-backbone"), // Filter down the list of all todo items that are finished. done: function() { return this.where({done: true}); }, // Filter down the list to only todo items that are still not finished. remaining: function() { return this.where({done: false}); }, // We keep the Todos in sequential order, despite being saved by unordered // GUID in the database. This generates the next order number for new items. nextOrder: function() { if (!this.length) return 1; return this.last().get('order') + 1; }, // Todos are sorted by their original insertion order. comparator: 'order' }); // Create our global collection of **Todos**. var Todos = new TodoList; // Todo Item View // -------------- // The DOM element for a todo item... var TodoView = Backbone.View.extend({ //... is a list tag. tagName: "li", // Cache the template function for a single item. template: _.template($('#item-template').html()), // The DOM events specific to an item. events: { "click .toggle" : "toggleDone", "dblclick .view" : "edit", "click a.destroy" : "clear", "keypress .edit" : "updateOnEnter", "blur .edit" : "close" }, // The TodoView listens for changes to its model, re-rendering. Since there's // a one-to-one correspondence between a **Todo** and a **TodoView** in this // app, we set a direct reference on the model for convenience. initialize: function() { this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); }, // Re-render the titles of the todo item. render: function() { this.$el.html(this.template(this.model.toJSON())); this.$el.toggleClass('done', this.model.get('done')); this.input = this.$('.edit'); return this; }, // Toggle the `"done"` state of the model. toggleDone: function() { this.model.toggle(); }, // Switch this view into `"editing"` mode, displaying the input field. edit: function() { this.$el.addClass("editing"); this.input.focus(); }, // Close the `"editing"` mode, saving changes to the todo. close: function() { var value = this.input.val(); if (!value) { this.clear(); } else { this.model.save({title: value}); this.$el.removeClass("editing"); } }, // If you hit `enter`, we're through editing the item. updateOnEnter: function(e) { if (e.keyCode == 13) this.close(); }, // Remove the item, destroy the model. clear: function() { this.model.destroy(); } }); // The Application // --------------- // Our overall **AppView** is the top-level piece of UI. var AppView = Backbone.View.extend({ // Instead of generating a new element, bind to the existing skeleton of // the App already present in the HTML. el: $("#todoapp"), // Our template for the line of statistics at the bottom of the app. statsTemplate: _.template($('#stats-template').html()), // Delegated events for creating new items, and clearing completed ones. events: { "keypress #new-todo": "createOnEnter", "click #clear-completed": "clearCompleted", "click #toggle-all": "toggleAllComplete" }, // At initialization we bind to the relevant events on the `Todos` // collection, when items are added or changed. Kick things off by // loading any preexisting todos that might be saved in *localStorage*. initialize: function() { this.input = this.$("#new-todo"); this.allCheckbox = this.$("#toggle-all")[0]; this.listenTo(Todos, 'add', this.addOne); this.listenTo(Todos, 'reset', this.addAll); this.listenTo(Todos, 'all', this.render); this.footer = this.$('footer'); this.main = $('#main'); Todos.fetch(); }, // Re-rendering the App just means refreshing the statistics -- the rest // of the app doesn't change. render: function() { var done = Todos.done().length; var remaining = Todos.remaining().length; if (Todos.length) { this.main.show(); this.footer.show(); this.footer.html(this.statsTemplate({done: done, remaining: remaining})); } else { this.main.hide(); this.footer.hide(); } this.allCheckbox.checked = !remaining; }, // Add a single todo item to the list by creating a view for it, and // appending its element to the `<ul>`. addOne: function(todo) { var view = new TodoView({model: todo}); this.$("#todo-list").append(view.render().el); }, // Add all items in the **Todos** collection at once. addAll: function() { Todos.each(this.addOne, this); }, // If you hit return in the main input field, create new **Todo** model, // persisting it to *localStorage*. createOnEnter: function(e) { if (e.keyCode != 13) return; if (!this.input.val()) return; Todos.create({title: this.input.val()}); this.input.val(''); }, // Clear all done todo items, destroying their models. clearCompleted: function() { _.invoke(Todos.done(), 'destroy'); return false; }, toggleAllComplete: function () { var done = this.allCheckbox.checked; Todos.each(function (todo) { todo.save({'done': done}); }); } }); // Finally, we kick things off by creating the **App**. var App = new AppView; });
Backbone 如何实现双向数据绑定?
http://codedocker.com/backbone-events-introduce/
model to view
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<span class="name"></span>' }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');
view to model
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<input type="text" class="name-input"><span class="name"></span>', events: {'input .name-input': '_changeName'}, _changeName: function(e){ var value = e.currentTarget.value; this.model.set('name', value); return false; } }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');