找回密码
 FreeOZ用户注册
楼主: woodheadz
打印 上一主题 下一主题

[学习深造] 发起了一个Javascript开源MVVM框架

[复制链接]
91#
 楼主| 发表于 6-10-2013 01:48:00 | 只看该作者
今天有得空加了点工。
itemtemplate现在支持引用,所以可以实现递归模版,也就是说支持和树状结构的数据进行绑定了。
除了引用外,itemtemplate还支持回掉函数,也就是彻底由用户自行创建,这个在需求很变态(比如要求一个列表中的项目根据数据类型不同采用不同模版之类的)的时候特别管用。

增加了新的扩展“action”,基本上就是从knot.js提供UI事件支持,和普通UI事件的区别是knotjs在事件发生时会往事件handler里传递当前的data context。本来不打算加这个支持害怕不必要的增加系统复杂度,后来想了想,有这个东西的确还是能方便很多。

knot.js已经接近完成了,接下来还打算增加异步validator和独立的cbs文件的支持,之后,我认为就可以做第一次发布了。  新增这些东西后Knot.js压缩后尺寸依然极小,不过14k.

做了个新的包含树状结构,master-detail结构的demo在这里, 按ctl+alt+k可以呼出调试窗口,实时监控绑定状态。:
http://knotjs.atwebpages.com/example/treeExample.html

这个demo里面的treeview完全是由Knot.js绑定到一组div上创建而成的,包括tree的展开收缩状态,全部可以由绑定来实现。关键是这个实现同时还非常简单直观。

html:
  1. <body onload="onLoad();">
  2. <h2>Knot.js Example</h2>
  3. <div>
  4.     <div id="treeArea">
  5.         <div>Group</div>
  6.         <input id="textCategoryTitle" class="disableWhenNoCategorySelected"/>
  7.         <div id="treeView">
  8.             <div id="treeItem">
  9.                 <div><button>+</button><span></span></div>
  10.                 <div></div>
  11.             </div>
  12.         </div>
  13.         <div>
  14.             <button onclick="addCategory()" class="disableWhenNoCategorySelected">+</button>
  15.             <button onclick="removeCategory()" class="disableWhenNoCategorySelected">-</button></div>
  16.     </div>
  17.     <div id="peopleListArea">
  18.         <div>People</div>
  19.         <div id="peopleList">
  20.             <div>
  21.                 <span id="spanTitle"> </span><span id="spanName"></span>
  22.                 <hr/>
  23.                 Age:<span id="spanAge"></span>,
  24.                 From:<span id="spanFrom"></span>
  25.             </div>
  26.         </div>
  27.     </div>
  28.     <div id="editArea">
  29.         <div>
  30.             <div class="fieldTitle">Name:</div>
  31.             <input class="disableWhenNoSelection" type="text"/>
  32.             <span class="errorMessage"></span>
  33.         </div>
  34.         <div>
  35.             <div class="fieldTitle">Age:</div>
  36.             <input class="disableWhenNoSelection" type="text"/>
  37.             <span class="errorMessage"></span>
  38.         </div>
  39.         <div>
  40.             <div class="fieldTitle">Sex:</div>
  41.             <select class="disableWhenNoSelection">
  42.                 <option>Male</option>
  43.                 <option>Female</option>
  44.             </select>
  45.         </div>
  46.         <div>
  47.             <div class="fieldTitle">From:</div>
  48.             <select class="disableWhenNoSelection">
  49.                 <option id="selectFromOptionTemplate"></option>
  50.             </select>
  51.         </div>

  52.         <p>
  53.             <button class="disableWhenNoCategorySelected" onclick="onNewPeople();">New</button>

  54.             <button class="disableWhenNoSelection" onclick="onRemovePeople()">Delete</button>
  55.         </p>
  56.     </div>
  57. </div>
  58. </body>
复制代码
cbs
  1. <script type="text/cbs">
  2.     body{
  3.         knotDataContext:/model
  4.     }

  5.     #treeView{
  6.         foreach:*categories =>treeItem;
  7.     }
  8.     #treeItem>div:first-child{
  9.         class:*isSelected=>boolToClassConverter;
  10.         @click: onTreeItemClicked;
  11.     }
  12.     #treeItem>div:first-child span{
  13.         text:*title
  14.     }

  15.     #treeItem>div:last-child{
  16.         foreach: *children =>treeItem;
  17.         style-display: *isExpanded =>visibleControl
  18.     }

  19.     #treeItem button {
  20.         text: *isExpanded =>boolToExpandSymbol;
  21.         style-opacity: *children.length =>boolToOpacity;
  22.         disabled: *children.length =>enableControl;
  23.         @click: onExpandButtonClicked
  24.     }

  25.     .disableWhenNoCategorySelected{
  26.         disabled: */model.selectedCategory=>enableControl;
  27.     }

  28.     #textCategoryTitle{
  29.         value: */model.selectedCategory.title =!mustNotNull;
  30.     }


  31.     #peopleList{
  32.         foreach:*selectedCategory.peopleList;
  33.     }

  34.     #peopleList>div{
  35.         class:*isSelected=>boolToClassConverter;
  36.         @click:onPeopleClicked
  37.     }

  38.     #peopleList>div span [0]{
  39.         text:*sex=>sexToTitle
  40.     }
  41.     #spanName{
  42.         text:*name
  43.     }
  44.     #spanAge{
  45.         text:*age
  46.     }
  47.     #spanFrom{
  48.         text:*from
  49.     }


  50.     #editArea input[type="text"] [0]{
  51.         value:*selectedPeople.name =!mustNotNull & duplicatedName;
  52.     }
  53.     #editArea .errorMessage [0]{
  54.         style:{
  55.             display:!selectedPeople.name=>visibleControl
  56.         };
  57.         text:!selectedPeople.name
  58.     }

  59.     #editArea input[type="text"] [1]{
  60.         value:*selectedPeople.age =!mustNotNull & checkInteger&ageRange;
  61.     }
  62.     #editArea .errorMessage [1]{
  63.         style:
  64.         {
  65.             display:!selectedPeople.age=>visibleControl
  66.         };
  67.         text:!selectedPeople.age
  68.     }

  69.     #editArea select [0]{
  70.         selected:*selectedPeople.sex;
  71.     }
  72.     #editArea select [1]{
  73.         selected:*selectedPeople.from;
  74.         foreach:/countries
  75.     }

  76.     .disableWhenNoSelection{
  77.         disabled:*/model.selectedPeople=>enableControl;
  78.     }

  79.     #selectFromOptionTemplate{
  80.         text:--self
  81.     }

  82. </script>
复制代码
javascript 代码。绝大多数都是极为简单的逻辑,复杂部分基本上都由绑定解决掉了。
  1. var model = {
  2.         categories:[
  3.             {title:"test", children:[], peopleList:[], isExpanded:true, parent:model}
  4.         ],
  5.         selectedCategory:null,
  6.         selectedPeople:null};

  7.     //here is everything start
  8.     function onLoad() {

  9.         //tie a knot! done!!
  10.         Knot.tie();

  11.         //when validating fails, set focus to the failed element.
  12.         Knot.registerOnValidatingError(function (msg, node) {
  13.             setTimeout(function () {
  14.                 if (node.focus)
  15.                     node.focus();
  16.             }, 1);
  17.         });
  18.     }

  19.     function deselectTreeData(data, children){
  20.         for (var i = 0; i < children.length; i++) {
  21.             if (children[i] != data) {
  22.                 Knot.setValue(children[i], "isSelected", false);
  23.             }
  24.             if(children[i].children){
  25.                 deselectTreeData(data, children[i].children);
  26.             }
  27.         }
  28.     }

  29.     function onExpandButtonClicked(){
  30.         Knot.setValue(this, "isExpanded", !this.isExpanded)
  31.     }
  32.     function onTreeItemClicked(){
  33.         Knot.setValue(this, "isSelected", true);
  34.         Knot.setValue(model, "selectedCategory", this);
  35.         if(this.peopleList.length>0)
  36.             selectPeople(this.peopleList[0]);
  37.         else
  38.             Knot.setValue(model, "selectedPeople");
  39.         deselectTreeData(this, model.categories);
  40.     }

  41.     function addCategory(){
  42.         if(!validate())
  43.             return;
  44.         var cate = model.selectedCategory;
  45.         if(!cate)
  46.             cate = model;
  47.         var newCate = {title: "new", isExpanded:true, peopleList:[], parent:model.selectedCategory};
  48.         if(!cate.children)
  49.             Knot.setValue(cate, "children", []);
  50.         Knot.addToArray(cate.children, newCate);
  51.     }

  52.     function removeCategory(){
  53.         if(!model.selectedCategory)
  54.             return;

  55.         Knot.removeFromArray(model.selectedCategory.parent.children, model.selectedCategory);
  56.         Knot.setValue(model.selectedCategory, null);
  57.         Knot.setValue(model.selectedPeople, null);
  58.     }

  59.     //call this function to validate all data. it is called when model.selected is changed.
  60.     function validate() {
  61.         var errMessage = Knot.validate(document.body);
  62.         if (errMessage) {
  63.             alert(errMessage);
  64.             return false;
  65.         }
  66.         return true;
  67.     }
  68.     //event handler of "new" button
  69.     function onNewPeople() {
  70.         if (!validate())
  71.             return;
  72.         //set default sex to "Male"
  73.         var n = {sex:"Male"};
  74.         //call Knot.addToArray, so that UI will refresh automatically
  75.         Knot.addToArray(model.selectedCategory.peopleList, n);
  76.         selectPeople(n);
  77.     }

  78.     //event handler of "remove" button
  79.     function onRemovePeople() {
  80.         if (model.selectedPeople) {
  81.             var index = model.selectedCategory.peopleList.indexOf(model.selectedPeople);
  82.             Knot.removeFromArray(model.selectedCategory.peopleList, model.selectedPeople);
  83.             if (model.selectedCategory.peopleList.length > 0) {
  84.                 selectPeople(model.selectedCategory.peopleList[Math.min(model.selectedCategory.peopleList.length - 1, index)], true);
  85.             }
  86.             else {
  87.                 //Use Knot.setValue to update data, so the UI will refresh
  88.                 Knot.setValue(model, "selectedPeople", null);
  89.             }
  90.         }
  91.     }

  92.     //set the data to current selected data.
  93.     //setting "model.selected" to update the edit controls, setting "isSelected" to update css on peopleList items
  94.     function selectPeople(data, ignoreValidating) {
  95.         if (!ignoreValidating && !validate())
  96.             return;
  97.         Knot.setValue(data, "isSelected", true);
  98.         Knot.setValue(model, "selectedPeople", data);

  99.         for (var i = 0; i < model.selectedCategory.peopleList.length; i++) {
  100.             if (model.selectedCategory.peopleList[i] != data) {
  101.                 Knot.setValue(model.selectedCategory.peopleList[i], "isSelected", false);
  102.             }
  103.         }
  104.     }

  105.     function onPeopleClicked() {
  106.         selectPeople(this);
  107.     }

  108.     //these converters are used to change the values on data to anything you want them on html. You can create your own, it is super easy!
  109.     //they are associate to Knot by "=>" mark in html
  110.     var boolToClassConverter = Knot.Converters.createBoolToValue("selected", "unselected");
  111.     var enableControl = Knot.Converters.createBoolToValue(false, true);
  112.     var visibleControl = Knot.Converters.createBoolToValue("", "none");

  113.     //these validators are used to validate the inputted value. the last one(duplicatedName) is used to check whether these's another one
  114.     //with the same name. You can see how simple it is.
  115.     var mustNotNull = Knot.Validators.createNotNull("Must not be null!");
  116.     var checkInteger = Knot.Validators.createInteger("Must be integer");
  117.     var ageRange = Knot.Validators.createValueRange(0, 150, "Must in 0~150!");
  118.     var sexToTitle = Knot.Converters.createKeysToValues(["Male", "Female"], ["Mr.", "Miss."]);
  119.     var duplicatedName = function (value, data) {
  120.         for (var i = 0; i < model.selectedCategory.peopleList.length; i++) {
  121.             if (model.selectedCategory.peopleList[i] == data)
  122.                 continue;
  123.             if (model.selectedCategory.peopleList[i].name == value) {
  124.                 return value + " already exists!";
  125.             }
  126.         }
  127.     }

  128.     var boolToExpandSymbol = Knot.Converters.createBoolToValue("-", "+");
  129.     var boolToOpacity = Knot.Converters.createBoolToValue(1, 0);

  130.     //options for the "From" select
  131.     var countries = ["USA", "Japan", "China", "Australia"];
复制代码
回复  

使用道具 举报

92#
发表于 25-10-2013 14:03:02 | 只看该作者
不明觉厉
回复  

使用道具 举报

93#
发表于 21-12-2013 23:09:53 | 只看该作者
本帖最后由 caoglish 于 21-12-2013 23:57 编辑

不明觉厉,idea 非常不错

不说技术实现吧,一个第三方框架的选用原则至少3条:
1.项目有保证有长期稳定维护
2.保证一定量使用者
3.详细的使用文档和使用教程
3.有个社区来做解答资源

如果是技术方面的原则是:
1.有大量的demo
2.有测试方法
3.代码本身有完全的测试代码

要去使用只有一个人维护的库或者是framework,是在是要很大勇气的。除非是计划如果不好用,自己扩充了。

楼主要推广自己的framework,要好好在文档上面下功夫,还有demo可以在todoMVC做的好一点的todo demo,另外一定要写unit testing。并且提供详细的测试方法。



回复  

使用道具 举报

94#
 楼主| 发表于 22-12-2013 23:56:08 | 只看该作者

嗯。最近被其他的事扯住脱不开身。这个东西也先放下了。等过了年再回来继续。
回复  

使用道具 举报

95#
发表于 8-7-2014 15:02:02 | 只看该作者
顶一个,重新关注这个帖子
回复  

使用道具 举报

您需要登录后才可以回帖 登录 | FreeOZ用户注册

本版积分规则

小黑屋|手机版|Archiver|FreeOZ论坛

GMT+11, 27-10-2024 06:35 , Processed in 0.031099 second(s), 18 queries , Gzip On, Redis On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表