65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5离线MVC:第2部分

2013年4月6日

CPOL

4分钟阅读

viewsIcon

20760

downloadIcon

356

离线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
  • 控制器
    • home.js
    • contacts.js
  • models
    • contacts.model.js
  • 视图
    • 主页
      • index.html
    • contacts
      • addform.html
      • contact.html
      • editform.html
      • index.html

结论

我仍然希望有人坚持下去。这已经变成了一个很长的故事。但如果完成了,我们将拥有一个简单的小框架,易于扩展和实现。我可能会以一个使用文档来结束这个系列。但这个系列是关于我是如何做到的。下一篇文章将重点关注在 WebSql 之上创建一个持久的数据存储。

© . All rights reserved.