HTML5离线MVC:第2部分





0/5 (0投票)
离线Web应用MVC
引言
这是我的 HTML5 离线 MVC 系列文章的第二部分。目标是创建一个可在离线状态下运行的 iOS Web 应用。因此,我们将断开与所有 .NET MVC 的依赖。但我们仍然可以尝试维护类似的结构。本质上,我们正在创建一个可重用的 MVC 框架。本文系列的这一部分将在我们的控制器之上创建一个模型。
文章布局
- 创建 JavaScript MVC 结构- 创建视图控制器结构
- 创建控制器模型结构 <- (就是这个:)
 
- 连接并与 WebSql 数据库交互
- 通过 applicationCache 使整个应用离线
实现
在上一篇文章中,我们创建了源代码,拥有了一个简单而有效的路由/控制器/视图结构。现在我们需要专注于创建模型。在上一篇文章的源代码下载中,我有一个联系人控制器。在其中,我只是放了一个联系人数组。数组仍然是数据源,不应该放在控制器中。
模型可以以多种方式设计。但本质上,它们都应该具有以下方法:
- select(获取所有)
- get(id) (获取单个)
- 插入
- update
- 删除
模型应该包含所有业务逻辑。在此示例中,我不会添加任何内容,但在实际情况下,这里是说明“不允许这样做”的地方。
但数据本身并不在模型中!在这里,我们将创建一个模型结构,该结构可以将数据存储在 WebSql、localStorage 或在线 WCF 服务中。
我们所有的模型都将有一个 store 对象在其之上。这是实际保存数据的部分。
让我们来看看我们的联系人模型的最终结果。
namespace("Demo.Models", function(NS) {
    //Our simple model
    var Contacts={
        //Tel the CoreModal singleton which fields we've got and what their attributes should be
        fields: [
            { name: "id", attributes: "INTEGER PRIMARY KEY AUTOINCREMENT"},
            { name: "name"},
            { name: "lastname"},
            { name: "phonenumber"},
            { name: "email" }
        ],
        //Select all
        select: function(config)  {
            //Right now this is to simple were just passing on the success and failure functions,
            //but this is the model, so if we would have business logic 
            //we would trap the success handler in here first
            //apply the business logic an than return the success function 
            //that came in through the config object
            var success=config.success;
            var failure=config.failure;
            this.store.select(config);
        },
        //Get one
        get: function(id, config)   {
            var success=config.success;
            var failure=config.failure;
            this.store.get(id, config);
        },
        //add one
        insert: function(record, config)   {
            var success=config.success;
            var failure=config.failure;
            this.store.insert(record, config);
        },
        //update one
        update: function(id, record, config)  {
            var success=config.success;
            var failure=config.failure;
            this.store.update(id, record, config);
        },
        //delete one
        delete: function(id, config)    {
            this.store.delete(id, config);
        }
    }
    Demo.Models.Contacts=Contacts;
});
正如您可能注意到的,这都是异步就绪的。即使我们只是将记录放入数组中,我们仍然会应用成功/失败处理程序。这是因为我们希望我们的控制器和模型始终保持相同的结构。无论数据如何存储。对于在线 WCF/httpRequests 和 WebSql,我们需要异步处理,因此需要成功/失败回调结构。
让我们看看我们的联系人控制器的一部分,看看我们如何使用这个模型。
namespace("Demo.Controllers", function(NS) {
    var Contacts=function()    {
        var _self=this;
        //tell the parent class which models to prepare, 
        //this is an array of string representations
        //that match the namespace/objects where you're models reside.
        _self.models=["Demo.Models.Contacts"];
        Core.apply(_self, Core.Controller.prototype);
        return Core.Controller.apply(_self, arguments);
    };
    Contacts.prototype.index=function()    {
        //contacts/index
        var _self=this;
        //if the select succeeds we'll show the data
        var selectSuccess=function(data)  {
            _self.viewBag.contacts=data;
            _self.view();
        };
        //Ask our model to select all the records
        _self.models["Contacts"].select({success:selectSuccess});
    }
    Contacts.prototype.contact=function(action, id)    {
        //contacts/contact/{id}
        var _self=this;
        //ask our model to get a single record
        _self.models["Contacts"].get(id, {
            //We can also code the success function inline
            success: function(data) {
                //Stick the data in the viewbag
                _self.viewBag=data[0];
                //Run our view;
                _self.view();
            }
        });
    }
    ........
我们将如何实现这一点?
现在这看起来很酷。但我们显然需要围绕它的一些代码才能使其正常工作。
- 在上一篇文章中,我们创建了 Core.Controller类,它包含了使我们的控制器本身看起来尽可能简单的所有内容。这个类需要得到一定的扩展。
- 我们需要一个新的单例 Core.Model,它将返回模型并创建/查询 store。
- 我们需要一个实际保存数据的 store 类。
在上一篇文章中,我试图解释和展示我所能提供的所有源代码。但我们还有很长的路要走。因此,对于一些细节,您将不得不下载源代码。
我现在将简要解释 Core.Controller 的作用。
Core.Controller
在我们的联系人控制器中,我们指定我们希望有一个联系人模型。Core.Controller 类需要被扩展,以要求 Core.Model 单例返回该模型作为一个可工作的对象。
下载源代码后,您可以在构造函数的 return 函数中看到这是一个循环。
//The models that are ready
var readyModels={};
//We'll need to know how many models there are
var numberOfModels=_self.models.length;
//loop through the models
for(var i=0, j=_self.models.length; i<   j; i++)    {
    //container for the model
    var success=function(name, model)  {
        //add the current mode to readyModels
        readyModels[name]=model;
        //count down, for if we had more than one model
        //the controllerAction should be ran after all of them are ready
        //we cannot reference i because this is asynchronised
        numberOfModels--;
        if(numberOfModels==0)   {
            //all the models are ready
            //replace the string array of models with the ready models
            _self.models=readyModels;
            //set the modelsReady boolean
            _self.modelsReady=true;
            //run the controller action
            _self[controllerAction].apply(_self, callArguments);
        }
    }
    //get the model
    Core.Model.getModel(_self.models[i], success);
}  
Core.Model
Core.Model 单例将返回 Core.Controller 所请求的模型,并将 store 类绑定到模型之上。
namespace("Core", function(NS)    {
    //Core.Model has the task of binding the models to the controllers
    //and to hide the database interaction from the models
    Core.Model={
        //What models that we already create we'd like to prevent doing it again
        models: {},
        //Return the model to the controller
        getModel: function(name, callback)    {
            //This will get the object out of a string like "Demo.Namespace1.Object1"
            var obj=Core.getObject(name);
            //We can not namespace our tables in something like WebSql.
            //This is a concern it's not possible to have
            //multiple PERSON tables in different namespaces.
            //Here we cut of the namespace and work just with the classname.
            //This might be a point where perfection is needed.
            var shortName=Core.getLastNsPart(name);
            var _callback=function()    {
            callback(shortName, obj);
            }
            if(obj!==undefined)    {
                if(this.models.hasOwnProperty(shortName))    {
                    //We've already got one. So we don't have to make it again. Just do callback
                    _callback();
                }
                else    {
                    //What type of store do we need to create
                    switch(obj.type)    {
                        case "MEMORY":
                            obj.store=new Core.Data.MemoryDataStore();
                        break;
                        case "PERSISTENT":
                        //this line is a sneak preview. We'll be adding this in the next article
                        obj.store=new Core.Data.PersistentDataStore();
                        break;
                        default:
                            obj.store=new Core.Data.MemoryDataStore();
                        break;
                    }
                    //Send in the fields specified in the Model so the store can create itself
                    obj.store.create({ name: shortName }, obj.fields, _callback);
                    //Remember this model to prevent executing this code more than once
                    this.models[shortName]=obj;
                 }
            }
            else    {
                throw new Error("Model " + name + " doesn't exist!");
            }
        },
    }
});
DataStore 类
呼,这仅仅是为了创建一个简单的东西花费了这么长时间。一个对象数组。在这篇文章中,我将创建 DataStore 类,它只将数据保存在内存中。因此,它基本上是无用的,但它将让我们很好地理解这样一个 store 需要做什么。
之后,我们可以创建我们的 WebSql datastore。但由于处理 WebSql 本身就值得写一篇文章,我们将把这部分分开。
DataStore 需要做什么?
- 创建
- select
- get
- 插入
- update
- 删除
我们的类将继承一个我不会详细介绍的超类。
namespace("Core.Data", function(NS) {
    var MemoryDataStore=function()  {
        //The name of our store
        this.name=null;
        //The fields
        this.fields=null;
        //The keyname
        this.key=null;
        //What holds the actual items
        this.items=new Array();
        //The key increment
        this.autoincrement=0;
        return this;
    };
    MemoryDataStore.prototype=new Core.Data.DataStore();
    MemoryDataStore.prototype.create=function(config, fields, callback) {
        //copy some base information
        this.name=config.name;
        //copy the fields
        this.fields=fields;
        //We'll need a primary key. We cannot just use the array index.
        //For it will change when deleting a record.
        var getPrimaryKey=function(fields)  {
            for(var i=0, j=fields.length; i<j; i++)  {
                //look for the primary key
                if(fields[i].key)   {
                    //we found it
                    return fields[i].name;
                }
            }
            return null;
        };
        //Get the key field.
        this.key=getPrimaryKey(fields);
        //the primary key is important, we cannot just use the array index, 
        //because that will change
        //when a entry is deleted
        if(this.key==null)  {
            throw new Error("No primary key found for : " + this.name);
        }
        //do a callback
        callback();
    };
    MemoryDataStore.prototype.select=function(config)   {
        //Since this is just an array it'll be rather simple
        config.success(this.items);
    };
    MemoryDataStore.prototype.get=function(id, config)  {
        //gets a record with the primary key id
        var _self=this;
        var record=null;
        var getRecord=function(id)  {
            for(var i=0, j=_self.items.length; i<j; i++)    {
                if(_self.items[i][_self.key]==id)    {
                    return _self.items[i];
                }
            }
            return null;
        }
        record=getRecord(id);
        if(record==null)    {
            config.failure("Record not found!");
        }
        else    {
            config.success([record]);
        }
    };
    MemoryDataStore.prototype.insert=function(record, config)   {
        //Inserts record
        this.autoincrement++;
        record[this.key]=this.autoincrement;
        this.items[this.items.length]=record;
        //We will return the record in case the model needs to know what id we gave it.
        config.success(record);
    };
    MemoryDataStore.prototype.update=function(id, record, config)   {
        //Updates a record
        var _self=this;
        var index=null;
        var getIndex=function(id)   {
            //Find the record with primary key id
            for(var i=0, j=_self.items.length; i<j; i++) {
                if(_self.items[i][_self.key]==id)   {
                    return i;
                }
            }
            return -1;
        }
        index=getIndex(id);
        if(index==-1)   {
            config.failure("Record not found!");
        }
        else    {
            this.items[index]=record;
            config.success();
        }
    };
    MemoryDataStore.prototype.delete=function(id, config)   {
        //Deletes a record
        var _self=this;
        var index=null;
        var getIndex=function(id)   {
            //Find the record with primary key id
            for(var i=0, j=_self.items.length; i<j; i++) {
                if(_self.items[i][_self.key]==id)   {
                    return i;
                }
            }
            return -1;
        }
        index=getIndex(id);
        if(index==-1)   {
            config.failure("Record not found!");
        }
        else    {
            //Delete the actual record
            this.items.splice(index, 1);
            config.success();
        }
    };
    NS["MemoryDataStore"]=MemoryDataStore;
}); 
下载中有哪些内容?
这是在我们迄今为止创建的内容之上实现的演示。
它将包含 assets/js 文件夹中的所有 Core 类。
- 资源文件- css
- js- 核心- data- core.data.datastore.js
- core.data.memorydatastore.js
 
- core.controller.js
- core.js
- core.model.js
- core.router.js
 
- data
 
- 核心
 
- 控制器- home.js
- contacts.js
 
- models- contacts.model.js
 
- 视图- 主页- index.html
 
- contacts- addform.html
- contact.html
- editform.html
- index.html
 
 
- 主页
结论
我仍然希望有人坚持下去。这已经变成了一个很长的故事。但如果完成了,我们将拥有一个简单的小框架,易于扩展和实现。我可能会以一个使用文档来结束这个系列。但这个系列是关于我是如何做到的。下一篇文章将重点关注在 WebSql 之上创建一个持久的数据存储。


