Git Product home page Git Product logo

esui's Issues

增加一个DOM事件模块

在控件的开发中,免不了要往main元素上加事件,也免不了要在dispose()的时候把这些事件去掉。

但是这里存在几个问题:

  1. 往往注册上去的事件执行时,我们希望在事件的处理函数内部可以访问到控件的实例(如通过this或者me),这就必然导致事件的处理函数是一个在闭包内的函数(为了固定this或能引用me),也进一步导致,在取消这些事件的时候,容易找不回那个处理函数(因为在闭包里)不好取消。
  2. 在控件的整个生命周期和交互过程中,会不限量地注册各种事件,要在dispose()的时候一一去掉,就要记住注册了哪些事件,用哪些处理函数,这些逻辑全部用代码来写而不抽象出来早晚会混乱。

当然一个非常简单的方法,就是参考现在的controlHelper模块,用onxxx属性来挂事件,到时候只要onxxx = null;就能去掉,但这不仅仅受到 一个事件只能注册一个函数 的限制,同时也只解决了问题1,没解决问题2。看看controlHelper中的initMouseBehaviordispose中的处理,并不是十分潇洒漂亮的形式,这也仅仅是限定地处理了鼠标相关的,有更多事件的话会更难管理。

因此,建议在控件上(事实上是controlHelper模块)中添加一个专门负责main对象上的事件处理,通过addDOMEventremoveDOMEventclearDOMEvents等方法来管理DOM事件,保证注册上就能去掉,而且在dispose()的时候可以去干净。

如果大家认为确实可以有这样一个模块的话,我会负责实现之。

[评审]UI标准 - Button

ECOM UI组件标准 - Button

继承层级

Button
    - Control

prototype.type

"Button"

控件主元素

任何元素都可做为按钮的控件主元素。默认的控件主元素为button。

属性

{string} content

按钮的显示文字。不进行HTML过滤。

{number} height

按钮的高度。

{number} width

按钮的宽度。

公共实例方法

{void} setContent( {string} content )

设置按钮的显示文字。

参数:

  • {string} content: 按钮的显示文字

{void} setHeight( {number} height )

设置按钮的高度。

参数:

  • {number} height: 按钮的高度

{void} setWidth( {number} width )

设置按钮的高度。

参数:

  • {number} width: 按钮的宽度

事件

click

当按钮被点击时触发。

事件对象成员:

  • {string}type: 事件名

实现无根节点的Tree控件

虽然E-JSON中说明树的数据源结构应该是一个Object,但是很多系统中的树,其实并不需要一个要,而是一个标题(<h3>之类)下直接放一列的一级节点,形如:

*选择频道*

    + 其它频道
    + 频道1
    - 频道2
        - 子频道1
        - 子频道2
        - 子频道3
    + 频道3

很多时候,树的根节点并没有什么意义,第一层直接显示才是正常的。

因此,需要让Tree控件的datasource属性支持一个数组。

[评审]UI标准 - Calendar

ECOM UI组件标准 - Calendar

继承层级

Calendar
    - InputControl
        - Control

功能描述

日历控件包括一个日期显示框和一个弹出层形式的单月日历。

prototype.type

"Calendar"

控件主元素

控件主元素必须为div。

数据格式说明

选中的日期,为Date类型的Javascript对象。

允许选择日期区间“range”的数据格式,以一个Object表示。该Object拥有name为begin和end的成员,分别为Date类型。

{
    begin: new Date(1983, 8, 3),
    end: new Date(2011, 10, 4)
}

属性

{string} dateFormat

日期显示的格式化方式。默认'yyyy-MM-dd'。

{string} paramDateFormat

日期参数的格式化方式。默认'yyyy-MM-dd'。

{Object} range

可选中的日期区间。可选,默认

{
    begin: new Date(1983, 8, 3),
    end: new Date(2046, 10, 4)
}

{Date} rawValue

当前选中的日期的原始数据。

公共实例方法

{Date} getRawValue()

获取当前选中的日期。

{void} setRange( {Object} range )

设置允许选中的日期区间。

参数:

  • {Object|string} range: 允许选中的日期区间

支持字符串型,‘,’分割起始时间和结束时间。如:1983-09-03,2046-11-04。

{void} setRawValue( {Date} value )

通过日期格式,设置当前选中的日期。

参数:

  • {Date} value: 当前选中的日期

事件

change

当选择的日期发生改变时触发。

事件对象成员:

  • {string}type: 事件名
  • {Date}rawValue: 选择的值,日期格式

onchange问题

select calendar schedule 等都有onchange事件。

  • 当setValue、setRawValue的时候需要执行onchange么?
  • onchange是否需要有返回值为false,取消行为的功能?

HTML的data-ui属性中值的trim问题

以前有讨论过,我的意见是data-ui属性中,类似style属性写法的值,key和value都应该去掉前面的空格。

因为很多情况下会这么写:

<div data-ui="type: Panel; id: panel;"></div>

在冒号和分号后会习惯性地加一个空格,这也使得代码更清晰易读,因此应该在解析的时候去掉这空格。

对于 原本就故意想加空格 这样的需求,是否可以要求其车用data-ui-*属性完成?

Select控件标准的几个问题

关于emptyText

原定emptyText是在 什么都没选择 的情况下显示的文字,但这里有几点比较奇怪:

  • 事实上Select的交互过程中,只有刚渲染完会出现 什么都没选择 的情况,而为了保证后续还能回到这个状态,在下拉框中会保留一项
  • 原生的<select>元素是没这种东西的,默认选中第一项,由<option>控制
  • 有时候,datasource满足了emptyText的需求,即有一个默认项,此时去不掉emptyText的配置会很麻烦

因此,建议把这个属性修改一下,新的描述如下:

emptyText指定在datasource之外,增加一个value为空的选项的文字。如果此属性的值为空,则不增加额外的一项。额外的一项永远加在最前面。

关于staticText

据有关部门了解,这个属性是用来把Select控件变成一个命令菜单用的。但其实命令菜单不应该是InputControl,因此建议单独实现CommandMenu控件,优势如下:

  • CommandMenu只需{ text: handler }[ text ... ]的配置,而不需要{ text: value }的配置,因此两个控件的datasource是不同的
  • CommandMenu没有随表单提交的需求,不会额外生成提交的参数
  • Select控件中大量逻辑在处理valuerawValueselectedIndex这三个属性的同步问题,而CommandMenu不需要
  • Select处理同步已经有很大的负担,再额外增加staticText复杂性会太高

关于Command扩展和Table控件

几个事情需要提一下:

  1. 所有的Extension是给业务人员用的,控件开发人员 不能使用 ,这就类似一个基础库的代码不能去依赖业务线的代码,这是一种强制性的设计理念。Table中有用到Command,虽然可能辛苦点,但希望Table可以清理一下这一块的代码,写成标准的DOM事件。不然会导致不少的问题(比如在使用Table的时候再配一个Command扩展会冲突等)
  2. Command在修发后,我发现当触发command事件时,会带上两个参数:eelement,这两个参数全是 DOM相关 的,而ESUI的理念是 屏蔽全部DOM相关的逻辑 ,因此在ESUI体系下暴露的事件对象中让使用者可以访问到DOM是 不合理 的,现在由于Table依赖着他,因此我还没去掉这些代码,希望清理Table后可以去除

Table由于过于庞大,且最近事多,没来得及详细看一遍,后续我会尽量抽时间Review。

@wurongyao 关注一下

关于渲染和重渲染的差异

控件第一次渲染称为 渲染 ,已经渲染后因为属性的变化等导致的再次渲染(主要是setProperties这方法)称为 重渲染 ,希望确定几个事:

  1. 渲染和重渲染是否要暴露不同的事件,因为很多时候在afterrender事件上注册main的DOM事件,如果再次重渲染,这个DOM事件不应该再注册一次,因此建议是有区分的
  2. set方法是否默认调用repaint,我认为是不需要的,默认调用repaint的方法应该通过setXxx方法暴露出来。有时因为业务需要,想在控件上挂一点和控件本身没啥关系的数据,ESUI又不希望开发者直接挂属性,所以会用set,这要是set一次repaint()一次,会很糟糕

Tip控件未完成部分

参考:http://fe.baidu.com/doc/ecom/std/ui/v1.1/tip.text

  1. 增加hideDelay属性,控制消失的延迟时间,注意计时器不要混乱
  2. 增加mode属性,控制交互的事件
  3. 增加showhide事件

另,建议增加:

  1. 增加showDelay属性,当mode值为over时生效,不要鼠标路过就显示出来,默认值为100ms
  2. 增加自动摆放模式,当arrow为1或true等非字符串值,或者无arrow时,自动根据空间判断放置的位置和箭头(可二期再做)
  3. 增加从已有的DOM中抽取titlecontent属性,避免在js中还要写HTML或者需要在模板中分离对应的内容的尴尬(可二期再做)

@badplum 关注,尽快实现

完善TextBox

  1. createMain接受参数options,从参数中拿mode等值
  2. 去掉render方法,使用initStructure来注册focusblur等事件和其它逻辑
  3. 去掉setRawValue,交由repaint处理
  4. textarea模式下不应该把enter事件丢出去
  5. 事件注册统一用helper.addDOMEvent,这样inputHandler也不用挂上面了,dispose方法可以去掉

我们还是谁写的谁负责的原则,请 @errorrik 辛苦一下

关于控件的readOnly和disabled状态

几个问题:

  1. 是否所有控件都有disabled状态,各控件在disabled状态下的行为如何标准化,如Panel控件如果有disabled状态,则是否其子控件都进入disabled状态?
  2. 控件在disabled状态下,修改其属性是否重新渲染,如TextBoxdisabled状态下调用setValue(value)是否有效?
  3. 控件在readOnly状态下,修改属性是否重新渲染?

个人意见:

  1. 并非所有控件都有disabled状态,但InputControl一定会有。如LabelPanelDialog这一些并不需要这样的状态。
  2. disabledreadOnly状态下均 正常进行 repaint操作,这一点参考HTML的<input>系控件。

需要增加一个Crumb控件

面包屑导航条是不少系统有的内容,但在业务级别上制作这个会有一些通用的东西可以抽取:

  • 当前页不使用链接,其它项都使用链接,需要区分
  • 各项之间的分隔符可以统一

建议提供一个简单的Crumb控件,接受一个数组作为数据源来绘制面包屑

关于控件的生成

在从HTML生成控件main.init时,建议在生成控件后,为元素加上一个data-ui-id属性,这个属性的值就是控件的id值。

分为以下3个情况:

  • 如果本身就有这个属性,则控件的id一定是这个属性的值,因此再写回去没问题
  • 如果本身有data-ui属性,里面有id的值,则再写回去也没问题
  • 如果没声明id,则不会有data-ui-id这个属性,控件自动生成一个id,后加上这个属性也没问题

因此认为这么做没有副作用。

这么做的好处是:

  • 调试的时候可以迅速从DOM树看到对应的控件的id,从而用ViewContext.prototype.get去拿到并调试
  • getControlFromElement(虽然不希望这方法存在)可以更方便地找到对应的控件

@errorrik 评估一下,OK的话我去加上

painters模块

概述

在ESUI中新加了painters模块,该模块用于生产实现repaint方法的对象,具体使用大致如下:

var paint = require('./painters');
XxxControl.prototype.repaint = require(./controlHelper').createRepaint(
    paint.style('width'),
    paint.html('content'),
    {
        name: 'personName',
        paint: function (control, value) {
            control.main.innerHTML = 'Hello ' + value;
        }
    }
);

大致描述过程可以总结为:

  1. 引用painters模块
  2. 创建一个数组,其中每一项是一个painter对象
  3. 使用controlHelper.createRepaint,以第2步创建的数组为参数,生成repaint方法的实现

painter对象

一个painter对象描述 使用什么方法渲染哪个属性painter对象是一个普通的javascript对象,但其必须有以下2个属性:

  • {string} name:指定负责的属性名称,一个painter对象只能负责一个属性
  • {function(Control, *)} paint:指定渲染该属性的方法,方法接受 控件的实例 和** 属性的值** 作为参数

内置有以下painter对象的生成函数(自己看源码更容易理解):

  • style({string} name, {string} property):将控件属性的值作为main元素的样式,会自动加上px
  • html({string} name, {string} member, {function} generate):将属性的值作为某个类型为HTMLElement的成员(默认为main)的innerHTML使用,可提供generate方法指定如何生成HTML
  • text({string} name, {string} member, {function} generate):将属性的值作为某个类型为HTMLElement的成员(默认为main)的innerText使用,可提供generate方法指定如何生成HTML,注意generate方法生成的是HTML,不需要转义
  • delegate({string} name, {string} member, {string} method):将更新委托给某个成员的某个方法,用于组合控件时如将title转给titleLabel.setText方法。

示例

以下是Select控件的示例,Select控件需要关注的属性有:

  • widthheight影响到主元素的样式
  • datasource修改后,下拉弹层中的内容需要更新
  • rawValue修改后,需要更新和值相关的界面
  • disabled变成true时,如果已经打开了下拉层,则要关上

实现如下:

/**
 * 根据控件的值更新其视图
 *
 * @param {Select} select Select控件实例
 * @inner
 */
function updateValue(select) {
    // 同步`value`
    var hidden = select.main.getElementsByTagName('input')[0];
    hidden.value = select.rawValue;

    // 同步显示的文字
    var selectedItem = select.datasource[select.selectedIndex];
    var displayText = selectedItem ? selectedItem.text : '';
    var textHolder = select.main.getElementsByTagName('span')[0];
    textHolder.innerHTML = require('./lib').encodeHTML(displayText);
}

var paint = require('./painters');

/**
 * 重绘
 *
 * @param {Array=} 更新过的属性集合
 * @protected
 */
Select.prototype.repaint = require('./controlHelper').createRepaint(
    // 宽和高通过样式设定
    paint.style('width'),
    paint.style('height'),
    // datasource更新后通过getLayerHTML生成selectionLayer的innerHTML,
    // selectionLayer是延迟生成的,可能不存在,painter会自行处理
    paint.html('datasource', 'selectionLayer', getLayerHTML),
    // 禁用时关上弹层
    {
        name: 'disabled',
        paint: function (select, value) {
            if (value && select.selectionLayer) {
                hideLayer(select);
            }
        }
    },
    // rawValue变化时,需要的逻辑有些复杂,且不通用,所以单独一个方法
    {
        name: 'rawValue',
        paint: updateValue
    }
);

框架变更

为了更好地使用这套机制,框架层面有以下变化:

setProperties方法添加返回值,将设置的值中有变化的部分返回,返回一个对象。对象的键是属性名,值为一个变更项,包含nameoldValuenewValue属性。这一变更是为了子类可以调用父类setProperties后知道哪些值有变化过,并触发如change之类的事件。

repaint现在有了2个参数,第2个参数是第一个参数的索引,没实际意义,只是实现了上面一点后这个毫无代价顺手就给了。

问题

  1. 有些控件,其实是存在多个属性其实对应一个元素的更新的,比如SelectselectedIndexrawValue,这种通常通过setProperties里额外处理,去掉其中一个属性来实现。但是painter是不是需要支持多个属性的管理,即多个属性更新一个或多个时,执行一次paint
  2. painter到底是通过OO实现还是函数式实现好

关于BoxGroup

几个意见:

  1. 我认为这东西不应该是个控件,反正不会在HTML中声明这个东西,也不会appendTorender,所以不需要继承InputControlControl,是个辅助类就行了
  2. 新的ESUI有Form控件,我建议在Form控件中提供getInputControlsByName({string} name, {string=} type)方法,不要再用getElementsByName + getControlFromElement的结构了,维持更好的结构性

@kitemao 参考,如果OK我去发动BoxGroupForm

showValidity的实现

需要一个高灵活性的实现,不同项目展示验证消息的方式大不一样。

建议:把showValidity丢到InputControl.prototype下去,helper模块是不对外的,无法提供劫持的功能

问题:默认的实现怎么样的合适,在InputControl后面插一个label元素放内容?

@errorrik @DDDBear @kitemao @wurongyao 一起讨论下吧,现在各系统的验证显示是长啥样的?

[评审]UI标准 - Table

Table控件标准

Table控件形如HTML中的<table>元素,主要用于二维数据的展现。

继承关系

- Control
    - Table

主元素

Table控件的主元素是一个<div>元素。

属性

{int} width

表格体的宽度,单位为px。默认自适应内容宽度。
如果设置该值,则将按照该宽度绘制Table,而不再根据父容器大小自适应。

{int} bodyMaxHeight

表格体的最大高度,单位为px。

{boolean} breakLine

表格内容是否允许断行。默认false。

{Array.} datasource

控件的数据源,数据源中的每一个对象称为 数据项,对应到表格每行的显示数据 。

{boolean} disabled

控件的不可用状态。默认false。处于不可用状态时,表格拖拽、排序、行选中等功能都将禁用。

{boolean} columnResizable

表格是否允许拖拽改变列宽。默认true。

{boolean} followHead

在滚动条纵向滚动时,是否表头跟随。默认false。

{string} noDataHtml

没有数据时,表格体中显示的html内容。默认null。

{boolean} noHead

表格是否不显示标题。默认false。

{string} select

设置表格的选择方式。single(单选)|multi(多选)。默认null。

{string} selectMode

设置表格的选择模式。line|box。默认box。如果设置了值为line,则点击行时触发选择。

{boolean} sortable

表格是否允许排序。默认false,

{boolean} subrow

表格是否允许子行。默认false,

{boolean} subrowMutex

表格子行是否互斥,也就是打开一子行其他子行是否需自动关闭。默认false,

{Array.} fields

表格的列配置。

初始化的参数中,fields是一个数组,作为表格显示的列设置。其中每一列的可选属性如下:

{string} align

列内容的排序方式。left | center | right。 默认left。

{boolean} breakLine

列是否允许断行。默认应用控件breakLine属性。

{string|Function} content

每一行该列需要显示的内容。为Function时应用返回值。

Function的接口形式为 fn(item, index),item为每行对应数据项,index对应datasource数组序号。

{boolean} resizable

该列是否允许拖拽改变列宽。默认true。

{string} field

该列的字段名。主要用于可排序表格中的可排序字段。

{number} minWidth

该列的最小宽度。表格分配列宽时分配宽度不会小于设置的minWidth。

{boolean} stable

是否固定列。表格分配列宽时不会影响到固定列。默认false。

{boolean} sortable

是否依据该列进行数据排序,默认false。

{string|Function} title

该列标题需要显示的内容。

{string|Function} tip

该列标题需要显示的提示信息,当鼠标移动到表头的tip控件上时显示。

{number} width

设置该列的宽度。未设置stable时,会自动根据其他列设置的width,按比例分配,增减调整宽度。

公共实例方法

{Object} getSubrow(index)

获取表格子行的元素。

参数:

  • {int} index: 行序列值

{void} adjustWidth()

自适应表格宽度。
若之前已设置Table的width属性,该方法将不起效果。

事件

click

当控件主元素被点击时触发。

事件对象成员:

  • {string}type: 事件名

select

  • 当表格项被选中时触发。

事件对象成员:

  • {string}type: 事件名
  • {Array|number}selectedIndex: 单选模式时为选中项的索引,多选模式时为选中项索引的数组。

sort

当用户点击表格排序时触发。

事件对象成员:

  • {string}type: 事件名
  • {Object}field: 当前排序列,对应初始化参数fields设置中的当前排序项
  • {string}order: 排序方式,asc|desc

subrowopen

当用户点击展开子行按钮时触发

事件对象成员:

  • {string}type: 事件名
  • {int}index: 当前行序列,对应datasource中的当前行序列
  • {Object}item: 当前行数据

subrowclose

当用户点击展开子行按钮时触发

事件对象成员:

  • {string}type: 事件名
  • {int}index: 当前行序列,对应datasource中的当前行序列
  • {Object}item: 当前行数据

控件DOM结构

控件主元素的固定结构如下:

<div>
    <div> <!-- head -->
        <table>
            <tr><th>标题</th></tr>
            <tr><th>标题</th></tr>
            ......
        </table>
    </div>
    <div> <!-- body -->
        <div> <!-- rows -->
            <table>
                <tr><td>数据</td></tr>
                <tr><td>数据</td></tr>
                ......
            </table>
        </div>
        <div>
            <table>
                <tr><td>数据</td></tr>
                <tr><td>数据</td></tr>
                ......
            </table>
        </div>
        ......
    </div>
    <div> <!-- foot -->
        <table>
            <tr><th>信息</th></tr>
        </table>
    </div>
</div>

关于InputControl的交互行为

以下两点是我 强烈 要推动的(甚至希望强制之):

  1. 对IE6不作完美兼容(这里是带有强制性地放弃IE6的完美兼容,而不是“如果我做得到,我去做”这样消极的想法),如鼠标悬浮的样式(仅样式,如果有动态交互的行为另谈)直接不支持IE6。这有助于我们减少控件开发者和业务线人员的精力,逐渐推动IE6的淘汰。
  2. 输入控件支持键盘操作,如tab导航等。

因此,我提出以下建议:

  1. 控件是否加hoveractivepressfocus这些state由控件自己决定,仅样式上的变化的话,通过CSS处理,不通过鼠标事件管这事。
  2. 一定要有:hover:active:focus这几个伪类,样式通过这些伪类实现,不去使用这几个state相关的class。通过鼠标事件修改的class通常只能管理鼠标操作的情况,键盘操作不能覆盖,远没有伪类来得正确。
  3. InputControl的主元素必须有tabindex,默认值为0,这个可以在InputControl.prototype.initStructure中实现。
  4. 复杂的输入控件考虑键盘的操作,如Select应该能通过TAB聚焦,然后按方向键下或者回车打开层,使用方向键选择具体的项,按回车确认。

关于控件生命周期各阶段的各方法的可用性

参考以下代码:

var label = new Label();
label.main; // null
label.setText('Hello World');
label.appendTo(document.body);

需要确定几个问题:

  1. appendTo导致生成main元素前,setText能不能调用(调用时是否允许异常)
  2. 如果允许调用setText,则在渲染后,这个Label的文字要不要显示为 Hello World (即是否允许在main出现前的相关属性调用被丢弃)

[评审]UI标准 - Wizard

Wizard控件标准

Wizard控件用于多步骤的引导式导航。

继承关系

- Control
    - Wizard

主元素

Wizard控件的主元素 只能<ol><ul>元素,推荐(默认)使用<ol>

属性

{Array.} steps

指定步骤,数组中的每一项称为 单步数据项 ,一个单步数据项可以包含以下属性:

  • {string} text:显示的文字
  • {string=} panel:每个步骤可提供一个对应的面板的DOM元素的id,如果有该属性,则当该步骤激活时显示该面板,非激活的步骤对应的页面将被隐藏。

{string=} finishText

指定所有步骤完成时的文字。如果有该属性,则将在所有的步骤之后多出一个节点显示该文字

{number} activeIndex

当前激活的步骤的下标。

方法

{Object} getActiveStep()

获取当前激活的步骤对应的单步数据项。

返回值

当前激活的步骤对应的单步数据项。

{void} stepNext()

进入下一步,会随带触发enter事件。

{void} stepPrevious()

回到上一下,会随带触发enter事件。

事件

enter

在进入某一步时触发。

扩展点

{string} nodeTemplate

nodeTemplate属性可以用来指定控件中每一项生成时的HTML模板,其中占位符包含:

  • ${text}:显示的文本。

占位符的替换均在经过HTML编码后进行。

{function(Object): string} getNodeHTML

该方法用于生成控件中每一项的HTML串,默认实现是基于nodeTemplate属性的字符串格式化操作。

重写该方法后,nodeTemplate的功能将失效。

控件DOM结构

控件的标准DOM结构如下:

<ol class="ui-wizard">
    <!-- for ${steps} as ${step} -->
        <li class="ui-wizard-node">${step.text}</li>
    <!-- /for -->
    <!-- if ${finishText} -->
        <li class="ui-wizard-last ui-wizard-finish">${finishText}</li>
    <!-- /for -->
</ol>

与状态相关的class如下:

  • ui-wizard-node-first:第一个节点。
  • ui-wizard-node-last:最后一个节点,如果有finishText属性,则最后一个节点永远是完成提示文字的节点。
  • ui-wizard-active:当前的节点。
  • ui-wizard-done:已经完成的节点。
  • ui-wizard-active-prev:当前节点的前一个节点。
  • ui-wizard-last-active:既是最后一个节点又是激活的节点。
  • ui-wizard-panel-hidden:由Wizard控件控制的被隐藏的面板。

Tree的设计提案

关于Tree控件的设计

Tree控件从实现上来说可能并不是最难的,但就交互来说,绝对可以算得上最烦杂的控件,甚至可以说没有之一。一个Tree控件最常用的交互有以下2个:

  • 展开节点,称之为expand
  • 收起节点,称之为collapse

但在这2个交互发生时,采取的行为却大为不同:

  • 数据可能是远程的,作为延迟加载,则要依赖XMLHttpRequest来加载
  • 数据可能是本地的,则从树状的对象中直接提取后展开或收起
  • 数据还可能是其它地方来的,比如通过节点运算从一个本地的数组中筛选出来(类似本地数据库)
  • 可能这个节点根本没有数据,则展开操作仅改变前面的图标不做任何操作

但是Tree作为一个控件,去引入XMLHttpRequest之类的,并且来根据自己的数据源判断进行这些操作,显然不合理且会增加实现的复杂度。

因此,我对Tree进行了一个重新设计,看下图:

tree

这个设计下,Tree控件仅提供最基本的事件和方法,有:

  • expandcollapse事件,提供节点对象(数据源中的对象,非DOM节点)作为参数
  • fillNode方法,向一个节点中填充子节点
  • emptyNode方法,清掉一个节点中的子节点
  • indicateNodeLoading方法:指示某个节点的数据正在加载

当点击一个节点时,Tree控件仅会根据节点的状态触发一个事件,本身不做任何的数据查询、加载、绘制工作,直到注册事件的逻辑来调用fillNodeemptyNode,才进行数据的填充操作

当然这么设计的话,控件使用会变得非常麻烦,因此再提供一个叫TreeDatasource的扩展(名字现在觉得不太对,要再想想),来实现这些功能。

TreeDatasource是一个针对Tree设计的类,esui提供的默认实现是接受一个树状的对象(参见EJSON相关章节)。当这个扩展起作用后,会注册Treeexpandcollapse事件,当事件发生时,在Treedatasource中找到对应节点,取出子数据,自动调用fillNodeemptyNode完成视图展现。

这么做的好处是,对于一个非常依赖数据,同时有可能需要延迟加载数据的控件,进一步把 数据视图 分离,保证控件只关注视图和交互,不关心数据的存取。如果需要延迟加载数据,则可以进一步提供一个RemoteTreeDatasource的扩展(后续由ef项目提供),这样esui不用关心XMLHttpRequest,又能非常简单地挂载上数据的远程加载。

对于这个设计,还存有几个问题:

  1. Tree提供的方法是不是够,从我对Tree的使用来看是够了,看看其它项目组有没进一步的需求(注意每个节点加个checkbox不是这一需求覆盖范围,有另外的设计支持)
  2. 这个TreeDatasource应该是个扩展,还是Tree自身抽象出来的一个组件。
    • 扩展的特点是可用可不用,不高兴了业务逻辑自己注册事件调用方法,但一个扩展只为一个类型的控件服务感觉别扭。
    • 如果是Tree自身要求的一个组件,则认为是必须有的,作为Tree的一个属性,同时避免扩展只为特定控件服务的情况。

Schedule快捷按钮无效

点击[全周投放]等快捷按钮时报错:

Uncaught TypeError: Cannot read property 'getVal' of undefined

[评审]UI标准 - Tree

Tree控件标准

Tree是树控件,将一个树状的数据源对象以树型显示出来,同时控制节点的展开/收起操作。

继承关系

- Control
    - Tree

主元素

Tree控件的主元素可以是<div>或任何流式元素,默认使用<div>元素。

属性

{Object} datasource

树的数据源,其中一个节点的对应的数据称为 节点数据项 ,一个节点数据项可包含以下属性:

  • {string} id:节点的唯一id,必须在所有节点中具有唯一性。
  • {string} text:显示的文字。
  • {Array.<Object>=} children:子节点,数组中每个对象是一个节点数据项。

{TreeStrategy} strategy

与当前树关联的交互策略对象,详见 扩展点 部分对TreeStrategy的介绍。

需注意的是,strategy属性是 一次性 的,在实例化的时候设置,随后 不能 修改。

方法

{void} expandNode({string} id, {Array.=} children)

展开指定的节点,如提供了children参数,则使用该数组作为子节点数据。

参数

  • {string} id:待展开的节点的id。
  • {Array.<Object>=} children:填充的子节点数据。

children未提供时,将按以下规则处理:

  1. 如果曾经展开过该节点,即子节点的DOM结构已经生成,则直接将子节点的DOM显示出来,不作进一步处理。
  2. 如果未加载子节点的DOM结构,则去取id对应的节点数据中的children属性作为数据填充。

通常来说,当对应的节点已经有children属性时,调用expandNode时不需要传递children参数,以免在原来节点已经展开过的情况下,再次生成子树的DOM结构造成不必要的性能损失。

{void} collapseNode({string} id, {boolean=} removeChild)

收起指定的节点,并根据removeChild参数决定是否移除已经生成的子树的DOM结构。

参数

  • {string} id:待收起的节点的id。
  • {boolean} removeChild:默认值为false。如果此参数为true,则在收起的时会移除子树的DOM结构。当需要有效减少节点数量和控制性能时,建议传递此参数。

{void} indicateNodeLoading({string} id)

将指定节点标记为“加载中”状态。通常当树的节点展开时需要从远程加载数据,则可以在数据加载期间调用此方法给予用户提示。

参数

  • {string} id:需要标记的节点的id。

事件

expand

当一个收起的节点被请求展开时触发。

需注意的是,Tree控件仅触发该事件,并不会直接展开节点,需要配合TreeStrategy来添加默认展开的效果。

事件对象属性如下:

  • {Object} node:待展开的节点对应的节点数据项。

collapse

当一个展开的节点被请求收起时触发。

需注意的是,Tree控件仅触发该事件,并不会直接收起节点,需要配合TreeStrategy来添加默认收起的效果。

事件对象属性如下:

  • {Object} node:待收起的节点对应的节点数据项。

扩展点

{string} itemTemplate

itemTemplate属性可以用来指定控件中每一项生成时的HTML模板,其中占位符包含:

  • ${text}:显示的文本。
  • ${id}:节点的id。

占位符的替换均在经过HTML编码后进行。

{string} getItemHTML({Object} node)

该方法用于生成控件中每一项的HTML串,默认实现是基于itemTemplate属性的字符串格式化操作。

重写该方法后,itemTemplate的功能将失效。

TreeStrategy

由于树是一个无限定深度的数据结构,Tree控件往往会面对数据量很大,需要按需加载的情形。但是控件应当负责 数据的展现 ,而不对 数据的获取和处理 负责,因此提供TreeStrategy类来对数据的获取和处理进行抽象。

TreeStrategy类是对 树的数据相头的策略 的抽象,其包含以下方法:

  • {boolean} isLeafNode({Object} node):判断一个节点是否为叶子节点,树对叶子节点的展现样式是特殊的。
  • {boolean} shouldExpand({Object} node):判断一个节点是否应当展开,当树渲染一个节点时会调用此方法。如果需要展开,则会进一步渲染其children属性。
  • {void} attachTo({Tree} tree):绑定到对应的控件上,使当前策略生效。

通常来说,一个TreeStrategyattachTo方法需注册expandcollapse事件,在事件的处理函数中获取数据,并调用expandNodecollapseNode来使树得到正确的交互。

collapse事件发生时,通常简单地调用collapseNode方法收起节点即可,需要时可以通过collapseNode方法的removeChild参数控制子节点的删除以回收内存和提高性能。

expand事件发生时,需要根据数据的获取方案来提供不同的逻辑,一般如下:

  1. 如果数据是静态的,则直接调用expandNode,不传递children参数,由控件自动查找数据并生成子节点。
  2. 如果数据是远程加载的,则:
    1. 调用indicateNodeLoading方法,使节点进入加载状态。
    2. 通过XMLHttpRequest等手段加载远程数据,在收到数据后调用expandNode并传递children参数展开节点。注意在调用expandNode时,如果提供了children参数,则Tree控件会将提供的参数作为该节点的children属性保存,因此下一次调用时不再需要传递该参数。

框架内默认提供的TreeStrategy类实现了对静态数据的处理,远程动态数据需使用方自行实现。

控件DOM结构

控件的标准DOM结构如下:

<div class="ui-tree">
    <div class="ui-tree-root ui-tree-node">
        <span class="ui-node-indicator">节点前的指示器</span>
        ${getItemHTML}(${datasource})
        <!-- if ${datasource.children} && ${datasource.children.length} > 0 -->
            <ul class="ui-tree-sub-root">
                <!-- for ${datasource.children} as ${node} -->
                <li>
                    <!-- 与上面一样 -->
                    <span class="ui-node-indicator">节点前的指示器</span>
                    ${getItemHTML}(${node})
                    <!-- if ${node.children} && ${node.children.length} > 0 -->
                        <ul class="ui-tree-sub-root">
                            <!-- 递归 -->
                        </ul>
                    <!-- /if -->
                </li>
                <!-- /for -->
            </ul>
        <!-- /if -->
    </div>
</div>

状态相关的class如下:

  • ui-tree-root-expanded:根节点处于展开状态
  • ui-tree-root-collapsed:根节点处于收起状态
  • ui-tree-node-expanded:该节点处于展开状态。
  • ui-tree-node-collapsed:该节点处于收起状态。
  • ui-tree-sub-root-expanded:该子树处于展开状态。
  • ui-tree-sub-root-collapsed:该子树处于收起状态。
  • ui-tree-node-indicator-expanded:该指示器对应的节点处于展开状态。
  • ui-tree-node-indicator-collapsed:该指示器对应的节点处于收起状态。
  • ui-tree-node-indicator-busy:该指示器对应的节点处于加载状态。

TODO

  • 每一项前带CheckBox的多选树的扩展点设计。

[评审]UI标准 - Select

Select控件标准

Select控件形如HTML中的<select>元素,是一个下拉列表,可以选择其中一项。

继承关系

- Control
    - InputControl
        - Select

主元素

Select控件的主元素可以是一个<div>或者其它类型的流式元素,也可以是一个<select>元素。

当使用<select>元素作为主元素时,控件会删除该元素,并在相同的位置放置一个<div>元素作为替换。

当满足以下2个条件时:

  • 使用<select>元素作为主元素时
  • 实例化控件时未给定datasource属性

Select控件会从主元素的<option>子元素中提取数据作为自身的datasource属性。

属性

{number} width

指定控件的宽。

{number} height

指定控件的高。

{Array.} datasource

控件的数据源,数据源中的每一个对象称为 数据项 ,一个数据项需包含:

- `{string} text`:该项的文本,与`name`二选一。
- `{string} name`:该项的文本,与`name`二选一。
- `{*} value`:该项的值,通常是字符串类型,否则在显示时将被转为字符串。

如果nametext均存在,则 name为优先

{string} emptyText

该属性指定当控件未选择任何一项时显示的文本。如果此属性不为空,则该文本将在以下情况出现:

  • 实例化控件时没给valuerawValueselectedIndex属性,导致未选中任何项。
  • 实例化或更新时,给定的valuerawValueselectedIndex不在datasource范围内,如给了一个datasource不包含的value,或者一个超出datasource.lengthselectedIndex值。
  • 修改了datasource,导致原来选中的项不在新的datasource中。

需要注意的是,如果修改datasource后,原来选定的rawValue还在新的datasource的范围内,则不会显示emptyText,而是继续选中该项。此时如果需要回到未选任何一项的状态,可以通过select.set('selectedIndex', -1);来完成。

{number} selectedIndex

选中的项在datasource中的下标,可以通过设置该属性来选中指定的项。如果该值小于0或者大于datasource.length,则会根据是否有emptyText属性来决定显示emptyText或者选中第一项。

{string} rawValue

控件的原始值,当有一项被选中时,返回该项的value属性。当未选中任何一项时,返回null

事件

change

当选中项变化时触发。

扩展点

{string} itemTemplate

itemTemplate属性可以用来指定控件中每一项生成时的HTML模板,其中占位符包含:

  • ${text}:显示的文本。
  • ${value}:数据项的值。

占位符的替换均在经过HTML编码后进行。

{function(Object): string} getItemHTML

该方法用于生成控件中每一项的HTML串,默认实现是基于itemTemplate属性的字符串格式化操作。

重写该方法后,itemTemplate的功能将失效。

控件DOM结构

控件主元素的固定结构如下:

<div>
    <span>${text}</span>
    <input name="${name}" type="hidden" value="${value}" />
</div>

Select控件会在document.body下创建一个元素,用于显示下拉层,该元素会在任意地方(除控件自身外)发生mousedown事件时隐藏。

<div class="ui-select-layer">
    <!-- for ${datasource} as ${item} -->
        ${getItemHTML}(${item})
    <!-- /for -->
</div>

默认的getItemHTML返回内容如下:

<span>${text}</span>

TODO

  • 当下拉层打开时,支持使用键盘进行选择。
  • 支持optgroup形式的分组内容。

是否能为控件增加data属性?

  为了控件更好的封闭性,控件的main元素不宜暴露给用户。

  但是用户还是会有往控件上添加某些数据的需求,如其交互关联Dom元素的Id,或某些业务信息。
  这些数据以前用户往往会加在main元素的属性中,现在这个main元素不再返回了,可能会给用户带来一些麻烦。

  如果能往控件元素上增加存储数据的data属性,将方便用户的使用,用户也将大大降低对main元素本身的依赖。将数据属性和元素属性区分开来,好处是不会干扰控件本身的属性。

  方法可以参考html5 中的 data-* 属性,并在控件中增加 setData getData。( 或者其他合适的命名)

esui开发时的几个约定问题

私有成员

私有成员与方法是下划线_开头,还是仅仅通过注释标识?

常见集合数据类型的命名

状态是一个kv,做为控件的属性,是命名为statestatesstateMap

对Array类型的,应该是+s,这点应该没有疑问。

[评审]UI标准 - MonthView

ECOM UI组件标准 - MonthView

继承层级

MonthView 
    - Control

功能描述

单月日历控件包括日期选择、月份选择、年份选择、单月浏览。

prototype.type

"MonthView"

控件主元素

控件主元素必须为div。

数据格式说明

选中的日期,为Date类型的Javascript对象。

允许选择日期区间“range”的数据格式,以一个Object表示。该Object拥有name为begin和end的成员,分别为Date类型。

{
    begin: new Date(1983, 8, 3),
    end: new Date(2046, 10, 4)
}

使用data-ui定义range时,支持字符串型,‘,’分割起始时间和结束时间。如:1983-09-03,2046-11-04。

属性

{string} dateFormat

日期显示的格式化方式。默认'yyyy-MM-dd'。

{Object} range

可选中的日期区间。可选,默认

{
    begin: new Date(1983, 8, 3),
    end: new Date(2046, 10, 4)
}

{Date} rawValue

当前选中的日期的原始数据。

如果rawValue超出range范围,则该日期有可能不会展示在日历上。

公共实例方法

{Date} getRawValue()

获取当前选中的日期。

{void} setRange( {Object} range )

设置允许选中的日期区间。

参数:

  • {Object} range: 允许选中的日期区间

{void} setRawValue( {Date} value )

通过日期格式,设置当前选中的日期。

参数:

  • {Date} value: 当前选中的日期

事件

change

当选择的日期发生改变时触发。

事件对象成员:

  • {string}type: 事件名

changeYear

当选择的年份发生改变时触发。

事件对象成员:

  • {string}type: 事件名

changeMonth

当选择的月份发生改变时触发。

事件对象成员:

  • {string}type: 事件名

[评审]UI标准 - Tab

Tab控件标准

Tab控件可以用于标签页的切换。每个标签可以对应一个容器元素,Tab控件自动控制容器的切换。

Tab标签自身会创建一个导航条,包含各个标签的元素。

继承关系

- Control
    - Tab

主元素

`Tab`控件的主元素可以是一个<div>或者其它类型的流式元素,默认使用`<nav>`元素。

导航元素

当给定主元素时,主元素下可以存在一个导航元素,导航元素 必须 是一个<ul><ol>元素,内含若干个<li>元素。

导航元素 必须data-role="navigator"属性,且 必须 是主元素的 直接子元素

一个标准的导航元素结构如下:

<ul data-role="navigator">
    <li data-for="intro">简介</li>
    <li data-for="character">人物介绍</li>
    <li data-for="content">正文</li>
    <li data-for="publish">出版信息</li>
</ul>

当实例化控件时,如果未给定tabs属性或者tabs属性为空数组,且主元素下有导航元素,则按以下规则,从导航元素的子元素中提取出tabs属性:

  • innerText作为title属性。
  • data-for属性的值作为panel属性。

需要注意的是,导航元素的子元素仅起到标记的意义,不要依赖子元素的具体样式。当控件渲染时,会把导航元素中的内容 清空重新生成 符合规范的子元素结构。

面板元素

当实例化控件时,如果同时满足以下所有条件时:

  • 未给定tabs属性或tabs属性为空数组
  • 主元素下没有导航元素
  • 主元素下有其它元素

则会将主元素下的每一个直接子元素作为一个面板,按以下规则生成tabs属性:

  • title属性作为title属性。
  • id属性作为panel属性。

随后控件渲染时,会建立导航元素并作为主元素的 第一个子元素

属性

{Array.} tabs

标签页的配置,数组中的每一项称为 配置项 ,一个配置项的结构如下:

  • {string} title:标签页的显示名称。
  • {string=} panel:对应的元素的id,当一个标签被激活时,该标签配置项的panel属性对应的DOM元素将被显示出来,其它配置项对应的DOM元素则被隐藏。

{boolean} allowClose

是否允许删除标签,默认值为false。当此属性值为true时,会在每个标签页中显示一个关闭的标记,点击后会删除该标签页。

当标签页删除时,其panel属性对应的DOM元素并不会被删除,但会被隐藏。

{number} activeIndex

指定当前激活的标签的下标。当修改此属性时,会触发activate事件。

方法

{void} activate({Object} config)

激活config属性对应的标签页。

参数

  • {Object} config:需激活配置项,使用全等(===)进行判断。

{void} add({Object} config)

在最后添加一个新标签页。

参数

  • {Object} config:添加的标签页的配置项,不去重。

{void} insert({Object} config, {number} index)

在指定位置添加一个新标签页。

参数

  • {Object} config:添加的标签页的配置项,不去重。
  • {number} index:插入的位置。如果小于0则会插在第一个,如果大于现有标签页的数量则插在最后。

{void} remove({Object} config)

移除指定的标签页。

参数

  • {Object} config:需移除的标签页配置项,使用全等(===)进行判断。

{void} removeAt({number} index)

移除指定位置的标签页。

参数

  • {number} index:需移除的位置,如果在现有标签页范围之外则无效果。

事件

activate

一个标签由未激活状态转为激活状态时触发,事件对象的属性如下:

  • {number} activeInex:激活的标签的下标。
  • {Object} tab:激活的标签的配置项。

add

添加一个标签时触发,事件对象的属性如下:

  • {number} activeInex:激活的标签的下标。
  • {Object} tab:激活的标签的配置项。

remove

移除一个标签时触发,事件对象的属性如下:

  • {number} activeInex:激活的标签的下标。
  • {Object} tab:激活的标签的配置项。

扩展点

{string} contentTemplate

contentTemplate属性可以用来指定控件中每个标签页生成时的HTML模板,其中占位符包含:

  • ${title}:显示的文本。

占位符的替换均在经过HTML编码后进行。

{function(Object, boolean): string} getContentHTML

该方法用于生成控件中每一项的HTML串,默认实现是基于contentTemplate属性的字符串格式化操作。且当第2个参数(allowClose)为true时,追回一个<span>标签。

重写该方法后,contentTemplate的功能将失效。

控件DOM结构

默认生成的结构如下:

<nav>
    <ul>
        <!-- for ${tabs} as ${item} -->
        <li>${item.title}</li>
        <!-- /for -->
    </ul>
</nav>

当开启allowClose属性时,每个标签页中的内容变为

<li>
    ${item.title}
    <span class="ui-tab-close">关闭</span>
</li>

一个被激活的标签页会增加ui-tab-active类。

[评审]UI标准 - Dialog

ECOM UI组件标准 - Dialog

继承层级

Dialog
    - Control

功能描述

对话框控件包括三个部分:标题、主体、腿部。

对话框控件内置两种模式:alert、confirm。可通过静态方法调用。

prototype.type

"Dialog"

控件主元素

控件主元素必须为div。

使用html搭建dialog时支持将title、content、foot内容预写入主元素下。并使用data-role属性指定节点角色。如:

    <div data-ui="type:Dialog;width:400;mask:true;id:staticDg">
        <h1 data-role="title">我来自静态html</h1>
        <div data-role="content">
          <p>第一行</p>
          <span data-ui="type:Button;id:springBtn;skin:spring">显示文字</span>
        </div>
    </div>

属性

{boolean=} autoPosition

是否自动定位居中。默认值为false。

{boolean} closeButton

是否具有关闭按钮。默认值为true。

{string} content

内容区域的显示内容。默认为主元素内容区域的内容。支持html。

{boolean} mask

可以通过boolean指定对话框是否具有遮挡层。

{string} title

标题的显示文字。支持html。

{number} width

对话框的宽度,单位为px。默认值为600。

{number} height

对话框的高度,单位为px。默认值为175。

{boolean} needFoot

对话框是否需要足部内容。有些对话框不需要使用foot来装载负责确认或取消操作的按钮,此时可以将此值置为false。

{string} foot

对话框的足部显示内容。默认值包含两个按钮:确定和取消。可以自定义符合自己要求的内容。

构造器静态方法

{void} alert ( {Object} args )

显示警告对话框。

args参数各项成员:

  • {string} title: 标题文字
  • {string} type: 对话框类型。其决定了显示的icon。
  • {string} content: 主体内容。
  • {number} width: 弹出框宽度。
  • {Function} onok: 点击确定按钮的行为。若返回值不为false则关闭对话框。

{string} confirm ( {Object} args )

显示询问对话框。

args参数各项成员:

  • {string} title: 标题文字
  • {string} type: 对话框类型。其决定了显示的icon。
  • {string} content: 主体内容。
  • {number} width: 弹出框宽度。
  • {Function} onok: 点击确定按钮的行为。若返回值不为false则关闭对话框。
  • {Function} oncancel: 点击取消按钮的行为。若返回值不为false则关闭对话框。

公共实例方法

{ui.Panel} getBody()

获取对话框主体的控件对象。

{ui.Panel} getFoot()

获取对话框腿部的控件对象。

{ui.Panel} getHead()

获取对话框头部的控件对象。

{void} show()

显示对话框。

{void} hide()

隐藏对话框。

{void} setTitle( {string} title )

设置对话框的标题。

参数:

  • {string} title: 对话框主体的标题,支持html

{void} setContent( {string} content )

设置对话框主体的内容。

参数:

  • {string} content: 对话框主体的内容,支持html

{void} setFoot( {string} content )

设置腿部内容。

参数:

  • {string} content: 对话框腿部的内容,支持html

{void} setWidth( {number} width )

设置对话框的宽度,单位为px。

参数:

  • {number} width: 对话框的宽度

{void} setHeight( {number} height )

设置对话框的高度,单位为px。

参数:

  • {number} height: 对话框的高度

事件

show

当对话框被显示时触发。

事件对象成员:

  • {string}type: 事件名

hide

当对话框被隐藏时触发。

事件对象成员:

  • {string}type: 事件名

[评审]UI标准 - Crumb

Crumb控件标准

Crumb控件用作面包屑导航,用于展现一系列有递进层次的链接。

继承关系

- Control
    - Crumb

主元素

Crumb控件的主元素可以是<div>或任何流式元素,默认使用<nav>

属性

{Array.} path

指定导航的路径,数组中的每一项称为 节点数据项 ,一个节点数据项可以包含以下属性:

  • {string} text:显示的文字
  • {string=} href:跳转的链接地址,可以没有该属性,如果没有该属性,则对应的节点是一个文本而非链接。

{string} separator

指定每两个节点之间的分割符,可以是一段HTML,控件不负责对其进行HTML转义。默认值为&gt;

扩展点

{string} textNodeTemplate

用来指定控件中文本节点生成时的HTML模板,其中占位符包含:

  • ${text}:显示的文本。

占位符的替换均在经过HTML编码后进行。

{string} linkNodeTemplate

用来指定控件中链接节点生成时的HTML模板,其中占位符包含:

  • ${text}:显示的文本。
  • ${href}:链接地址。

占位符的替换均在经过HTML编码后进行。

{function(Object): string} getNodeHTML

该方法用于生成控件中每一项的HTML串,默认实现是基于textNodeTemplatelinkNodeTemplate属性的字符串格式化操作。

重写该方法后,textNodeTemplatelinkNodeTemplate的功能将失效。

控件DOM结构

控件的标准DOM结构如下:

<nav class="ui-crumb">
    <ol>
    <!-- for ${path} as ${node} -->
        <li class="ui-crumb-node">
        <!-- if ${node.href} -->
            <a href="${item.href}">${item.text}</a>
        <!-- /if -->
        <!-- else -->
            <span>${item.text}</span>
        <!-- /else -->
        </li>
        <li class="ui-crumb-separator">${separator}</li>
    <!-- /for -->
    </ol>
</nav>

与状态相关的class如下:

  • ui-crumb-node-first:第一个节点。
  • ui-crumb-node-last:最后一个节点。

Tab控件标准重新修订

以下是新的Tab控件UI标准,供大家讨论

功能描述

Tab控件可以用于标签页的切换。每个标签可以对应一个容器元素,Tab控件自动控制容器的切换。

Tab标签自身会创建一个导航条,包含各个标签的元素。

继承层级

Tab -> Control

prototype.type

"Tab"

配置项及属性

以下属性可在构造函数中传入,也可在运行过程中通过setsetProperties修改。

  • {Array} tabs:指定标签页的配置。
  • {boolean} allowClose:如果此项为 true ,则每个标签上会出现一个关闭元素,点击该元素会自动删除该标签。默认值为 false
  • {number} activeIndex:指定当前激活的标签的下标。默认值为 0

其中tabs中的每一项包含以下内容:

  • {string} title:标签的标题,必须有。
  • {string=} panel:标签对应的面板元素的id,可选。

其中activeIndex的变化如下:

  • 正常情况下,添加或移除标签,activeIndex始终指向当前激活的标签的下标。
  • 如果移除激活的标签,则该标签后面的那个标签会被激活。如果该标签是最后一个标签,则在移除后的最后一个标签被激活。
  • 如果当前没有任何标签,则activeIndex的值为 -1
  • 如果修改了tabs导致activeIndex超出标签的数量,则其值将会被修正为 0

控件主元素

Tab控件的控件主元素必须是<div>元素,如果构造控件时没给定主元素,则创建一个空的<div>元素作为主元素。

HTML声明的导航条

Tab控件的主元素中可以包含一个导航条元素,该导航条元素结构必须如下所示:

<ul data-role="navigator">
    <li data-for="a">tab1</li>
    <li data-for="b">tab2</li>
    <li data-for="c">tab3</li>
</ul>

导航条元素满足以下条件:

  • 元素必须为<ul><ol>等可包含<li>元素的容器。
  • 必须有data-role="navigator"属性。
  • 子元素必须是<li>元素。

Tab控件发现导航条后,会采取以下行为:

  1. 从其下的<li>元素抽取标签选项,以data-forpanel属性,以innerTexttitle属性。
  2. 使用该选项 覆盖构造函数传入的tabs选项
  3. 删除该导航条元素,后续Tab控件会自己建立符合需求的导航条。

HTML声明子元素

当符合以下 全部 条件时:

  • 构造函数未传入tabs选项,或该选项为一个空数组。
  • 主元素中没有导航条元素。
  • 主元素下有其它子元素。

Tab控件自动将每一个子元素作为一个标签配置项,元素的title属性作为配置项的title属性,元素的id属性作为配置项的panel属性。

公共方法

{void} add({Object} config)

在最后面添加一个标签,其中config包含title和可选的panel选项。

{void} insert({Object} config, {number} index)

在指定位置添加一个标签,其中config包含title和可选的panel选项。

{void} remove({Object} config)

移除指定的标签,其中config必须是控件tabs属性中已有的对象,通过引用判断相等。

{void} removeAt({number} index)

移除指定位置的标签。

{void} activate({Object} config)

激活指定的标签,其中config必须是控件tabs属性中已有的对象,通过引用判断相等。

{void} activateAt({number} index)

激活指定位置的标签。

事件

add

当添加或插入一个标签时触发,事件对象包含:

  • tab:添加的标签的配置项。

remove

通过方法或点击关闭标签的元素从而移除一个标签时触发,事件对象包含:

  • tab:移除的标签的配置项。

activate

因任何情况导致一个标签被激活时触发,事件对象包含:

  • activeIndex:当前激活的标签的下标
  • tab:当前激活的标签配置项

注意,当因为插入或移除标签导致activeIndex变化,但当前激活的标签并没有改变时, 不会 触发该事件。

问题

  1. 是否需要setActiveIndexgetActiveIndex等方法,现在可以使用set('activeIndex')来操作
  2. 是否添加属性使得控件在导航条最后面有一个“添加”按钮,点击后触发add事件,从而实现可变的标签页功能
  3. 移除一个标签的时候,对应的面板是否有默认的操作(隐藏?删除?)

为body增加功能相关的class

有时候,一个控件的样式,可以使用CSS3来做,同时降级到低级浏览器能看。但又有时候,感觉降级的效果并不那么漂亮,所以要再加一些或者改变一下更合适。

因此,学习一下Modenizr,让esui也在body上加一些class,以方便写css吧。具体要什么样式,各控件自己控制吧,定义控件的时候加上去就好,但要求 **统一用ui-support-xxx的形式。

求讨论

helper.getGUID不能使用当前时间

很多自动化测试和工具依赖于元素的id的稳定性,如果每次进页面的guid计算是以当前时间为基准的,会导致自动化的代码找不到对应的元素。

需要修改helper.getGUID的实现,使用一个与时间无关的算法,建议设置一个初始值,然后稳定自增

controlHelper需要几个和class相关的methods

  • {Array} getPartClasses( {Control} control, {string=}part )
  • {Array} getStateClasses( {Control} control, {string}state )
  • {void} addPartClasses( {Control} control, {string=} part, {HTMLElement=} element )
  • {void} removePartClasses( {Control} control, {string=} part, {HTMLElement=} element )
  • {void} addStateClasses( {Control} control, {string} state )
  • {void} removeStateClasses( {Control} control, {string} state )

[评审]UI标准 - Form

Form控件标准

Form控件类同<form>元素,指代一个表单,基到普通的面板提供一些额外的方法。

继承关系

- Control
    - Panel
        - Form

主元素

Form控件的主元素可以是任何流式元素,推荐(默认)使用<form>

属性

{string=} action

指定表单提交的URL,暂无作用。如果主元素是<form>元素,则会设置对应的属性。

{string=} method

指定表单提交时的动作,暂无作用。如果主元素是<form>元素,则会设置对应的属性。

{string=} submitButton

可以用该属性指定一个Button控件的id,当点击该按钮时,表单将触发submit事件。

方法

{InputCollection} getInputControls({string=} name, {string=} type)

在当前Form下查询符合条件的InputControl,并返回一个集合。该方法对InputControl的判断采用以下策略:

  • 控件是InputControl或其子类的实例。
  • 控件的主元素在当前Form的元素下。
  • 没有指定name参数或者控件的name属性与name参数相等。
  • 没有指定type参数或者控件的type属性与type参数相等。
  • 控件与当前的Form使用同一个ViewContext实例。
  • 控件不作为另一个InputControl的子控件存在。

该方法返回一个InputCollection对象,InputCollection是对输入控件的集合的封装,除了正常的for循环外,还支持以下方法:

  • {void} checkAll():全选。
  • {void} uncheckAll():全部取消选择。
  • {void} checkInverse():反选。
  • {void} checkByValue(Array.<string> values):选中给定值的控件。
  • {string} getValueAsString():获取逗号分隔的值的字符串形式。

{Object} getData()

将当前Form下的InputControlrawValue收集起来并返回一个对象。对象的键为控件的name属性,值为rawValue属性。

该方法找InputControl的策略与getInputControls相同。

事件

submit

submitButton指定的按钮被点击时触发。

关于initChildren的行为

现在的initChildren,会把一段HTML中的所有元素分解成控件,且 无论其层级,统一作为自己的子控件 ,如下代码:

var html = [
    '<div data-ui="type: Panel; childName: header;">',
        '<span data-ui="type: Label; childName: close;">关闭</span>',
    '</div>',
    '<div data-ui="type: Panel; childName: foot;">',
        '<div data-ui="type: Button; childName: close;">关闭</div>',
    '</div>'
];
this.main.innerHTML = html.join('');
this.initChildren();

会产生2个childNameclose 的控件,且不会 一个在header中,一个在foot中 ,而是统一作为自己的子控件。那么只会有一个可以通过this.getChild('close')访问到。

而我希望的是,可以通过this.getChild('foot').getChild('close').on('click', closeThis)来注册事件。

由于控件的开发者是不能在里面写id的,因此一个有层级的childName会起到更好的效果。不然childName就要像id一样写成一个是 headerClose 一个是 footClose ,这让childNameid的概念之间产生混淆。

从命名上来说,我认为initChildren的意思是 初始化子控件 ,但并不代码一定 所有控件都是子控件 ,而是应该以递归的方式创建出一个树。

[评审]UI标准 - Region

ECOM UI组件标准 - Region

功能描述

Region用于地域选择。

Region支持单选和多选两种选择模式。

单选以下拉选框形式展示。

继承层级

Region
    - InputControl
        - Control

prototype.type

"Region"

控件主元素

控件主元素必须为div。

数据格式说明

多选模式下,value的字符串格式为逗号分割的地域id数组。

如果选择了某城市,则value串种除了包含该城市下的所有区域id,还包括该城市的id。

地域列表可通过静态参数配置,也可初始化时传入。地域列表是一个Array,列表中的每一项是一个可具有id、text、children属性的多重结构的数据。

[
    {
        id: "China",
        text: "**地区",
        children: [
            {
                id: "North",
                text: "华北地区",
                children: [
                    {id: "1", text: "北京"},
                    {id: "3", text: "天津"},
                    {id: "13", text: "河北"},
                    {id: "26", text: "山西"},
                    {id: "22", text: "内蒙古"}
                ]
            },
            {
                id: "NorthEast",
                text: "东北地区",
                children: [
                    {id: "21", text: "辽宁"},
                    {id: "18", text: "吉林"},
                    {id: "15", text: "黑龙江"}
                ]
            },
            {
                id: "East",
                text: "华东地区",
                children: [
                    {id: "2", text: "上海"},
                    {id: "19", text: "江苏"},
                    {id: "32", text: "浙江"},
                    {id: "9", text: "安徽"},
                    {id: "5", text: "福建"},
                    {id: "20", text: "江西"},
                    {id: "25", text: "山东"}
                ]
            },
            {
                id: "Middle",
                text: "华中地区",
                children: [
                    {id: "14", text: "河南"},
                    {id: "16", text: "湖北"},
                    {id: "17", text: "湖南"}
                ]
            },
            {
                id: "South",
                text: "华南地区",
                children: [
                    {id: "4", text: "广东"},
                    {id: "8", text: "海南"},
                    {id: "12", text: "广西"}
                ]
            },
            {
                id: "SouthWest",
                text: "西南地区",
                children: [
                    {id: "33", text: "重庆"},
                    {id: "28", text: "四川"},
                    {id: "10", text: "贵州"},
                    {id: "31", text: "云南"},
                    {id: "29", text: "西藏"}
                ]
            },
            {
                id: "NorthWest",
                text: "西北地区",
                children: [
                    {id: "27", text: "陕西"},
                    {id: "11", text: "甘肃"},
                    {id: "24", text: "青海"},
                    {id: "23", text: "宁夏"},
                    {id: "30", text: "**"}
                ]
            },
            {
                id: "Other",
                text: "其他地区",
                children: [
                    {id: "34", text: "香港"},
                    {id: "36", text: "澳门"},
                    {id: "35", text: "**"}
                ]
            }
        ]
    },
    {
        id: "Abroad",
        text: "国外",
        children: [
            {id: "7", text: "日本"},
            {id: "37", text: "其他国家"}
        ]
    }
]

属性

{Array} regionData

地域列表。默认应用构造器中的REGION_LIST设置。

{string} mode

选择模式。multi|single。默认值为multi。

{Array|String} rawValue

当前选中的地域value

当mode为‘single'时,地域id字符串格式。

当mode为‘multi'时,地域id数组格式。

公共实例方法

{Array} getRawValue()

获取选中的地域,数组格式。

{void} setRawValue( {Array} value )

通过数组格式,设置选中的地域。

参数:

  • {Array} value: 选中的地域

事件

change

当选中地域发生改变时触发。

事件对象成员:

  • {string}type: 事件名

在mixin.less中增加几个mixin

  1. .inline-block()提供兼容IE的display: inline-block;的功能
  2. .iconize(url)将元素中的文字进行大量偏移使其消失,并设置背景图片为指定的图标
  3. .reset-list()<ul><ol>paddingmarginlist-style设置为无

ViewContext id的进一步问题

  1. 通过setViewContext更换时,是否要确保main上的data-ctrl-view-context属性同步变化?
  2. 我们是否让viewContext是只读的,以避免这一类问题的出现?想不到需要改变viewContext的情况。如果是只读的,我考虑删掉setViewContext方法。

@errorrik

关于控件创建时的各来源的参数的优先级

比如说我们new一个控件的时候吧,会传一个options对象,这个对象里可能有个main属性提供HTML元素,这时就有问题了。

有一些参数,会从main上去提取,但同时options对象里又有同名的参数,这里的优先级需要讨论,典型的如下代码:

var a = document.createElement('a');
a.href = 'http://www.baidu.com';
document.body.appendChild(a);
var link = new Link({ main: a, href: 'http://www.google.com' });
link.render();

那么,这个link控件的href到底应该是百度还是Google呢?

同样的问题也出在从HTML解析出控件的时候:

<a href="http://www.baidu.com"
    data-ui-type="Link"
    data-ui-href="http://www.google.com">
    This is a link
</a>

[评审]UI标准 - RangeCalendar

ECOM UI组件标准 - RangeCalendar

功能描述

RangeCalendar用于选择日期区间。

继承层级

RangeCalendar 
    - InputControl
        - Control

prototype.type

"RangeCalendar"

控件主元素

控件主元素必须为div。

数据格式说明

RangeCalendar的value字符串格式为"yyyy-MM-dd,yyyy-MM-dd",逗号分隔的前后分别代表开始和结束日期。

对于rawValue以及可选择日期区间的数据格式,都以一个Object表示。该Object拥有name为begin和end的属性,分别为Date类型。

{
    begin: new Date(1983, 8, 3),
    end: new Date(2011, 10, 4)
}

默认属性

可以通过获取RangeCalendar类修改,效果作用于控件全局。

{string} dateFormat

日期显示的格式化方式。默认'yyyy-MM-dd'。

{string} paramDateFormat

日期参数的格式化方式。默认'yyyyMMdd'。

{string} shortCutItems

日期区间快捷选项列表。默认提供“昨天 | 最近7天 | 上周 | 本月 | 上个月 | 上个季度”六种快捷方式。每种快捷方式的定义格式如下:

            {
                name: '昨天',
                value: 0,
                getValue: function () {
                    var yesterday = new Date(this.now.getTime());
                    yesterday.setDate(yesterday.getDate() - 1);
                    return {
                        begin: yesterday,
                        end: yesterday
                    };
                }
            }

自定义属性

创建时定义,效果作用于单个控件实例。

{string} shownShortCut

需要显示的mini日历快捷方式。默认全部。格式:‘[快捷方式显示名],[快捷方式显示名]’,如'昨天,最近7天'

{Object} range

可选中的日期区间。默认

{
    begin: new Date(2011, 8, 3),
    end: new Date(2011, 10, 4)
}

{Object} rawValue

当前选中的日期区间,Object格式。

{
    begin: new Date(2011, 8, 3),
    end: new Date(2011, 10, 4)
}

{string} value

当前选中的日期区间的字符串形式,"yyyy-MM-dd hh:mm:ss,yyyy-MM-dd hh:mm:ss",逗号分隔的前后分别代表开始和结束日期。

如:'2011-08-03 00:00:00,2011-10-04 23:59:59'

{string | boolean} endlessCheck

日历可以配置为“无结束时间”即“无限”模式。endlessCheck为true时开启模式。rawValue中如果不包含结束时间,无限模式将被自动开启。

公共实例方法

{Object} getRawValue()

获取当前选中的日期区间,对象格式。

{void} setRange( {Object|string} range )

设置允许选中的日期区间。

参数:

  • {Object|string} range: 允许选中的日期区间

支持字符串型,‘,’分割起始时间和结束时间。如:1983-09-03,2046-11-04。

{void} setRawValue( {Object} value )

通过对象格式,设置当前选中的日期区间。

参数:

  • {Object} value: 当前选中的日期区间

事件

change

当选择的日期发生改变时触发。

事件对象成员:

  • {string}type: 事件名
  • {Object}rawValue: 选择的值,对象格式,参见本篇中的“数据格式说明”

关于lib的几个问题

看了下lib的函数,总结一下几个问题:

保持函数的简单性

比如bind函数,原实现是可以从scope中取fun为名字的方法的,我认为这是原tangram为了某些业务线的需求而产生的功能,我们不应该鼓励和提倡这种用法,因此我去掉了。

同样的问题在很多函数中都存在,例如:

  • format函数支持${name}{index}两种形式,是否只支持${name}就OK了
  • hasClassaddClassremoveClass支持多个class以空格分隔,确实这功能非常强大,jQuery也提供这一功能,但在一个UI控件体系中,代码都是可掌握的高质量代码,是否有必要提供这种 看似便利实则可能导致混乱和不一致性 的功能

关于继承

tangram的继承实现是没错的,但是它提供了一些额外的东西,包括:

  • 一个__type属性表示类型
  • 一个superClass属性引用基类
  • 一个extends方法生产子类

其中superClass比较广泛,可以认可(虽然我个人很反对这一做法),但是__typeextends从我的角度来看是完全没有存在的意义的,是否还需要保留。

关于深层次结构嵌套

现有的lib其实是比较混乱的,语言层面的东西(trimclone等)、字符串层面的东西(encodeHTML等)和DOM层面的东西(ghasClass等)混在一个下面,这种设计是没有问题的,我个人也很推崇这种,毕竟没多少东西,不必要再细分。

但是奇怪的是,有一个page二级“命名空间”,这货的存在意义是什么……?

题外话:如果现在lib中的东西是原封不动从tangram弄过来的话,这大概是我第一次认真地看tangram的源码,只想吐槽这代码质量是有多差……

需要增加一个WizardGuide控件

有几个系统有向导型的表单交互,即先填一个页,然后“下一步”填另一些项,再“下一步”继续填写,然后完成

这个控件和Crumb控件有点像,但有一些不同:

  • WizardGuide会把所有的路径全显示出来,并且有一个“当前激活”的
  • WizardGuide的每一项并不是一个链接,且没有点击事件

这个控件和Tab也有点像,但又有一些不同:

  • WizardGuide的各项之间是有顺序的
  • WizardGuide由于通常关联着一个很复杂的大表单,因此本身并不一定是通过多个panel的显示和隐藏来完成逻辑的

配置说明:

  • steps:说明整个向导过程中的步骤,每个step包含以下:
    • text:显示的文字
    • panel:对应的面板,可以没有
  • finishText:有些向导在最后有一个“完成”的字样,点击不起任何作用,仅视觉效果,通过这个字段可配置
  • activeIndex:当前激活的步骤的索引,如果有finishText则可能取到超出steps.length - 1的值
  • activeStep只读 ,当前激活的步骤对象,如果有finishText则可能取到undefined,此时是在最后一步上

事件说明:

  • enter:进入某一步骤时触发,如果有panel属性会控制对应panel的隐藏和显示后再触发

方法说明:

  • stepNext:去下一步,如果已经是第一步则无反应
  • stepPrevious:去上一步,如果已经是最后一步(有finishText的情况下会多出一步)则无反应

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.