这几天对MVVM模式做了简单的理解. MVVM中的MV是MVC中的MV, 而VM是在View之前添加了一层前端的逻辑层ViewModel. 该Model中的对象针对前端显示提供数据和接口. VM中有一部分功能是实现ViewModel和前端控件的自动绑定,包括读取和写入,我根据这个理解写了个简单的脚本,并进行了一次重构,写在这儿,权当整理.
1: 展示对象
展示对象很简单,本例中是一个用户信息,包括用户编号,用户名称和用户中文名称.所以对象应该如下定义:
js脚本:
function user_item() {
this.no = "";
this.name = "";
this.cname = "";
this.class_name = "user_item";
}
注: 本处class_name属性用来设置业务对象的名称. ViewModel和前端之间的映射关系当前约定为: viewMode.class_name + "." + viewMode.property为前端对象的id,例如: user_item的no属性,对应前端控件为:
创建ViewModel代码:
var user = new user_item();
2: 根据展示对象,设置3个前端控件
no:
name:
cname:
3: 在前端页面中,实现两个功能,自动读取页面控件的值写入ViewModel对象,将ViewModel对象中的值赋值给页面控件.
3.1 js代码:为user_item添加两个函数read和write.
其中read将页面控件值写入对象,write将对象属性设置到页面控件上.
为类添加成员函数,在js中需要使用prototype(我当前只会这一种)
3.1.1首先添加遍历成员变量的函数.
该函数有两个参数:要遍历的对象(obj)和对对象属性进行的操作(回调函数:ondo),并根据成员变量找到对应的控件,并将对象(obj),成员变量名称(field)和控件(control)作为回调函数的参数调用回调函数(ondo)
(注意: 遍历时,跳过函数成员和class_name变量)
function on_obj_properties(obj, ondo) {
for (i in obj) {
if (i != obj.class_name && typeof(obj[i]) != "function") {
var name = "#" + obj.class_name + "\\." + i;
var control = $(name);
if (control != undefined) {
ondo(obj, control, i);
}
}
}
}
3.1.2 read函数
user_item.prototype.read = function() {
on_obj_properties(this, function(obj, control, field){
obj[field] = control.val();
});
return this;
}
3.1.3 write函数
user_item.prototype.write = function() {
on_obj_properties(this, function(obj, control, field){
control.val(obj[field]);
});
}
3.1.4 完整的js代码(为data.js):
function user_item() {
this.no = "";
this.name = "";
this.cname = "";
this.class_name = "user_item";
}
function on_obj_properties(obj, ondo) {
for (i in obj) {
if (i != obj.class_name && typeof(obj[i]) != "function") {
var name = "#" + obj.class_name + "\\." + i;
var control = $(name);
if (control != undefined) {
ondo(obj, control, i);
}
}
}
}
user_item.prototype.read = function() {
on_obj_properties(this, function(obj, control, field){
obj[field] = control.val();
});
return this;
}
user_item.prototype.write = function() {
on_obj_properties(this, function(obj, control, field){
control.val(obj[field]);
});
}
3.2 前端测试
3.2.1要进行测试,需要在前端添加读取和写入的功能.添加read和write两个按钮:
3.2.2为按钮添加代码
点击页面中的write按钮会发现数据已经写入到控件,点击read按钮,会发现控件中的值已经写入ViewModel.
3.2.3 完整的前端代码
js test
no:
name:
cname:
4: 重构
每个ViewModel都会需要read和write,所以需要为ViewModel提供基类,ViewModel集成该类,read,write自动就有了,就不需要每个ViewModel都写了.
所以添加一个ViewModel的javascript基类:item_class, 代码如下
function item_class() {
}
item_class.on_obj_properties = function(obj, ondo) {
for (i in obj) {
if (i != obj.class_name && typeof(obj[i]) != "function") {
var name = "#" + obj.class_name + "\\." + i;
var control = $(name);
if (control != undefined) {
ondo(obj, control, i);
}
}
}
}
item_class.prototype.read = function() {
item_class.on_obj_properties(this, function(obj, control, field){
obj[field] = control.val();
});
return this;
}
item_class.prototype.write = function() {
item_class.on_obj_properties(this, function(obj, control, field){
control.val(obj[field]);
});
}
这样,user_item只需要集成该类即可, 代码如下:
user_item.prototype = new item_class();
5: 本例代码
5.1完成的js代码(data.js)
function item_class() {
}
item_class.on_obj_properties = function(obj, ondo) {
for (i in obj) {
if (i != obj.class_name && typeof(obj[i]) != "function") {
var name = "#" + obj.class_name + "\\." + i;
var control = $(name);
if (control != undefined) {
ondo(obj, control, i);
}
}
}
}
item_class.prototype.read = function() {
item_class.on_obj_properties(this, function(obj, control, field){
obj[field] = control.val();
});
return this;
}
item_class.prototype.write = function() {
item_class.on_obj_properties(this, function(obj, control, field){
control.val(obj[field]);
});
}
function user_item() {
this.no = "";
this.name = "";
this.cname = "";
this.class_name = "user_item";
}
user_item.prototype = new item_class();
前端代码
js test
no:
name:
cname:
6: 用Vue实现
VUE应该是天生支持 ViewModel的,使用VUE会使上面的代码更简单.
6.1 定义用户信息组件
6.1.1 定义组件模板
no:
name:
cname:
在该组件中使用user_item作为参数.
需要使用v-model绑定参数的属性,否则(如果使用v-bind),属性不会与控件值自动绑定,特别是读取时.
6.1.2注册组件
Vue.component('user-item', {
props: ['user_item'],
template: '#user-item'
});
6.1.3 HTML中使用组件
12
6.1.4 定义VUE
var save = new Vue({
el: '#save_div',
data: {
user_item: {
no : "a",
name : "b",
cname : "c",
}
}
});
定义事件:
function write_value() {
save.user_item.no = "id1";
save.user_item.name = "name2";
save.user_item.cname = "cname3";
}
function read_value() {
var text = "";
for (i in save.user_item) {
text += i + ":" + save.user_item[i] + ";";
}
alert(text);
}
6.1.5 完整代码
HTML:
js test
12
no:
name:
cname:
data.js:
Vue.component('user-item', {
props: ['user_item'],
template: '#user-item'
});
var save = new Vue({
el: '#save_div',
data: {
user_item: {
no : "a",
name : "b",
cname : "c",
}
}
});
function write_value() {
save.user_item.no = "id1";
save.user_item.name = "name2";
save.user_item.cname = "cname3";
}
function read_value() {
var text = "";
for (i in save.user_item) {
text += i + ":" + save.user_item[i] + ";";
}
alert(text);
}
7.vue 在组件中引用子组件
在当前的代码基础上,继续写着玩.
7.1 思考
在本案例中,no,name,cname是文本输入项.它们属于同一类, 可以概括为:
前端显示标签或名称,中间是输入控件,后面可能会有提示信息等.而且可能会发生变化,例如将
no:
修改为:
*no: 不可为空,只能是数字或字母
而且可能每个输入项都需要这样修改,应该怎么办呢?将其抽象为组件.
7.2 输入项组件
我们将输入项组件抽象为是否允许为空的注,输入项的标签, 输入项(例如input控件等:此处变化比较大,使用者应该可以根据需要自己决定怎么输入)
7.2.1 输入项组件的模板
{{ismust}} {{label}}:input control,ex:input,select,radio, checkbox
注意: 模板中使用了slot,类似于freemarker中的nestat
7.2.2 组件注册
Vue.component('row-item', {
props: ['ismust', 'label', 'user_item'],
template: '#row-item'
});
7.3.3 修改user-item组件模板
7.4完整代码
7.4.1 HTML
js test
12
{{ismust}} {{label}}:input control,ex:input,select,radio, checkbox
7..4.2 data.js
Vue.component('user-item', {
props: ['user_item'],
template: '#user-item'
});
Vue.component('row-item', {
props: ['ismust', 'label', 'user_item'],
template: '#row-item'
});
var save = new Vue({
el: '#save_div',
data: {
user_item: {
no : "a",
name : "b",
cname : "c",
}
}
});
function write_value() {
save.user_item.no = "id1";
save.user_item.name = "name2";
save.user_item.cname = "cname3";
}
function read_value() {
var text = "";
for (i in save.user_item) {
text += i + ":" + save.user_item[i] + ";";
}
alert(text);
}