# JSCommonUtil **Repository Path**: timzaak/JSCommonUtil ## Basic Information - **Project Name**: JSCommonUtil - **Description**: 基于模板的mv*轻量框架 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2014-03-19 - **Last Updated**: 2023-02-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #JSCommonUtil #### 该项目主要是对于web开发中常用的一些功能进行封装。项目主要包含: * 常用javascript类型判定(from avalon) * HashMap,Set集合实现,以及常用集合操作(from angular ,it may deprecate in the future) * 前端页面渲染流程 * javascript版本的promise * pubsub订阅机制 * Array函数扩展 PS:一些代码和思路来源于[AngularJS](http://www.angularjs.org/) [avalon](https://github.com/RubyLouvre/avalon), 并加以修改。 ## 项目使用场景 页面模板众多,而又想依赖ajax做单页应用 支持chrome游览器,其他游览器暂时未测试,一般游览器支持`Object.keys` `Array.forEach` `Array.map` ECMA5标准 的,基本上没什么问题 ## 项目依赖 * `jquery` ## 项目开发环境 项目使用[grunt](http://gruntjs.com/)作为自动化脚本环境, 使用[jasmine](http://jasmine.github.io/)和[jasmine-jquery.js](https://github.com/velesin/jasmine-jquery)作为测试框架,并且做了一些即是demo还是说明的html页面,可供开发参考,由于demo会发送ajax请求views文件夹下的模板,所以需要运行在web容器下面 欢迎fork! ## 简明教程 当前版本将重点放在模板渲染和模板与模板之间的数据变化相应。 ### 公共函数 `jc.util` 该对象现有两个常用的函数,暴露出来,以供大家使用 `isInDocument` `isInDocument(parent,child)`判定子节点是否存在,(当子节点和传入的父节点是同一个时,返回true), 当参数为一个时,判定该节点是否在当前页面body内 `nextUid` 在当前页面产生一个唯一的id标识 `jc.Array` 该对象内封装了多个函数`groupBy` `group` `objToArr` `isEqual` `getIndex` `contain` `getDistinct` `replaceElem`.具体说明,可参照`src/arrayHelper.js`里的注释,用法可参照`test/arrayHelperSpec.js` ,如果在开发中遇到数据转换的问题,可以使用这个对象内的方法。 `groupBy` `group` 两个函数都是用来对数组进行分组的。 `objToArr` 将object对象的key和value转换成数组。 `isEqual` 对数组进行深度判定值是否相等。 `getIndex` 根据提供的参数,数据这个元素在数组中的下表。 `contain` 判定数组中是否拥有某个元素,支持数组元素是是对象的判定。 `getDistinct` 数组去重,仅支持Array类型。 replaceElem 根据查找条件找到对象并替换掉 `jc.mix` 用来做javascript继承(父子继承和数据复制),code from jquery。这个函数在做模板间互动时,会减少一些工作量。 `jc.mix(bool,child,parent)`,`bool`参数代表着是深度copy,还是浅copy(当`parent`有个object|array属性时,浅copy会将这个属性引用赋值给子类,而深copy则是遍历父类的集合属性)。 具体如何用,可参考`prepareSpec.js`中关于该函数的测试用例。 `jc.initPB` 订阅发布模块,可用来创建自己的一套发布订阅机制,功能会随着版本的叠加而增强。现在还没有过滤器概念。但拥有`channel`,参考`pubsubSpec.js`中关于该函数的测试。 `jc.getType` 从avalon中拿过来的关于类型判定的函数,支持`boolean number string function array date regexp object error`的类型判定。返回值皆为小写。基本用法为 ```javascript if(jc.getType(unKnownType)==='object'){ //TODO }else{ //TODO } ``` ### 模板数据 在解释模板数据之前,需要解释一个名词`模板实例`。模板实例是指将模板插入到指定的dom中,并通过数据渲染,这时便成称之为模板实例。一个模板可以有多个实例。模板实例可通过`jc.th($jquerySelector)`获取。 模板数据,就是和模板实例绑定在一起的数据。通过模板的写,它可以监管模板实例view层变化,并反映在数据中。用户只需要操作模板数据即可,不需要再过多的关注view。模板数据以通过`jc.th($jquerySelector).data()`获取。 使用JSCommonUtils需要特别关注模板数据的操作与构建,好的模板数据构建会使模板更容易维护。所以,在写模板之前,请一定要思考好模板数据如何构造。`jc.mix` `jc.Array.objToArr`函数在构建模板数据时会很有用。 > 当要更改更改数组中的值时,请使用`data.array.set($index,value)`方法。这样才会触发变化 ### 模板渲染 `jc.render` ```javascript jc.render(data,url,$jquerySelector,callback,options) //data:模板数据 //url:地址,.html 要省略掉 //$jquerySelector 要确保选择器选出来的存在且唯一,否则抛出异常 //callback:回调函数,并为其传递进当前模板实例引用 //options 暂时还没有用,以后可能会扩展一些函数 ``` 整个项目中重要的函数,用来调用模板渲染函数`scanNodes`以及对模板状态的更新。具体说明可参考`src/documentParse.js`内关于此函数的注释 > 注意,jc.render内部执行了异步调用模板ajax请求,所以关于当前模板实例的业务逻辑都需要写到 > 回调函数中。 #### dom标签 现在已开发的标签有`jc-if` `jc-repeat` `jc-reuse` `jc-bind` `jc-cal` `jc-duplex` `jc-dupfilter` `jc-value` `jc-reset` `jc-attr` `jc-foreach` `jc-class`, 注意:如果使用了未被定义的标签,会自动调用`jc-cal`来进行匹配,例如 : ```html ``` 这两种使用方式等价 ##### `jc-if` ```html
...
``` * `jc-if`表示是否该div存在于dom中,如果`abc`为false|undefined|''|0,则会删除此div,且其子元素不会没扫描。 * `jc-if-not` 和`jc-if`正好相反,如果`abc`为false|undefined|''|0,则显示该div,并扫描子元素。 * `jc-if-loop` 则是和`jc-repeat`一块使用,可参看关于`jc-repeat`标签的讲解 `jc-if` `jc-if-not`在渲染自由度比较大的模板时,会被大量用到 ##### `jc-repeat` ```html
  • {{$index}}: {{elem}}
``` 数据 ```javascript var data={ arr:[1,2,3],//data:[1,2,3] isShow:true } //当要渲染上面的li元素时,data会依据当前要渲染的数组元素生成新的数据结构 { elem:$arrayData,//data.arr[$index] $index:$num, $data:data, $path:$string,//当前元素索引 $isForEach:$boolean//当前是否是在渲染repeat元素 } //来渲染li元素 //如果数组元素是一个object时,会生成下面的数据结构 { elem:{ $objectData, $index:$num, $path:$string//当前元素索引 }, $data:data, $isForEach:$boolean//当前是否是在渲染repeat元素 } //所以,模板数据的key值最好不要以`$`开头,有可能会被框架内部的值覆盖掉 ``` `jc-repeat-$param`用来循环当前元素。`$param`用来做模板写数据的索引,以`$_`或字母开头 在`jc-repeat`中,包含了额外的元素 * $index:当前循环到了第几个元素 ,以0开始 * $path:为当前父索引路径,用户不需要关注它 * $data:传递给`jc.render`的模版数据 在使用`jc-repeat`的时候,会遇到嵌套的情况,请参考`test-repeat.html`关于`jc-repeat`使用。需要注意的是: > `jc-repeat`不支持遍历object,可通过`jc.Array.objToArr`函数来进行转换。 > `$param`参数不要以`$`开头,并且最好不要和索引路径同名称 > `jc-if` `jc-repeat` 是有先后顺序的,先判定 `jc-if` 是不是要渲染,再来判定 `jc-repeat` > 当使用`jc-repeat`嵌套时,当嵌套的最里面为一个值数组时,直接使用$index,$path 即可获取值,如果为object数组时,需要elem.$index,elem.$path来获取值。且嵌套的内层可获取外层的值索引,而外层无法获取内层值索引。 > 当更改集合内的数据时,请使用`set($index,$value)`方法。例如 {a:[1,2,3]},可以使用a.set(2,5)将数据改为{a:[1,2,5]}。直接使用a[2]=5进行 > 赋值的话,不会触发响应关于此数据的变化。 ##### `jc-foreach` `jc-foreach`标签是`jc-repeat`标签的补充,性能上有所下降,能用`jc-repeat`标签的话,不要使用`jc-foreach`,`jc-foreach`是用来迭代内部元素的,例如,循环`dl`中的`dt dd`元素。这种功能是`jc-repeat`标签力所不逮的。 ```html
``` ##### `jc-cal` ```html
``` `jc-cal-$any`会计算表达式的值,然后以 $any='计算后的结果' 插入到当前元素的attribute中,如果当前dom已有该attribute,会被覆盖掉。 任何未被框架声明处理的`jc-$any`,都会转化成`jc-cal-$any`来进行处理 > 注意:`jc-cal-class`被做了特殊处理,会以addClass的形式添加进当前对象中,不会进行覆盖 ##### `jc-attr` `jc-attr-$any`和jc-cal标签都是作用在element上的attribute,但`jc-attr`和`jc-cal`相比,多了模版数据变化触发模版实例变化的功能。 > 注意: jc-attr 不支持 jc-attr-class 。 ##### `jc-value` ```html ``` `jc-value`会和`jc-cal-value`效果功能差不多,唯一不同的是当在`textarea` html标签中使用时,`jc-cal-val`不会起作用(``也不会起作用)。 ##### `jc-reset` ```html ``` `jc-reset-$event`用来重置当前表单,$event 为触发重置表单的事件,默认为`click` > 请不要使用``,它不会触发`jc-bind` 和`jc-duplex`标签效果 ##### `jc-class` ```html
``` `jc-class-$any` 是专门为class设计的,当表达式结果为true时,$any就会加到当前元素的class中去,如果为false,则会从中去除$any ##### `jc-reuse` ```html
....
``` js ```javascript jc.th(...).reRender('tag1',data) ``` 该标签是用来标明其子元素可能会被重新彻底渲染。`jc-reuse` 标签子元素的html会被缓存起来,并用其属性值作为标记,用户可以使用`jc.th(...)reRender`来把其重新渲染一边。注意,传递个reRender的值会更改模版数据。该标签是本框架未完善时的过渡标签,以后可能会废弃掉。 ##### `jc-bind` `jc-bind-$event`用在表单元素上,将view层的变化传递给模板数据。$event为触发事件,不写的话,会根据表单元素的不同特性进行默认。且根据表单元素的特点传递给模板数据的值类型也不尽相同,在设计上,尽量符合游览器表单元素基本使用情况。 下面是数据转化: * input checked ,input radio =>Boolean * input text,input password,select,textarea => string * input number => number 触发事件有 `mousemove` `mouseleave`等表单事件 > input number 如果输入值不正确的话,是不会触发模板数据变化 > `jc-bind`的工作原理,是先判定当前元素的类型,根据类型选定默认触发事件和值处理方法, > 然后绑定更改模板数据的函数到该元素上,当事件发生时,便会执行该函数 ##### `jc-duplex`和`jc-dupfilter` ```html ``` `jc-duplex-$event`是在`jc-bind`的功能之上,做的扩展,支持模板数据变化,触发view层也跟着变化。 如果`jc-duplex`的表达式使用了过滤器,必须在相同的节点上使用`jc-dupfilter`用来写反向过滤器,使之view数据和模板数据可以相互转换 > 用了`jc-duplex`不需要再写`jc-bind`标签 > 关于`jc-duplex`和`jc-bind`如何使用,请参考test_bind.html和test_duplex.html #### 表达式 JSCommonUtil默认`{{...}}` `{{#...}}`作为表达式在TextNode中的包裹标签,不支持{{...{{...}}...}}嵌套使用,如果想改,可以通过jc.config.interpolate(array)来配置(from avalon), `{{...}}`仅仅是计算框内的值然后渲染到页面中去,`{{#...}}`则在会渲染后,会将当前渲染的textNode和模板数据进行绑定,如果模板数据产生了变化,页面也会变化。 关于{{...}}的具体使用,可参考`test-textExpression.html`文件。 表达式分为两部分,第一部分`数值计算-值索引`,第二部分`过滤器`(也可以称之为文本转换器) 用法:`{{数值计算-值索引|过滤器1|过滤器2...}}` ##### 数值计算-值索引 要想熟练使用,需要理解其原理。 值索引: ```javascript //var UniObj ={} function dotPath(object, path) { var re = object; path.split(".").forEach(function(v) { if (re[v] === undefined) { re = UniObj } else { re = re[v] } }); if (re !== UniObj) { return re; } } ``` 数值计算: ```javascript var noop=function(){} //value 就是表达式中的第一部分,data为数据 Function.apply(noop, ["return " + value]).call(data) ``` 所以,当使用数值计算模式时,是需据要this关键字来索引数,请注意: `jc-duplex`,`jc-bind` 标签不支持数值计算模式 表达式如果是要索引数组数据,会和js数组索引有所区别: ```javascript var data={arr:[1,2,3]} // {{arr.0}} 正确 // {{arr[0]}} 错误 // {{this.arr[0]}} 正确 但是不推荐如此用,性能会降低一些 ``` ##### 过滤器 过滤器用来转换表达式第一部分计算结果成其它返回值。过滤器接收参数但会将参数转化为字符串,例如 `{{value|defaultVal(默认值)}}`,`默认值`会转化成字符串传递进defaultVal过滤器函数。 现在框架内部实现了三个过滤器。 * `defaultVal` 当模板数据根据索引找出来的值为undefined或者null时,就会转换为第一个参数。没有参数的话,会默认为空字符串 * `toBoolean` 返回true or false ,当有参数的时候,会将索引出来的值和参数做比较,相同,为true,不同为false * `sampleMap` 简单的值映射,可参考上面的例子 。 > 注意: > {{...}}只能在textNode中使用,不能在attribute中使用,而如果在下列 > area,base,basefont,br,col,command,embed,hr,img,input, > link,meta,param,source,track,wbr,noscript,script,style,textarea > html标签内,表达式不会被处理 > 以后可能会根据需求,将textarea开放出来,现在可用`jc-value`标签来做关于textarea的开发。 ### 模板实例间的订阅 具体例子可参照`常规页面联动.html`。如果数据结构比较混乱时,可参照`test-th-pb.html`。 #### jc.th `jc.th`函数会返回一个方法对象,里面提供类似jquery的`empty` `html` `hide` `show`等方法。 `listen` `listen($path,$targetDivs,$targetPath) `主要是用来监听其他模板数据变化并更改自己的模板数据,具体参考`test-th-pb.html`中的用法,这个函数的参数十分灵活,当目标模板实例和原模板实例 模板数据结构相同时, 可以直接`listen($path,$targetDivs)`使用,$path可以是数据,字符串,object。他们都会在内部被转化成寻址数组 > 注意:监听数组内的具体数据时,最好再监听下整个数组变化,如果数组被调用Array方法或重新赋值时,通知范畴是以数组为单位的。 `bind` `bind($path,function,fnName)`用来绑定一个function,可以通过对函数命名来分类,方便批量`unbind`。在绑定函数列表中,他们的执行是按照绑定的先后顺序来执行的。如果出现了bug,可以使用`jc.th.$pb.fn.$jcQueue`和`jc.$watchPB.fn.$jcQueue`查看当前系统内的函数绑定状态(`jc.th($div)._data("id")`可以获得模板实例的唯一标识) `unbind` `unbind(fnName)`解除绑定的函数,当传递进`$all`时,会删除掉整个绑定在该模板的函数。 `reRender` *deprecated* `reRender`方法要和`jc-reuse`标签搭配使用,使用`jc-reuse`表明该元素的子元素会被重用,然后使用`reRender`方法重新渲染这一块元素。注意,当你 想用该组合时,可以思考有没有更好的写法来替代它。 `empty` `empty`会将当前模版实例从dom中移除掉,并去除响应的事件监听,以达到节约内存的目的 >如果模板要清空的话,请使用`jc.th($jquerySelect).hide()` `jc.th($jquerySelect).show()`等来操作模板状态的变化。 `getCleanData` 渲染后的模版数据是有可能含有额外的`$index $path`值的,如果想获取纯净的数据,直接`jc.th($selector).getCleanData()`即可 ## JSCommonUtil扩展 如果需要更改当前dom的,请扩展 `jc.scanMod`对象,如果是对值进行转换(表达式的过滤器)的,例如将id转换为具体的名称,可扩转`jc.filters`,如果需要根据当前dom进行模板数据操作,可扩展`jc.bindMod`。注意: `jc-id`标签保留,可能会在后面的版本中使用 #####配置 ```javascript //默认配置 jc.config={ tplRootUrl:'./views/',//默认取模版的根目录 } jc.config.interpolate(['{{','}}']) //默认文本计算符 ``` ## 现阶段版本 1.1.0 ## TODO-1.2.0 * refactor the implements of subpub to make it support more fixtures, like `$broadcast` `$emit` of Angular * refactor the tempHelper to make it more easy to understand * make this project compatible with IE8-11/firefox * kick off jquery * kick off jquery ajax, implement a sample ajax and websocket * introduce the event system * fix `array.length=0` bug ## Project Thinking 框架写的时候,想的很多,然后写了一些较为复杂的代码,后来才发现,既然用最简单的方法能实现,就不要追求复杂的东西,虽然复杂带来了一些 想像上的性能提升,但随之而来的bug更会让人头疼。所以,code simple to happy!