JS案例:用购物车理解前端MVC架构
目录
什么是MVC:
Model View Controller即:模型-视图-控制器
通俗来讲,在编程语言中,Model就是数据,可以理解为数据库,View就是显示数据的外观,Controller是用来连接前两者的行为,常见的Vue采用的是M-V-VM架构,与MVC类似,但是基于MVC
MVC的作用:
说到作用,就不得不提面向对象与面向过程的区别了
面向过程就是,将解决问题的思路流程一步一步进行,紧扣在一起,最终达到结果
面向对象,是将某个问题的解决方式剥离开,其目的不是为了完成某个步骤,而是将某个事物(对象)的角色(属性)和行为(方法)作为核心
说了这些,到底MVC有什么好处呢?
举个栗子:A是某公司的一位前端程序员,平时用面向过程进行编程,这天,好不容易完成了手头上的活,准备回家,这时,产品经理走过来,让他改个小地方,这下就完了,面向过程的思维使他的代码环环相扣,代码耦合性强,内聚性高,密不可分,改一个地方就要几乎全改
A的哥哥也是一个前端程序员,平时用面向对象编程,产品经理让他改一个效果,由于用的面向对象,他的代码没有层次感,通用的方法全部提取出来,使得代码耦合性低,想改哪直接改相关的类或者方法就好了
当然,在小型项目中无法体现它的优点,甚至会小题大做,大材小用,而在大型项目中,其耦合性低,代码复用性高,搭建相对较快
如何使用MVC架构:
又是这个购物车,业余时间用MVC做了一个简单的购物车:
目录结构大致是这样
购物车整体流程:
目录结构将model view controller剥离开
Modedl层:存储数据,显示数据
View:根据Model数据渲染页面
Controller:传递数据
Command:操作数据,获取数据
Event:事件总线,注册事件
商品列表:
初始化View层,建立Ajax获取数据,之后由controller触发事件至事件总线,然后再由注册的事件将ajax数据传至Model中完成商品列表初始化
当model获取到商品列表数据时,通过代理set() 触发新建商品列表事件,通过command操作view达到新建列表目的
购物车表格:
当用户对view进行操作时,触发注册的事件,通过command修改Model中的数据(购物车列表)从而再由command驱动view中的刷新表格进行渲染
效果:
以下是所有代码:
后端(nodejs):
server.js
/*
*后端采用node+express搭建一个简单的接口,通过本地数据,将商品列表传至前端
*
*/
const express = require('express');
const path = require('path');
const app = express();
const shopData = require('./data/shopData.js')
let serverToken = 'hello'
app.all("*", function (req, res, next) { //跨域
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "content-type");
res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
next();
});
app.use('/getShopList', function (req, res) {
let data = req.query
if (!checkToken(data.token)) { //简单获取前端token,校验
res.send({
result: 0,
msg: 'token fail'
})
return
}
res.send({
result: 1,
msg: 'success',
type: 'getShopList',
shopData
})
})
function checkToken(teken) {
return teken == serverToken
}
app.use('/img', express.static(path.join(__dirname, './img'))); //后端目录静态化,用url+img访问文件夹
app.use('/client', express.static(path.join(__dirname, '../client')));
app.listen(1024, "127.0.0.1", function () {
console.log("服务开启,开始侦听");
});
data.js(存放商品列表)
module.exports = [{
"select": false,
"id": 1001,
"icon": "img/1.png",
"name": "餐饮0",
"num": 0,
"price": 10,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1002,
"icon": "img/2.png",
"name": "餐饮1",
"num": 0,
"price": 20,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1003,
"icon": "img/3.png",
"name": "餐饮2",
"num": 0,
"price": 30,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1004,
"icon": "img/4.png",
"name": "餐饮3",
"num": 0,
"price": 40,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1005,
"icon": "img/5.png",
"name": "餐饮4",
"num": 0,
"price": 50,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1006,
"icon": "img/6.png",
"name": "餐饮5",
"num": 0,
"price": 60,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1007,
"icon": "img/7.png",
"name": "餐饮6",
"num": 0,
"price": 70,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1008,
"icon": "img/8.png",
"name": "餐饮7",
"num": 0,
"price": 80,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1009,
"icon": "img/9.png",
"name": "餐饮8",
"num": 0,
"price": 90,
"sum": 0,
"delete": false
},
{
"select": false,
"id": 1010,
"icon": "img/10.png",
"name": "餐饮9",
"num": 0,
"price": 100,
"sum": 0,
"delete": false
}
]
前端
shopCar.html(入口页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>shopCar</title>
<link rel="stylesheet" href="./src/style/shop.css">
</head>
<body>
<script type="module">
/*
购物车整体流程:
目录结构将model view controller剥离开
Modedl层:存储数据,显示数据
View:根据Model数据渲染页面
Controller:传递数据
Command:操作数据,获取数据
Event:事件总线,注册事件
商品列表:
初始化View层,建立Ajax获取数据,之后由controller触发事件至事件总线,然后再由注册的事件将ajax数据传至Model中完成商品列表初始化
当model获取到商品列表数据时,通过代理set() 触发新建商品列表事件,通过command操作view达到新建列表目的
购物车表格:
当用户对view进行操作时,触发注册的事件,通过command修改Model中的数据(购物车列表)从而再由command驱动view中的刷新表格进行渲染
*/
import ShopView from './src/js/view/ShopView.js'
// 实例化View层入口函数
new ShopView()
</script>
</body>
</html>
shop.css
* {
margin: 0;
padding: 0;
}
.shopBox {
overflow: hidden;
width: 1000px;
margin: 50px auto 0;
}
.liItem {
float: left;
list-style: none;
padding: 10px;
width: 150px;
height: 200px;
text-align: center;
border: 1px solid lightcoral;
}
.liItem img {
width: 100px;
height: 100px;
}
.leftBtn,
.rightBtn {
width: 30px;
height: 30px;
background: white;
border: 1px solid black;
font-size: 25px;
line-height: 30px;
}
.text {
width: 50px;
height: 26px;
display: inline-block;
vertical-align: bottom;
text-align: center;
}
table {
font-size: 30px;
width: 1200px;
border: 1px solid lightcoral;
border-collapse: collapse;
margin: 50px auto;
}
.checkbox {
width: 30px;
height: 30px;
}
td {
border: 1px solid lightcoral;
text-align: center;
vertical-align: middle;
}
td button {
width: 150px;
height: 60px;
}
.numBox {
width: 150px;
height: 30px;
margin: auto;
position: relative;
}
.numBox>button {
width: 40px;
height: 42px;
background-color: white;
border: 1px solid #000000;
}
.numBox>input {
width: 70px;
height: 40px;
border: 1px solid #000000;
border-left: none;
border-right: none;
text-align: center;
}
JS文件夹:
bussiness
- Ajax.js
import ShopEvent from '../event/ShopEvent.js' import Utils from '../utils/Utils.js' import Api from '../config/Api.js' import ShopController from '../controller/ShopController.js' export default class Ajax {//Ajax类,用于请求后端或本地数据 // Ajax请求函数 static AjaxTool(method = Api.GET, url, data) { let xhr; if (window.ActiveXObject) { //ie浏览器 xhr = new ActiveXObject("Microsoft.XMLHTTP"); } else if (window.XMLHttpRequest) { //其他浏览器 xhr = new XMLHttpRequest(); } url = Api.URL + Api.PORT + Api.PATH + url if (method !== Api.POST) { method = Api.GET url = Utils.urlJoin(url, data) data = null } else { method = Api.POST } xhr.open(method, url); xhr.send(data ? JSON.stringify(data) : '') xhr.addEventListener('load', Ajax.loadHandler) //Ajax类是静态类,无法使用this } static loadHandler(e) { //this指向xhr let xhr = e.currentTarget; if (xhr.readyState === 4 && xhr.status === 200) { Ajax.data = xhr.response } else { Ajax.data = 'error' } } static set data(value) { //使用set对象代理模式替代请求数据回调函数(只写set表示data只可写入,不可读取) let res = JSON.parse(value) switch (res.result) { case 1: console.log(res.msg) ShopController.dispatch(ShopEvent.GET_DATA, res)//获取到数据后不做其他操作,将数据通过事件抛出至Event总线中 break; case 0: console.log('加载失败') console.log(res.msg) break; default: break; } } }
command
- MainCommand(command汇总)
import GetDataCommand from '../command/GetDataCommand.js' import CreateListCommand from '../command/CreateListCommand.js' import CreateTableCommand from '../command/CreateTableCommand.js' import AddItemCommand from '../command/AddItemCommand.js' import DelItemCommand from '../command/DelItemCommand.js' import ReduceItemCommand from '../command/ReduceItemCommand.js' import ChangeItemCommand from '../command/ChangeItemCommand.js' import SelectItemCommand from '../command/SelectItemCommand.js' export default { GetDataCommand, CreateListCommand, CreateTableCommand, AddItemCommand, DelItemCommand, ReduceItemCommand, ChangeItemCommand, SelectItemCommand }
- GetDataCommand(获取商品列表)
import ShopModel from '../model/ShopModel.js' export default class GetDataCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e ShopModel.getInstance().shopList = data.shopData//将ajax获取的数据发送到Model } }
- CreateListCommand(创建商品列表)
import CreateList from '../view/CreateList.js' export default class CreateListCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() {//创建商品列表 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e for (let i = 0; i < data.length; i++) { let createList = new CreateList(document.body) createList.shopList = data[i] } } }
- CreateTableCommand(创建购物车表格)
import CreateTable from '../view/CreateTable.js' export default class ShopCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() {//刷新购物车表格 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e let createTable = new CreateTable(document.body) createTable.shoppingList = data } }
- AddItemCommand(增加商品)
import ShopModel from '../model/ShopModel.js' export default class AddItemCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { //新增商品 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e AddItemCommand.addItem(ShopModel.getInstance().shoppingList, data) } static addItem(list, data) { //遍历查询某项商品增加或减少 let arr = list.filter(function (item) { return item.id === data.id; }); //若有返回值则对某项商品操作(在1-99区间,若为0则直接删除) if (arr.length == 0) { data.num++; data.sum = data.num * data.price; list.push(data); } else if (arr[0].num < 99) { arr[0].num++; arr[0].sum = arr[0].num * arr[0].price; } ShopModel.getInstance().shoppingList = list } }
- ReduceItemCommand(减少商品)
import ShopModel from '../model/ShopModel.js' export default class ReduceItemCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { //减少商品 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e ReduceItemCommand.reduceItem(ShopModel.getInstance().shoppingList, data) } static reduceItem(list, data) { //遍历查询某项商品增加或减少 let arr = list.filter(function (item) { return item.id === data.id; }); //若有返回值则对某项商品操作(在1-99区间,若为0则直接删除) if (arr[0].num > 1) { arr[0].num--; arr[0].sum = arr[0].num * arr[0].price; } else { data.num = 0; //此处初始化model中的shopList,否则会假删除(删除栈中的数量) list = list.filter(function (item) { return item.id !== data.id; }); } ShopModel.getInstance().shoppingList = list } }
- ChangeItemCommand(修改商品数量)
import ShopModel from '../model/ShopModel.js' export default class ChangeItemCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { //修改商品数量 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e ChangeItemCommand.changeItem(ShopModel.getInstance().shoppingList, data) } static changeItem(list, data) { let arr = list.filter(function (item) { return item.id === data.id; }); arr[0].sum = arr[0].num * arr[0].price; ShopModel.getInstance().shoppingList = list } }
- DelItemCommand(删除商品)
import ShopModel from '../model/ShopModel.js' export default class DelItemCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { //删除商品 } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e DelItemCommand.delItem(ShopModel.getInstance().shoppingList, data) } static delItem(list, data) { //遍历查询某项商品增加或减少 data.num = 0; //此处初始化model中的shopList,否则会假删除(删除栈中的数量) data.select = false; //此处初始化model中的shopList,否则会假删除(删除栈中的数量) ShopModel.getInstance().shoppingList = list.filter(function (item) { //数组过滤函数,返回id属性不等于当前id的数组,即删除当前选中的对象,并重新赋值 return item.id !== data.id; }); } }
- SelectItemCommand(选中商品)
import ShopModel from '../model/ShopModel.js' export default class SelectItemCommand { //行为类,用于执行ctrl层通过事件的方式发来的指令 constructor() { } eventHandler(e) { //使用动态方法,而不是static静态方法,因为该方法将被使用多次,使用new AddShopCommand实例化后调用 let { data } = e SelectItemCommand.selItem(ShopModel.getInstance().shoppingList, data) } static selItem(list, data) { //遍历查询某项商品增加或减少 if (!data) { //全选框 list.checkedAll = !list.checkedAll list.map(function (item) { item.select = list.checkedAll; //其他选项框与全选框状态一致 }) } else { //单选框 list.checkedAll = 1 //计数器,用来查询是否为全选状态 list.map(function (item) { //单选,选中某一个(在表格初始化时执行checkAll判断是否全选) if (item.id === data.id) { item.select = !item.select } list.checkedAll *= item.select }) } ShopModel.getInstance().shoppingList = list } }
components
- Counter(计数器组件)
import ShopEvent from '../event/ShopEvent.js' import ShopController from '../controller/ShopController.js' import Utils from '../utils/Utils.js' export default class Counter { //计数器组件 constructor(_data, _parentEle) { this.data = _data this.parentEle = _parentEle this.ele = this.createCounter() } createCounter() { //创建数量计数器 let div = Utils.createEle('div', {}, { className: 'numBox' }) this.parentEle.appendChild(div); let leftBtn = this.createMark(div, 'reduce') //减少商品按钮 let input = Utils.createEle('input', {}, { type: 'text', value: this.data.num }) div.appendChild(input); let rightBtn = this.createMark(div, 'add') //新增商品按钮 leftBtn.addEventListener("click", this.reduceItemEvent); rightBtn.addEventListener("click", this.addItemEvent); input.addEventListener("input", Utils.throttle(this.changeItemEvent, 500)); // 节流 return div; } createMark(parentEle, type) { //判断增加或减少键 let markBtn = Utils.createEle('button', {}, { textContent: type == "add" ? '+' : '-' }) parentEle.appendChild(markBtn); return markBtn } addItemEvent = e => { //新增商品时,抛发事件至command控制model修改数据,刷新表格 ShopController.dispatch(ShopEvent.ADD_SHOPIING_ITEM, this.data) } reduceItemEvent = e => { //减少商品 ShopController.dispatch(ShopEvent.REDUCE_SHOPIING_ITEM, this.data) } changeItemEvent = e => { //修改商品 e.target.value = this.data.num = this.checkNumber(e.target.value) ShopController.dispatch(ShopEvent.CHANGE_SHOPIING_ITEM, this.data) } checkNumber(value) { //过滤数据 value = value.replace(/[^0-9]/g, ""); //只允许输入数字 if (value === "0") { // 如果=0,就设为1 value = "1"; } if (value.length > 2) { // 如果输入的内容大于2位,让这个值为99(最大99个) value = "99"; } if (value.length === 0) { // 如果什么都没有输入,也设为1 value = "1"; } return value } }
config
- Api
export default class Api {//接口配置类 static URL = "http://127.0.0.1"; static PORT = ":1024"; static PATH = '/' static GET = "get"; static POST = "post"; static IMGPATH = Api.URL + Api.PORT + Api.PATH; static ServerApi = { getShopList: 'getShopList' //获取商品列表 } }
controller
- ShopController(控制层,做事件传导,数据传输)
export default class ShopController extends EventTarget { //控制层,处理用户交互,路由,输入,将model view controller剥离开,通过controller中的事件监听抛发进行路由传输数据 constructor() { //继承事件对象,用于抛发自定义事件 super(); } static get instance() { //单例写法与java中getinstance相似,new会生成一个新对象,分配内存,而这么写可以把一个已存在的引用给你使用,节省效能,若只使用get + 属性名而不用set产生只读属性,只允许调用,无法修改 if (!ShopController._instance) { Object.defineProperty(ShopController, "_instance", { value: new ShopController() }) } return ShopController._instance; } static dispatch(type, data) { //抛发自定义事件,传递数据 let event = new Event(type) event.data = data ShopController.instance.dispatchEvent(event) } static runCommand(type, Command) { //观察者模式,当自定义事件触发时调用其他类中的方法,与dispatch对应,类似于addEventlistener,只不过将回调函数换成类中的动态方法 var command = new Command() ShopController.instance.addEventListener(type, command.eventHandler) } }
event
- ShopEvent
export default class ShopEvent { constructor() { } // 所有自定义事件名称 static GET_DATA = 'get_data' static GET_SHOP_LIST = 'get_shop_list' static GET_SHOPIING_LIST = 'get_shopping_list' static ADD_SHOPIING_ITEM = 'add_shopping_item' static DEL_SHOPIING_ITEM = 'del_shopping_item' static REDUCE_SHOPIING_ITEM = 'reduce_shopping_item' static CHANGE_SHOPIING_ITEM = 'change_shopping_item' static SELECT_SHOPIING_ITEM = 'select_shopping_item' }
- EventGroup(事件总线)
import ShopEvent from './ShopEvent.js' import ShopController from '../controller/ShopController.js' import MainCommand from '../command/MainCommand.js' let { GetDataCommand, CreateListCommand, CreateTableCommand, AddItemCommand, DelItemCommand, ReduceItemCommand, ChangeItemCommand, SelectItemCommand } = MainCommand export default class EventGroup { //事件总线,注册所有model层与其它层的业务逻辑,全程通过controller层中的事件机制进行通信 constructor() { /* 1.Ajax获取到数据后,触发GetDataCommand中的方法,用于传递数据至Model层中,然后通过Model层调用CreateListCommand创造商品列表 2.当用户对商品做任何操作时,都会修改Model从而触发CreateTableCommand,以下操作会触发CreateTableCommand 3.点击商品列表或点击商品加号按钮时触发AddItemCommand,通过AddItemCommand修改model中的数据,从而驱动CreateTableCommand 4.点击商品减号按钮时触发ReduceItemCommand,修改model中的数据,从而驱动CreateTableCommand 5.点击商品删除按钮时触发DelItemCommand,删除model中的数据,从而驱动CreateTableCommand 6.修改商品数量时触发ChangeItemCommand,更新model中的数据,从而驱动CreateTableCommand 7.选中商品时触发SelectItemCommand,更新model中的数据,从而驱动CreateTableCommand */ ShopController.runCommand(ShopEvent.GET_DATA, GetDataCommand)//获取商品列表数据 ShopController.runCommand(ShopEvent.GET_SHOP_LIST, CreateListCommand)//新建商品列表 ShopController.runCommand(ShopEvent.GET_SHOPIING_LIST, CreateTableCommand)//刷新购物车表格 ShopController.runCommand(ShopEvent.ADD_SHOPIING_ITEM, AddItemCommand)//商品新增或数量加一 ShopController.runCommand(ShopEvent.DEL_SHOPIING_ITEM, DelItemCommand)//商品删除 ShopController.runCommand(ShopEvent.REDUCE_SHOPIING_ITEM, ReduceItemCommand)//商品数量减一 ShopController.runCommand(ShopEvent.CHANGE_SHOPIING_ITEM, ChangeItemCommand)//修改商品数量 ShopController.runCommand(ShopEvent.SELECT_SHOPIING_ITEM, SelectItemCommand)//选择商品 } }
model
- ShopModel(模型层,用于数据存放及数据逻辑)
import ShopEvent from '../event/ShopEvent.js' import ShopController from '../controller/ShopController.js' export default class ShopModel { //模型层,用于数据存放及数据逻辑,通过事件处理机制(controller)传递数据,再由command进行对数据操作 constructor() { this._shopList = null this._shoppingList = [] } static getInstance() { //单例写法与java中getinstance相似,new会生成一个新对象,分配内存,而这么写可以把一个已存在的引用给你使用,节省效能,若只使用get + 属性名而不用set产生只读属性,只允许调用,无法修改 if (!ShopModel._instance) { Object.defineProperty(ShopModel, "_instance", { value: new ShopModel() }) } return ShopModel._instance; } set shopList(value) {//设置商品列表 this._shopList = value; ShopController.dispatch(ShopEvent.GET_SHOP_LIST, value) } get shopList() { return this._shopList } set shoppingList(value) {//数据修改时,驱动view进行表格刷新 this._shoppingList = value; ShopController.dispatch(ShopEvent.GET_SHOPIING_LIST, value) } get shoppingList() { return this._shoppingList } }
utils
- Utils(工具类)
export default class Utils { //工具类 //将对象拼接到url中 static urlJoin(url, obj) { var list = [] for (var key in obj) { if (obj.hasOwnProperty(key)) { list.push(`${key}=${obj[key]}`) } } return `${url}?${list.join('&')}` } static createEle(ele, style, attribute) { //新增标签,设置属性及样式 let element = document.createElement(ele) if (style) { for (let key in style) { element.style[key] = style[key]; } } if (attribute) { for (let key in attribute) { element[key] = attribute[key]; } } return element } // 函数节流 static throttle(fn, time) { let _timer = null return function () { if (_timer) { clearTimeout(_timer) _timer = null } _timer = setTimeout(fn.bind(this, ...arguments), time) } } }
view
- ShopView(视图层,用于元素渲染,显示数据)
import Api from '../config/Api.js' import AJAX from '../bussiness/Ajax.js' import EventGroup from '../event/EventGroup.js' export default class ShopView { //视图层,用于元素渲染,显示数据,依据(model)模型数据创建 constructor() { new EventGroup() //注册所有自定义事件,用于数据传输 AJAX.AjaxTool(Api.GET, Api.ServerApi.getShopList, { //请求服务端购物车列表 token: 'hello'//发送从后端获取的token用于验证,此处未做获取,直接用一个字符代替 }) } }
- CreateList(列表视图)
import Api from '../config/Api.js' import Utils from '../utils/Utils.js' import ShopEvent from '../event/ShopEvent.js' import ShopController from '../controller/ShopController.js' export default class CreateList { //视图层,用于元素渲染,显示数据,依据(model)模型数据创建 constructor(parentEle) { this.parentEle = parentEle this._shopList = null } set shopList(value) { //使用对象代理,每当数据发生更改时渲染商品列表 if (this._shopList) { this.createListEle(this._shopList, this.parentEle) return; } this._shopList = value this.createListEle(value, this.parentEle) } get shopList() { return this._shopList } createListEle(data, parentEle) { let li = Utils.createEle('li', {}, { 'className': 'liItem' }) let img = Utils.createEle('img', {}, { 'src': Api.IMGPATH + data.icon }) let title = Utils.createEle('div', {}, { 'textContent': data.name }) let price = Utils.createEle('span', {}, { 'textContent': data.price + "元" }) li.appendChild(img); li.appendChild(title); li.appendChild(price); li.addEventListener('click', this.addItemEvent) parentEle.appendChild(li); } addItemEvent = e => { //当用户点击添加商品时,将数据通过controller层发送至事件总线,再驱动model层并修改数据,后续由Model提供数据刷新表格 ShopController.dispatch(ShopEvent.ADD_SHOPIING_ITEM, this.shopList) } }
- CreateTable(购物车表格视图)
import Api from '../config/Api.js' import Utils from '../utils/Utils.js' import Counter from '../components/Counter.js' import ShopEvent from '../event/ShopEvent.js' import ShopController from '../controller/ShopController.js' export default class CreateTable { //视图层,用于元素渲染,显示数据,依据(model)模型数据创建 constructor(parentEle) { this.parentEle = parentEle this._shoppingList = null this._table = null } set shoppingList(value) { //对象代理,若model数据改变时,刷新表格 let table = document.getElementById('table') if (table) { //初始化表格,若有则删除 table.remove() table = null; } this._shoppingList = value || [] this.createTab(value, this.parentEle) } get shoppingList() { return this._shoppingList } createTab(data, parentEle) { this._table = Utils.createEle('table', {}, { id: "table" }) let thr = Utils.createEle('tr') for (let prop in data[0]) { //创建表头,如果属性名是select,就创建全选按钮 let th = Utils.createEle('th') if (prop === "select") { let input = Utils.createEle('input', {}, { type: "checkbox", className: "checkbox", checked: this.checkedAll() //查询是否全选 }) input.addEventListener("change", this.seleteEvent); th.appendChild(input); } else { th.textContent = prop; } thr.appendChild(th) } this._table.appendChild(thr) for (let i = 0; i < data.length; i++) { let tr = Utils.createEle('tr') this._table.appendChild(tr); for (let str in this.shoppingList[i]) { let td = document.createElement("td"); this.selectTdType(td, this.shoppingList[i], str); tr.appendChild(td); } } parentEle.appendChild(this._table) } selectTdType(td, data, type) { switch (type) { case 'select': let input = Utils.createEle('input', {}, { type: 'checkbox', className: 'checkbox', checked: data['select'], data }) input.addEventListener("change", this.seleteEvent); td.appendChild(input); break; case 'icon': let img = Utils.createEle('img', {}, { 'src': Api.IMGPATH + data.icon }) td.appendChild(img); break; case 'num': new Counter(data, td) //实例化计数器组件 break; case 'delete': let btn = Utils.createEle('button', {}, { textContent: '删除', data }) td.appendChild(btn); btn.addEventListener("click", this.deleteItemEvent); break; default: td.textContent = data[type]; break; } } deleteItemEvent = e => { //触发删除事件,通过command删除后通知model驱动view进行刷新 ShopController.dispatch(ShopEvent.DEL_SHOPIING_ITEM, e.target.data) } seleteEvent = e => { //触发选择商品事件,通过command删除后通知model驱动view进行刷新 ShopController.dispatch(ShopEvent.SELECT_SHOPIING_ITEM, e.target.data) } checkedAll() { let count = 1 this.shoppingList.map((item) => { count *= item.select }) return count } }