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