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 之上创建一个持久的数据存储。