综合案例

1. 待办处理

核心思路: 定义了3个字段.

  • todo 是当前已有的数据, 会通过 b-template 进行渲染.
  • todoText 搜索框的默认文本, 如果空则显示placeholder, 通过 b-model 进行关联.
  • nextTodoId 这个是数据的自增字段, 不是必须, 可以使用唯一id来处理.

定义了一个方法, 通过 b-click 进行绑定. 点击的时候, 检验字段是否为空, 不为空则增加一条待办.

模板里面还通过b-click 绑定了一个removeTodo 删除的方法, 通过索引删除, 这里用到一个动态索引, 这个是内置的, 前面在事件绑定章节已经讲过. 有增删改的数据, i 拿到的是固定的值.

  1. var bs = bui.store({
  2. scope: "page",
  3. data: {
  4. todo: [{
  5. id: 1,
  6. title: "参加项目会议"
  7. },{
  8. id: 2,
  9. title: "项目启动会"
  10. },{
  11. id: 3,
  12. title: "看电影"
  13. }],
  14. todoText: "",
  15. nextTodoId: 4
  16. },
  17. methods: {
  18. addTodo: function (e) {
  19. if( this.todoText ){
  20. this.todo.push({
  21. id: this.nextTodoId++,
  22. title:this.todoText
  23. })
  24. this.todoText = "";
  25. }else{
  26. bui.hint("请填写待办事项")
  27. }
  28. },
  29. removeTodo: function (index) {
  30. // 方法1: 通过索引删除
  31. this.todo.splice(index,1);
  32. this.todoText = "";
  33. }
  34. },
  35. templates: {
  36. tplTodo: function (data) {
  37. var _self = this;
  38. var html = "";
  39. data.forEach(function (item,i) {
  40. html += `<li id="${item.id}" class="bui-btn bui-box">
  41. <div class="span1">${item.title}</div>
  42. <i b-click="page.removeTodo($parentIndex)" class="icon-remove large"></i>
  43. </li>`;
  44. })
  45. return html;
  46. }
  47. }
  48. })

核心html

  1. <!-- 搜索条控件结构 -->
  2. <div class="bui-searchbar bui-box">
  3. <div class="span1">
  4. <div class="bui-input">
  5. <i class="icon-search"></i>
  6. <input type="text" value="" placeholder="请输入待办" b-model="page.todoText" />
  7. <div class="bui-btn" b-click="page.addTodo">添加</div>
  8. </div>
  9. </div>
  10. </div>
  11. <!-- 数组todo lendth 改变的时候,不会自动触发,需要监听 page.todo 数组改变 -->
  12. <div class="section-title">待办事项: <b b-text="page.todo.length"></b></div>
  13. <ul b-template="page.tplTodo(page.todo)" class="bui-list"></ul>

预览

查看效果

2. 弹窗选择交互

如果数据一开始有值,还需要把值跟模板里的数据进行比对,处理成选中状态.

js

  1. var bs = bui.store({
  2. scope: "page", // 用于区分公共数据及当前数据的唯一值
  3. data: {
  4. items: [{
  5. id: "guangzhou",
  6. name: "广州",
  7. }, {
  8. id: "shenzhen",
  9. name: "深圳",
  10. }, {
  11. id: "dongguan",
  12. name: "东莞",
  13. }],
  14. checked: ["shenzhen"], //缓存选中的值, 默认选中深圳
  15. checkedObj: [{
  16. id: "shenzhen",
  17. name: "深圳",
  18. }],
  19. },
  20. methods: {
  21. open: function() {
  22. this.uiDialog.open();
  23. }
  24. },
  25. watch: {
  26. checked: function(val) {
  27. var _self = this;
  28. // 获取的使用 this.$data.xxx
  29. var items = bui.array.getAll(_self.$data.items, val, "id");
  30. // 替换新的值 this.xxx
  31. bui.array.replace(this.checkedObj, items);
  32. }
  33. },
  34. computed: {},
  35. templates: {
  36. tplItem: function(data) {
  37. var html = "";
  38. data.forEach(function(item, i) {
  39. html += `<li class="bui-btn" id="${item.id}">${item.name}</li>`
  40. })
  41. return html;
  42. },
  43. tplCity: function(data) {
  44. var html = "";
  45. var _self = this;
  46. data.forEach(function(item, i) {
  47. // 渲染已经选择的城市
  48. var hasChoose = _self.checkedObj && bui.array.compare(_self.checkedObj, item.id, "id");
  49. var hasChecked = hasChoose ? "checked" : "";
  50. html += `<li class="bui-btn bui-box bui-btn-line">
  51. <div class="span1">
  52. <label for="interest+${i}">${item.name}</label>
  53. </div>
  54. <input id="interest+${i}" type="checkbox" class="bui-choose" name="interest" value="${item.id}" text="" ${hasChecked} b-model="page.checked">
  55. </li>`
  56. })
  57. return html;
  58. }
  59. },
  60. mounted: function() {
  61. // 加载后执行
  62. this.uiDialog = bui.dialog({
  63. id: "#uiDialog"
  64. });
  65. }
  66. })

注意: checkedObj: null 数组如果需要通过this.checkedObj = []赋值操作, 先设置为空; 如果初始值是数组, 则需要通过 bui.array.xxx 去操作才会触发界面响应.

核心html

  1. <div class="bui-page page-store">
  2. <main>
  3. <div class="bui-btn" b-click="page.open()">点击选择喜欢的城市</div>
  4. <div class="subtitle">您已选择:</div>
  5. <!-- 列表控件 html 对应的结构: -->
  6. <ul class="bui-list" b-template="page.tplItem(page.checkedObj)"></ul>
  7. </main>
  8. <!-- 对话框需要在 bui-page 里面, 这样默认才会解析 b- 行为属性的值 page.xxx -->
  9. <div id="uiDialog" class="bui-dialog">
  10. <div class="bui-dialog-head">选择喜欢的城市</div>
  11. <div class="bui-dialog-main">
  12. <ul class="bui-list" b-template="page.tplCity(page.items)"></ul>
  13. </div>
  14. <div class="bui-dialog-close"><i class="icon-close"></i></div>
  15. </div>
  16. </div>

预览

查看效果

3. 多选联动复杂场景

2.1 简单思路版

在data设计了4个字段, 分别是:

  • selectA A的数据源
  • selectB B的数据源
  • selectAChecked A的选中暂存区
  • selectBChecked B的选中暂存区

定义了4个方法:

  • modifyStatusA 点击以后修改A的激活状态, 并把数据存到A暂存区
  • modifyStatusB 点击以后修改B的激活状态, 并把数据存到B暂存区
  • addToB 合并A的选中数据到B的数据源里面, 数据改变会自动渲染到视图
  • addToA 合并B的选中数据到A的数据源里面, 数据改变会自动渲染到视图

操作数据便会更新视图. 代码有点多, 但是理清了思路,我们后面再看优化版.

  1. var bs = bui.store({
  2. scope: "page",
  3. data: {
  4. selectAChecked: [], // A区选中暂存区
  5. selectBChecked: [], // B区选中暂存区
  6. selectA: [ // 联动select的数据源
  7. { text: 'One', value: 'A', selected: false },
  8. { text: 'Two', value: 'B', selected: false },
  9. { text: 'Three', value: 'C', selected: false }
  10. ],
  11. selectB: [],
  12. },
  13. methods: {
  14. modifyStatusA: function (index) {
  15. var selectedItem = this.selectA[index],
  16. selecteds = this.selectAChecked,
  17. // 判断是否唯一
  18. indexs = bui.array.index(this.selectA[index].value,selecteds,"value");
  19. // 选中暂存区的增加或减少
  20. if( indexs > -1 ){
  21. selecteds.splice(indexs,1);
  22. }else{
  23. selecteds.push(selectedItem);
  24. }
  25. // 修改选中状态
  26. this.selectA[index].selected = !this.selectA[index].selected;
  27. // 替换整条数据并触发数据变更
  28. bui.array.set(this.selectA,index,this.selectA[index]);
  29. },
  30. modifyStatusB: function (index) {
  31. var selectedItem = this.selectB[index],
  32. selecteds = this.selectBChecked,
  33. indexs = bui.array.index(this.selectB[index].value,selecteds,"value");
  34. // 选中暂存区的增加或减少
  35. if( indexs > -1 ){
  36. selecteds.splice(indexs,1);
  37. }else{
  38. selecteds.push(selectedItem);
  39. }
  40. // 更新字段
  41. this.selectB[index].selected = !this.selectB[index].selected;
  42. // 替换整条数据并触发数据变更
  43. bui.array.set(this.selectB,index,this.selectB[index]);
  44. },
  45. addToB: function (e) {
  46. // 删除选中状态
  47. this.selectAChecked.forEach(function(item,i){
  48. item.selected = false;
  49. })
  50. // 合并并触发 this.selectB
  51. bui.array.merge(this.selectB,this.selectAChecked);
  52. // 删除this.selectA选中数据,通过value字段比对,支持多个
  53. bui.array.remove(this.selectA,this.selectAChecked,"value")
  54. // 清空A暂存区数据
  55. bui.array.empty(this.selectAChecked);
  56. },
  57. addToA: function (e) {
  58. // 删除选中状态
  59. this.selectBChecked.forEach(function(item,i){
  60. item.selected = false;
  61. })
  62. // 合并并触发 this.selectA
  63. bui.array.merge(this.selectA,this.selectBChecked);
  64. // 删除选中数据,通过value字段比对
  65. bui.array.remove(this.selectB,this.selectBChecked,"value")
  66. // 清空B暂存区数据
  67. bui.array.empty(this.selectBChecked);
  68. },
  69. },
  70. templates: {
  71. // 联动的示例,增加了事件绑定
  72. tplSelectA: function (data,te) {
  73. var html ='';
  74. data.forEach(function (item,i) {
  75. var active = item.selected ? "active" : "";
  76. html +=`<li b-click='page.modifyStatusA($index)' class="bui-btn ${active}">${item.text}</li>`;
  77. })
  78. return html;
  79. },
  80. tplSelectB: function (data) {
  81. var html ='';
  82. data.forEach(function (item,i) {
  83. var active = item.selected ? "active" : "";
  84. html +=`<li b-click='page.modifyStatusB($index)' class="bui-btn ${active}">${item.text}</li>`
  85. })
  86. return html;
  87. }
  88. }
  89. })

核心的html绑定

  1. <style type="text/css">
  2. .bui-select .active {
  3. color: red;
  4. }
  5. </style>
  6. <div class="bui-box">
  7. <div class ="span1">
  8. <h2 class="bui-box"><b b-text="page.selectAChecked.length"></b>/<b b-text="page.selectA.length"></b></h2>
  9. <div class="bui-select" b-template="page.tplSelectA(page.selectA)">
  10. </div>
  11. </div>
  12. <div style="width: 100px">
  13. <div class="bui-btn" b-click="page.addToB">添加到B</div>
  14. <div class="bui-btn" b-click="page.addToA">添加到A</div>
  15. </div>
  16. <div class="span1">
  17. <h2 class="bui-box"><div class="span1">列表2</div><b b-text="page.selectBChecked.length"></b>/<b b-text="page.selectB.length"></b></h2>
  18. <div class="bui-select" b-template="page.tplSelectB(page.selectB)">
  19. </div>
  20. </div>
  21. </div>

预览

查看效果

2.2 代码优化版

优化了操作流程, 在原来的data增加多了2个状态: 这2个状态都是一个对象, 因为实际上想要的是 disabled这个样式名, 如果你的样式上, 是设计的 canAdd, canDel 作为样式名, 只需要布尔值就行.

  • canAdd 是否能够增加
  • canDel 是否能够删除

原先的4个方法, 优化成了2个, 一个点击的时候修改状态, 一个是合并数据.

原先的2个模板方法, 优化成了1个, 通过传进来的不同字段进行处理就行. 这里要理解,b-click='page.setStatus(${target},$index,${targetChecked})' ${target}$index 的区别.

注意: b-template 传过去的第一个值会被解析成数据, 其它参数传什么就是什么.

  1. /**
  2. * 设计思路说明:
  3. * 左边列表A: selectA
  4. * 右边列表B: selectB
  5. * 左边选中列表暂存区A: selectAChecked
  6. * 右边选中列表暂存区B: selectBChecked
  7. * 点击列表, 往暂存区存放对应的数据, 并且通过watch 暂存区的数据变更,把按钮的状态变成能够操作.
  8. * 点击按钮添加到B, 则把右边列表数据,合并A选中的暂存区, 并删除选中状态, 清空A暂存区数据
  9. * 点击按钮添加到A, 则把左边列表数据,合并B选中的暂存区, 并删除选中状态, 清空B暂存区数据
  10. */
  11. var bs = bui.store({
  12. scope: "page",
  13. data: {
  14. canAdd: {
  15. disabled: true
  16. },
  17. canDel: {
  18. disabled: true
  19. },
  20. selectAChecked: [], // A区选中暂存区
  21. selectBChecked: [], // B区选中暂存区
  22. selectA: [
  23. { text: 'One', value: 'A', selected: false },
  24. { text: 'Two', value: 'B', selected: false },
  25. { text: 'Three', value: 'C', selected: false }
  26. ],
  27. selectB: [],
  28. },
  29. methods: {
  30. setStatus: function (target,index,checked) {
  31. var selectedItem = this[target][index],
  32. selecteds = this[checked],
  33. // 判断是否唯一
  34. indexs = bui.array.index(this[target][index].value,selecteds,"value");
  35. // 选中暂存区的增加或减少
  36. if( indexs > -1 ){
  37. selecteds.splice(indexs,1);
  38. }else{
  39. selecteds.push(selectedItem);
  40. }
  41. // 修改选中状态
  42. this[target][index].selected = !this[target][index].selected;
  43. // 替换第几条数据并触发数据this[target] 的dom变更
  44. bui.array.set(this[target],index,this[target][index]);
  45. },
  46. moveSelect: function (target,checked,targetB) {
  47. // 修改按钮状态
  48. var btn = checked == "selectBChecked" ? "canDel" : "canAdd";
  49. if( this[btn].disabled ){ return; }
  50. // 移动过去以后,不需要选中状态
  51. this[checked].forEach(function (item) {
  52. item.selected = false;
  53. })
  54. // 合并并触发 this.selectB
  55. bui.array.merge(this[target],this[checked]);
  56. // 删除this.selectA选中数据,通过value字段比对,支持多个
  57. bui.array.delete(this[targetB],this[checked],"value");
  58. // 清空暂存区数据
  59. bui.array.empty(this[checked]);
  60. },
  61. },
  62. watch: {
  63. selectAChecked: function (data) {
  64. // 修改添加按钮状态
  65. this.canAdd.disabled = !data.length;
  66. },
  67. selectBChecked: function (data) {
  68. // 修改删除按钮状态
  69. this.canDel.disabled = !data.length;
  70. }
  71. },
  72. templates: {
  73. // 联动的示例,增加了事件绑定
  74. tplSelect: function (data,target,targetChecked) {
  75. var html ='';
  76. data.forEach(function (item,i) {
  77. var active = item.selected ? "active" : "";
  78. // $index 为内置的动态索引, i 不一定等于 $index
  79. html +=`<li b-click='page.setStatus(${target},$index,${targetChecked})' class="bui-btn ${active}">${item.text}</li>`;
  80. })
  81. return html;
  82. }
  83. }
  84. })
  1. <style type="text/css">
  2. .bui-select .active {
  3. color: red;
  4. }
  5. .btn-controls {
  6. width: 1rem;
  7. margin:0 .1rem;
  8. }
  9. .btn-controls .bui-btn{
  10. margin-top:.1rem;
  11. }
  12. </style>
  13. <div class="bui-box">
  14. <div class ="span1">
  15. <div class="subtitle bui-box">
  16. <div class="span1">列表1</div>
  17. <b b-text="page.selectAChecked.length"></b>/<b b-text="page.selectA.length"></b>
  18. </div>
  19. <div b-template="page.tplSelectA(page.selectA)" class="bui-list">
  20. </div>
  21. </div>
  22. <div class="btn-controls">
  23. <!-- 传多个数据源字段 -->
  24. <div b-click="page.moveSelect(selectB,selectAChecked,selectA)" b-class="page.canAdd" class="bui-btn round">&gt;&gt;</div>
  25. <div b-click="page.moveSelect(selectA,selectBChecked,selectB)" b-class="page.canDel" class="bui-btn round">&lt;&lt;</div>
  26. </div>
  27. <div class="span1">
  28. <div class="subtitle bui-box">
  29. <div class="span1">列表2</div>
  30. <b b-text="page.selectBChecked.length"></b>/<b b-text="page.selectB.length"></b>
  31. </div>
  32. <div b-template="page.tplSelectB(page.selectB)" class="bui-list">
  33. </div>
  34. </div>
  35. </div>

预览

查看效果

4. 动态表单

  1. <div class="bui-page page-store page-input">
  2. <header>
  3. <div lass="bui-bar">
  4. <div class="bui-bar-left">
  5. <a class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></a>
  6. </div>
  7. <div class="bui-bar-main">动态表单</div>
  8. <div class="bui-bar-right">
  9. </div>
  10. </div>
  11. </header>
  12. <main>
  13. <div class="bui-btn" b-click="page.add"><i class="icon-plus"></i>添加</div>
  14. <div b-template="page.tplUser(page.users)">
  15. </div>
  16. <div class="container-xy">
  17. <button class="bui-btn primary round" b-click="page.submitForm">提交</button>
  18. </div>
  19. </main>
  20. </div>

点击添加会新增一个动态表单, 注意 $index 的指向是指向 b-target="ul", 获得的索引,才是跟数据一一对应的.

  1. var bs = bui.store({
  2. scope: "page", // 用于区分公共数据及当前数据的唯一值
  3. data: {
  4. users: [{
  5. name: "",
  6. id: ""
  7. }],
  8. },
  9. methods: {
  10. submitForm: function(e) {
  11. console.log(this.users)
  12. return false;
  13. },
  14. add: function() {
  15. this.users.push({
  16. name: "",
  17. id: ""
  18. })
  19. },
  20. remove: function(index) {
  21. console.log(index)
  22. bui.array.deleteIndex(this.users, index);
  23. }
  24. },
  25. watch: {},
  26. computed: {},
  27. templates: {
  28. tplUser: function(data) {
  29. var html = `<ul class="bui-list">`;
  30. var that = this;
  31. data.forEach(function(item, index) {
  32. let length = that.users.length - 1;
  33. html += `<li class="bui-btn-title bui-box clearactive">表单${length}<div class="span1"></div><div class="bui-btn" b-click="page.remove($index)" b-target="ul"><i class="icon-remove"></i></div></li><li class="bui-btn bui-box clearactive">
  34. <label class="bui-label">姓名</label>
  35. <div class="span1">
  36. <div class="bui-value"><input type="text" name="fname" b-model="page.$index.name" b-target="ul"></div>
  37. </div>
  38. </li>
  39. <li class="bui-btn bui-box clearactive">
  40. <label class="bui-label">身份证</label>
  41. <div class="span1">
  42. <div class="bui-value"><input type="text" name="fname" b-model="page.$index.id" b-target="ul"></div>
  43. </div>
  44. </li>`
  45. })
  46. html += `</ul>`
  47. return html;
  48. }
  49. },
  50. mounted: function() {
  51. // 加载后执行
  52. }
  53. })

总结

数据驱动的改变在于思路的转变, 合理的使用,可以大大的减少手动操作dom的代码. 数据驱动不比dom操作, 有可能需要去理解核心的业务以后才能上手, 所以在代码的设计数据上, 写上自己的设计思路, 这对于后面的维护会很有帮助.

bui.store 并不是必须用到的, 使用单页及模块化已经可以很好的处理各种问题. 只是在一些表单联动上, 你会发现这个真的很有用!