woodheadz 发表于 6-10-2013 00: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:<body onload="onLoad();">
<h2>Knot.js Example</h2>
<div>
    <div id="treeArea">
      <div>Group</div>
      <input id="textCategoryTitle" class="disableWhenNoCategorySelected"/>
      <div id="treeView">
            <div id="treeItem">
                <div><button>+</button><span></span></div>
                <div></div>
            </div>
      </div>
      <div>
            <button onclick="addCategory()" class="disableWhenNoCategorySelected">+</button>
            <button onclick="removeCategory()" class="disableWhenNoCategorySelected">-</button></div>
    </div>
    <div id="peopleListArea">
      <div>People</div>
      <div id="peopleList">
            <div>
                <span id="spanTitle"> </span><span id="spanName"></span>
                <hr/>
                Age:<span id="spanAge"></span>,
                From:<span id="spanFrom"></span>
            </div>
      </div>
    </div>
    <div id="editArea">
      <div>
            <div class="fieldTitle">Name:</div>
            <input class="disableWhenNoSelection" type="text"/>
            <span class="errorMessage"></span>
      </div>
      <div>
            <div class="fieldTitle">Age:</div>
            <input class="disableWhenNoSelection" type="text"/>
            <span class="errorMessage"></span>
      </div>
      <div>
            <div class="fieldTitle">Sex:</div>
            <select class="disableWhenNoSelection">
                <option>Male</option>
                <option>Female</option>
            </select>
      </div>
      <div>
            <div class="fieldTitle">From:</div>
            <select class="disableWhenNoSelection">
                <option id="selectFromOptionTemplate"></option>
            </select>
      </div>

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

            <button class="disableWhenNoSelection" onclick="onRemovePeople()">Delete</button>
      </p>
    </div>
</div>
</body>cbs<script type="text/cbs">
    body{
      knotDataContext:/model
    }

    #treeView{
      foreach:*categories =>treeItem;
    }
    #treeItem>div:first-child{
      class:*isSelected=>boolToClassConverter;
      @click: onTreeItemClicked;
    }
    #treeItem>div:first-child span{
      text:*title
    }

    #treeItem>div:last-child{
      foreach: *children =>treeItem;
      style-display: *isExpanded =>visibleControl
    }

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

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

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


    #peopleList{
      foreach:*selectedCategory.peopleList;
    }

    #peopleList>div{
      class:*isSelected=>boolToClassConverter;
      @click:onPeopleClicked
    }

    #peopleList>div span {
      text:*sex=>sexToTitle
    }
    #spanName{
      text:*name
    }
    #spanAge{
      text:*age
    }
    #spanFrom{
      text:*from
    }


    #editArea input {
      value:*selectedPeople.name =!mustNotNull & duplicatedName;
    }
    #editArea .errorMessage {
      style:{
            display:!selectedPeople.name=>visibleControl
      };
      text:!selectedPeople.name
    }

    #editArea input {
      value:*selectedPeople.age =!mustNotNull & checkInteger&ageRange;
    }
    #editArea .errorMessage {
      style:
      {
            display:!selectedPeople.age=>visibleControl
      };
      text:!selectedPeople.age
    }

    #editArea select {
      selected:*selectedPeople.sex;
    }
    #editArea select {
      selected:*selectedPeople.from;
      foreach:/countries
    }

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

    #selectFromOptionTemplate{
      text:--self
    }

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

    //here is everything start
    function onLoad() {

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

      //when validating fails, set focus to the failed element.
      Knot.registerOnValidatingError(function (msg, node) {
            setTimeout(function () {
                if (node.focus)
                  node.focus();
            }, 1);
      });
    }

    function deselectTreeData(data, children){
      for (var i = 0; i < children.length; i++) {
            if (children != data) {
                Knot.setValue(children, "isSelected", false);
            }
            if(children.children){
                deselectTreeData(data, children.children);
            }
      }
    }

    function onExpandButtonClicked(){
      Knot.setValue(this, "isExpanded", !this.isExpanded)
    }
    function onTreeItemClicked(){
      Knot.setValue(this, "isSelected", true);
      Knot.setValue(model, "selectedCategory", this);
      if(this.peopleList.length>0)
            selectPeople(this.peopleList);
      else
            Knot.setValue(model, "selectedPeople");
      deselectTreeData(this, model.categories);
    }

    function addCategory(){
      if(!validate())
            return;
      var cate = model.selectedCategory;
      if(!cate)
            cate = model;
      var newCate = {title: "new", isExpanded:true, peopleList:[], parent:model.selectedCategory};
      if(!cate.children)
            Knot.setValue(cate, "children", []);
      Knot.addToArray(cate.children, newCate);
    }

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

      Knot.removeFromArray(model.selectedCategory.parent.children, model.selectedCategory);
      Knot.setValue(model.selectedCategory, null);
      Knot.setValue(model.selectedPeople, null);
    }

    //call this function to validate all data. it is called when model.selected is changed.
    function validate() {
      var errMessage = Knot.validate(document.body);
      if (errMessage) {
            alert(errMessage);
            return false;
      }
      return true;
    }
    //event handler of "new" button
    function onNewPeople() {
      if (!validate())
            return;
      //set default sex to "Male"
      var n = {sex:"Male"};
      //call Knot.addToArray, so that UI will refresh automatically
      Knot.addToArray(model.selectedCategory.peopleList, n);
      selectPeople(n);
    }

    //event handler of "remove" button
    function onRemovePeople() {
      if (model.selectedPeople) {
            var index = model.selectedCategory.peopleList.indexOf(model.selectedPeople);
            Knot.removeFromArray(model.selectedCategory.peopleList, model.selectedPeople);
            if (model.selectedCategory.peopleList.length > 0) {
                selectPeople(model.selectedCategory.peopleList, true);
            }
            else {
                //Use Knot.setValue to update data, so the UI will refresh
                Knot.setValue(model, "selectedPeople", null);
            }
      }
    }

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

      for (var i = 0; i < model.selectedCategory.peopleList.length; i++) {
            if (model.selectedCategory.peopleList != data) {
                Knot.setValue(model.selectedCategory.peopleList, "isSelected", false);
            }
      }
    }

    function onPeopleClicked() {
      selectPeople(this);
    }

    //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!
    //they are associate to Knot by "=>" mark in html
    var boolToClassConverter = Knot.Converters.createBoolToValue("selected", "unselected");
    var enableControl = Knot.Converters.createBoolToValue(false, true);
    var visibleControl = Knot.Converters.createBoolToValue("", "none");

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

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

    //options for the "From" select
    var countries = ["USA", "Japan", "China", "Australia"];

smnox 发表于 25-10-2013 13:03:02

不明觉厉

caoglish 发表于 21-12-2013 22: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。并且提供详细的测试方法。



woodheadz 发表于 22-12-2013 22:56:08

caoglish 发表于 21-12-2013 23:09 static/image/common/back.gif
不明觉厉,idea 非常不错

不说技术实现吧,一个第三方框架的选用原则至少3条:


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

ubuntuhk 发表于 8-7-2014 14:02:02

顶一个,重新关注这个帖子:good:good
页: 1 2 3 [4]
查看完整版本: 发起了一个Javascript开源MVVM框架