MVC应用的数据建模基于Dojo

本文翻译自: http://dojotoolkit.org/documentation/tutorials/1.7/data_modeling/


MVC,即模型(Model),视图(View)和控制Controller),是当今应用开发的主导模式。这里,我们要从Dojo的基础功能出发,来看看Dojo是如何支持MVC应用的。通过这篇文章我们会了解到如何通过Dojo的object stores 和 Stateful objects(有状态的对象)来构建MVC应用,以及如何基于这些模型实现我们的显示层和控制层。

MVC应用的数据建模(基于Dojo)

MVC模式是当今应用开发的主导模式。该模式主要目的是组织严密的,易于管理的的代码结构。Dojo控件库自身的代码结构就是高度基于MVC模式的,同时也能很好的支持那些基于MVC模式的应用。一个好的MVC应用的核心是它能拥有一个完美的数据模式。接下来我们会介绍一下如何通过Dojo的Object stores和stateful objects(有状态对象)去构建一个包含view和controller的完美模式。

Model

Model层是MVC中的“M”,数据层指的是那些应用中需要访问和处理的核心数据信息,他是一个应用的中心,viewer和controller层则主要用来作为用户和Model(数据)层之间交互的一个桥梁。Model层则封装了存储和验证的过程。

Dojo的object store完美的担当了Dojo应用中Model的这一个角色。store的接口就是按照分离出数据层的思想来设计的。不同的存储媒介基于相同的store接口。Stores本身也支持扩展更为强大的功能。接下来我们来看看如何构建一个基本的store。我们将使用一个JsonRest的store,同时缓存住我们取到的数据:

require(["dojo/store/JsonRest","dojo/store/Memory","dojo/store/Cache","dojo/store/Observable"],function(JsonRest,Memory,Cache,Observable){
    masterStore = new JsonRest({
        target: "/Inventory/",idProperty: "name"
    });
    cacheStore = new Memory({ idProperty: "name" });
    inventoryStore = Cache(masterStore,cacheStore)

现在,我们的“inventoryStore”就成了我们的数据层,我们可以通过“get()”取数据,“query()”查询数据,“put()”修改数据。store这里封装了对真实数据的存储过程,包括与服务器(server)端的交互。

我们的视图层(viewer)便可以开始查询结果了:

results = inventoryStore.query("some-query");
viewResults(results);
  
// pass the results on to the view
function viewResults(results){
    var container = dom.byId("container");
  
    // results object provides a forEach method for iteration
    results.forEach(addRow);
  
    function addRow(item){
        var row = domConstruct.create("div",{
            innerHTML: item.name + " quantity: " + item.quantity
        },container);
    }
}

我们的 viewResults在这里就是充当了数据层的一个viewer。我们还能够通过dojo/string的substitute方法实现简单的模板化视图。
function addRow(item){
    var row = domConstruct.create("div",{
        innerHTML: string.substitute(tmpl,item);
    },container);
}

数据绑定

MVC里面比较重要的一块就是view层应该监听数据层的改动,然后及时的反应在界面上。这种方式避免了用controller去监听数据层的无畏的资源开销。Controller只需要更新model层,viewer会自动将该改动反映到应用的界面上。我们能够通过dojo/store/Observable来实现这一点。

masterStore = Observable(masterStore);
...
inventoryStore = Cache(masterStore,cacheStore);
现在我们的viewer能够通过observe的方式来监听查询结果的变化了
function viewResults(results){
    var container = dom.byId("container");
    var rows = [];
      
    results.forEach(insertRow);
  
    results.observe(function(item,removedIndex,insertedIndex){
        // this will be called any time a item is added,removed,and updates
        if(removedIndex > -1){
            removeRow(removedIndex);
        }
        if(insertedIndex > -1){
            insertRow(item,insertedIndex);
        }
    },true); // we can indicate to be notified of object updates as well
  
    function insertRow(item,i){
        var row = domConstruct.create("div",{
            innerHTML: item.name + " quantity: " + item.quantity
        });
        rows.splice(i,container.insertBefore(row,rows[i]));
    }
  
    function removeRow(i){
        domConstruct.destroy(rows.splice(i,1)[0]);
    }
}

现在我们viewer已经能够即时的反映model数据的变化了,与此同时,我们的controller相关代码也能够基于用户的操作来对model数据作出相应的修改。Controller可以通过put(),add(),和 remove()来修改数据。通常来讲,Controller相关代码主要用来处理事件,比如:当用户点击add按钮时,我们便创建一个新的数据对象。

on(addButton,"click",function(){
    inventoryStore.add({
        name: "Shoes",category: "Clothing",quantity: 40
    });
});
该行为会直接更新我们的应用界面(viewer),我们无需再用额外的代码去直接操作我们的界面了。这个时候我们的Controller就只需要基于用户的操作来修改model数据了。此刻,Model数据层的存储和view层的更新渲染就与我们的逻辑完全隔离开来了

数据模型进阶

之前我们用到的store都非常简单,没有包含任何逻辑(可能服务端会包含一些逻辑和验证)。我们其实可以在不影响其他模块的同时对store加入一些额外的功能。

验证

验证功能便可作为store的一个扩展,这个扩展对JsonRestStore来说非常简单,因为所有的更新都会调用put()方法(add() 会调用 put()),我们只需要扩展一下put方法即可。

var oldPut = inventoryStore.put;
inventoryStore.put = function(object,options){
    if(object.quantity < 0){
        throw new Error("quantity must not be negative");
    }
    // now call the original
    oldPut.call(this,object,options);
};
此时,验证逻辑已经被加入了。
inventoryStore.put({
    name: "Donuts",category: "Food",quantity: -1
});
由于quantity的值小于0,所以此时该方法会抛出异常。

Hierarchy层次结构

如同我们给我们的数据模型加入逻辑功能一样,我们也为元数据加入了一些特有的含义,其中之一就是层次结构。object store定义了getChildren()方法是我们能够实现我们的父---子结构。存放这种父---子结构有很多种方式。

stored objects可以存放一个指向其所有子对象的数组引用。这种做法适用于小的,顺序的列表数据。同样,objects也可以存放一个指向其父对象的引用,这种做法伸缩性更强。

为了实现第二种数据结构,我们可以加入getChildren()方法。在如下示例中,我们的构造的层次结构来源于我们的含有独立子对象的实例,我们创建getChildren()方法用于找到所有category属性值为该父对象名称的对象集合,这些对象就是该父对象的所有子对象。这就是将父/子关系定义为子对象的一个属性的解决方案。

inventoryStore.getChildren = function(parent,options){
    return this.query({
        category: parent.name
    },options);
};
现在,我们可以不用管数据的内部结构,而直接通过 getChildren()获取父对象的所有子节点,检索子节点方式如下:
require(["dojo/_base/Deferred"],function(Deferred){
    Deferred.when(inventoryStore.get("Food"),function(foodCategory){
        // retrieved the food category object,now get it's children
        inventoryStore.getChildren(foodCategory).forEach(function(food){
            // handle each item in the food category
        });
    });
至此,我们能取得子节点的对象,接下来我们来看看如何修改他们。我们知道category定义了数据的层次结构,如果我们改变某个对象元素的层次结构,我们只需要修改category的属性值即可。
donut.category = "Junk Food";
inventoryStore.put(donut);
Dojo stores的一个核心思想就是提供统一的数据操作接口。如果我们想简单的通过设定父对象的方式来定义对象的层次结构,我们可以在 put()方法里使用 options 参数集里的 parent属性。
inventoryStore.put = function(object,options){
    if(options.parent){
        object.category = parent;
    }
    // ...
};
现在我们可以设置父对象了。
inventoryStore.put(donut,{parent: "Junk Food"});


有序的Store

默认情况下,一个store通常是一群无序对象的集合。尽管如此,我们还是可以实现store的有序排列,尤其是对象集合中已经存在预留的隐含的序列属性时。实现有序store的第一个工作就是在调用query()方法后返回一个有序的store(当且仅当没有其他排序设定的影响)。实现它往往不需要对store作扩展,你只需要返回相应顺序的数据序列即可。

有序的Store通常还有一个需求,就是其元素能够前后移动,甚至直接到最前或者最后等等。我们可以通过put()方法的options参数中加入before属性来实现。

inventoryStore.put = function(object,options){
    if(options.before){
        // we set the reference object's name in the object's "insertBefore" 
        // so the server can put the object in the right order
        object.insertBefore = options.before.name;
    }
    // ...
};
服务器现在可以通过insertBefore属性来做排序了。我们的controller层的代码可以开始移动我们的对象了(我们使用事件代理并假设节点的 itemIdbeforeId已经在创建时设定了):
require(["dojo/on"],function(on){
    on(moveUpButton,".move-up:click",function(){
        // |this| in event delegation is the node
        // matching the given selector
        inventoryStore.put(inventoryStore.get(this.itemId),{
            before: inventoryStore.get(this.beforeId)
        });
    }); 


事物

事物是应用程序里面比较重要的一块,通常用于把一系列应用的逻辑操作绑定到一起,一次性执行。他的作用之一就是集合一系列操作然后通过单一请求一次性提交,示例如下:

require(["dojo/_base/lang"],function(lang){
    lang.mixin(inventoryStore,{
        transaction: function(){
            // start a transaction,create a new array of operations
            this.operations = [];
            var store = this;
            return {
                commit: function(){
                    // commit the transaction,sending all the operations in a single request
                    return xhr.post({
                        url:"/Inventory/",// send all the operations in the body
                        postData: JSON.stringify(store.operations)
                    });
                },abort: function(){
                    store.operations = [];
                }
            };
        },put: function(object,options){
            // ... any other logic ...
              
            // add it to the queue of operations
            this.operations.push({action:"put",object:object});
        },remove: function(id){
            // add it to the queue of operations
            this.operations.push({action:"remove",id:id});
        }
    });
基于上述代码,我们可以利用事物来构建我们的自定义操作:
removeCategory: function(category){
    // atomically remove entire category and the items within the category
    var transaction = this.transaction();
      
    var store = this;
    this.getChildren(category).forEach(function(item){
        // remove each child
        store.remove(item.name);
    },this).then(function(){
        // now remove the category
        store.remove(category.name);
        // all done,commit the changes
        transaction.commit();
    });
}


数据绑定: dojo/Stateful

Dojo对集合层次和实体层次的数据模型有一个清晰的界限,Dojo的store提供了集合层次的模型,接下来我们来看看实体层次的对象模型。Dojo对独立的对象也使用了统一的接口。这里我们可以使用dojo/Stateful的接口与对象交互。接口很简单,主要有三个方法:

  • get(name) - 检索属性值
  • set(name,value) - 设置属性值
  • watch(name,listener) - 注册一个监听属性变化的callback方法(不设第一个参数则表示监听所有属性改动)

和之前介绍的viewer绑定数据一样,他能及时反映相应的数据变化。首先,我们先创建一个viewer,绑定到一个对象:

<form id="itemForm">
    Name: <input type="text" name="name" />
    Quantity: <input type="text" name="quantity" />
</form>
然后,我们绑定到HTML:
function viewInForm(object,form){
    // copy initial values into form inputs
    for(var i in object){
        updateInput(i,null,object.get(i));
    }
    // watch for any future changes in the object
    object.watch(updateInput);
    function updateInput(name,oldValue,newValue){
        var input = query("input[name=" + name + "]",form)[0];
        if(input){
            input.value = newValue;
        }
    }
}
现在我们可以通过store里面的一个对象初始化form:
require(["dojo/Stateful","dojo/_base/Deferred"],function(Stateful,Deferred){
    Deferred.when(store.get("Donut"),function(item){
        item = new Stateful(item); // wrap with stateful
        viewInForm(item,dom.byId("itemForm"));
    });

现在我们如果通过controller代码修改这个对象,在viewer上就会马上反映出来。

item.set("quantity",4);

在这个例子中,我们可能也想加入 onchange事件用于监听用户输入进而修改对象本身,以达到一个双向的绑定。(object的改动会反映到form上,同样form的改动也会影响object)。Dojo的form manager(dojox.form.manager)同样也提供了很多高级的交互功能。

接下来我们还要记住将修改了的对象传回store里面,如下:

on(saveButton,function(){
    inventoryStore.put(currentItem); // save the current state of the Stateful item
});

总结

通过使用Dojo的store架构和stateful(有状态的)接口,我们便有了构建我们MVC应用的利器。Viewers能监听数据的变化。Controllers能通过统一的接口来操作数据而不用知道数据的特殊结构,同时也不需要额外的代码来操作viewer的变化。集合和实体的接口边界清晰。所有这些都能帮助您构建您自己的MVC应用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易知道用户保存了什么,我希望当用户第一次看到网格时,网格处于不可编辑状态.当用户准备好后,他们可以单击编辑按钮,这将使网格的某些部分可编辑.然后,有一个保存或取消按钮可以保存更改或还原.在大多数情况下它是有效的.但
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以轻松打包为Web,Mobiles和可能的Standalone版本的方式来实现这一目标.我不想使用Flash.因此,我确信(无论如何我会听取建议)使用JavaScript和SVG.我正在对这个问题进行大量研究,但我很难把各个部分放在一起.我知道Raphae
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部件–我在表单中使用–将固定格式(JavaScript基本格式)的实数值(例如:12.56)设置为HTML表单输入字段(但根据浏览器区域设置显示/编辑它们,所以用户总是看到格式正确的数字).另一方面,Grails期望输入字段根据浏览器
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforjavaScript的学习历程,本篇将从最开始的arcgisapiforjavaScript部署开始,个人声明:博文不在传道受业解惑,旨在方便日后复习查阅。由于是自学,文章中可能会出现一些纰漏,请留言指出,不必留有情面哦!2.下载ArcGISforDe
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑:Thedeclarefunctionisdefinedinthedojo/_base/declaremodule.declareacceptsthreearguments:className,superClass,andproperties.ClassNameTheclassNameargumentrepresentsthenameofthec
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这个问题曾多次被问到,但为了弄清楚我的事实,我需要澄清一些事情,因为我在客户端技术方面的经验非常有限.我们决定使用GWT而不是纯JavaScript框架构建我们的解决方案(假设有更多的Java经验).这些是支持我的决定的事实.>
路由dojo/framework/srcouting/README.mdcommitb682b06ace25eea86d190e56dd81042565b35ed1Dojo应用程序的路由路由FeaturesRoute配置路径参数RouterHistoryManagersHashHistoryStateHistoryMemoryHistoryOutletEventRouterContextInjectionOutl
请原谅我的无知,因为我对jquery并不熟悉.是否有dojo.connect()的等价物?我找到了这个解决方案:http:/hink-robot.com/2009/06/hitch-object-oriented-event-handlers-with-jquery/但是没有断开功能!你知道jquery的其他解决方案吗?有jquery.connect但这个插件在我的测试中不起作用.
与java类一样,在dojo里也可以定义constructor 构造函数,在创建一个实例时可以对需要的属性进行初始化。//定义一个类mqsy_yjvar mqsy_yj=declare(null,{     //thedefaultusername    username: "yanjun",          //theconstructor   
我一直在寻找一些最佳实践,并想知道Dojo是否具有框架特定的最佳实践,还是最好只使用通用的Javascript标准?特别是我主要是寻找一些功能和类评论的指导方针?解决方法:对于初学者来说,这是项目的风格指南:DojoStyleGuide
我有’05/17/2010’的价值我想通过使用dojo.date.locale将其作为“2010年5月17日”.我尝试过使用dojo.date.locale.parse,如下所示:x='05/17/2010'varx=dojo.date.locale.parse(x,{datePattern:"MM/dd/yyyy",selector:"date"});alert(x)这并没有给我所需的日期
我正在尝试创建一个包含函数的dojo类,这些函数又调用此类中的其他函数,如下所示:dojo.provide("my.drawing");dojo.declare("my.drawing",null,{constructor:function(/*Object*/args){dojo.safeMixin(this,args);this.container=args[0];
我知道你可以使用jQuery.noConflict为jQuery做这件事.有没有办法与Dojo做类似的事情?解决方法:我相信你可以.有关在页面上运行多个版本的Dojo,请参阅thispage.它很繁琐,但似乎是你正在寻找的东西.一般来说,Dojo和jQuery都非常小心,不会破坏彼此或其他任何人的变量名.
我有一个EnhancedGrid,用户经常使用复杂的过滤器.有没有办法允许用户保存或标记过滤器,以便将来可以轻松地重新应用它?我知道我可以通过编程方式设置过滤器,但我无法预测用户想要的过滤器.谢谢!编辑:自己做了一些进展…使用grid.getFilter()返回过滤器的JSON表示,然后使用json.strin
我有这个代码:dojo.declare("City",null,{constructor:function(cityid,cityinfo){}});dojo.declare("TPolyline",GPolyline,{constructor:function(points,color){},initialize:function(map){});应该是什
我遇到的问题是我的所有javascript错误似乎来自dojo.xd.js或子模块.我正在使用chrome调试器和许多dijit功能,如dijit.declaration和dojo.parser.这有点烦人,因为它很难找到简单的错误或滑倒.我希望我可以添加一个选项,允许我的调试器在我的非dojo代码中显示选项会发生的位置.我是
我正在使用DojoToolkit数字/解析函数来处理格式化和使用ICU模式语法解析字符串.有没有人知道有可能采取任意ICU模式字符串并以某种方式使用Dojo(或其他)库将其分解为它的部分(例如,对于数字模式,它可以被分解为小数位数,数千个分组等…).我希望这样做,而不需要让我的代码密切了
我有两个看似相关的问题,访问在不同的地方定义的javascript函数.我遇到的第一个问题是调用我在firgbug或safari控制台中定义的函数.我定义了一个名为getRed的函数,如下所示:functiongetRed(row,col){//dosomethingstuffandreturntheredvalueasa
我想添加一个在Ajax调用中指定的外部样式表.我已经找到了一种方法来使用jQuery(参见下面的示例),但是我需要使该方法适应dojoJavaScript框架.JQuery示例$('head').append('<linkrel="stylesheet"type="text/css"href="lightbox_stylesheet.css">');谢谢.解决方法:一旦你
我正在尝试使用dojo.connect将onMouseDown事件连接到图像,如:dojo.connect(dojo.byId("workpic"),"onMouseDown",workpicDown);functionworkpicDown(){alert("mousedown");}类似的代码几行后,我将onMouse*事件连接到dojo.body确实完全正常工作.但是当我点击图像时