vandf
v1.3.7
Published
Open source vanilla JavaScript data-driven framework
Downloads
7
Readme
Vandf
1.总体功能描述
采用主流的javascript语言进行开发,兼容chorme、firefox等主流浏览器。
Vandf框架(The vanilla javascript data framework),由原来的D3C开关框架演变而来,采用数据驱动的底层架构,数据可以自由的将组件渲染为浏览器DOM元素构成的界面。该框架对数据没有任何约束,即不规定数据的特定结构。同一组件可以用不同结构的数据进行驱动。
组件定义方式简单便捷,符合直觉,成型组件的任意组成部分仍可根据使用者的需要方便的修改。
该框架可以方便的与纯css框架配合构建现代化的响应式组件,例如可以和tailwind css框架和bulma css框架进行配合。
该组件也可以与d3.js数据驱动的图形库进行很好的配合,可以有效降低d3.js使用的难度,使得更容易构建出用户需要的图表应用。
通过该框架定义的组件,可以通过任意组合来轻松的实现整个用户界面的构建。
Vandf框架具有如下特点:
- 易学易用
将HMTL的DOM模型的复杂性隔离起来,开发者不需要面对复杂的DOM元素操作,只需要关心业务数据本身和视图展示的数据需求,心智负担大大减小。
- 性能出色
框架只更新数据变更的DOM元素,完全响应式的渲染系统,几乎不需要手动优化,框架未采用虚拟DOM架构,没有虚拟DOM 差异比对方面的性能消耗,可以达到非常高的渲染效率。
灵活多变
框架可以在数据构建模型时按需配置界面的显示样式生成,也可以在DOM元素创建和渲染过程中,根据不同数据和状态构建外观,提供了灵活多变的自由度。
精细操控 组件中的任意部件都可以进行精细控制,在保证组件的独立完整性的基础上,仍提供了组件各个部分精细操作的通道。
2.认识Vandf
Vandf是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的数据驱动的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vandf 都可以胜任。示例展示了Vandf申明式组件定义、响应式视图模型、用户数据模型三个核心功能。
- 声明式组件定义
Vandf组件定义采用了申明式定义方式,通过定义标签tag,样式style,属性attrs,类classes等,使得我们可以简明地描述最终输出的 HTML层次关系和展现的样式。组件定义时可以绑定视图模型和数据模型,还可以通过data(items)方法初始化数据,下例中提供了三个数据,Vandf会自动创建3个计数器按钮。如下图所示:
- 组件定义
let app = Van.new('app')
Van.new('counter', app.id)
.tag('button')
.view(CounterView)
.model(CounterModel)
.supply([0,10,20])
app.attach(document.querySelector('#app'));
- 用户数据模型
用户数据模型可以通过构造函数获取组件定义阶段的初始化数据,也可以通过data(parentDatum)方法获取父组件的数据,data()方法返回一个数组,Vandf会构建与数组个数一样多的组件单元。当未定义数据模型或data()方法时,Vandf会自动将父组件的数据(datum)原封不动的向子组件传递。
class CounterModel {
constructor(data){
this.count = data;
}
supply () {return this.count}
add(index){ this.count[index] = this.count[index] + 1 }
}
- 视图模型
Vandf视图模型可以应以组件创建(create)、渲染(render)、释放(destroy)、状态(states)方法,可以非常容易地构建响应式组件。视图模型可以在创建(create)阶段注册事件进行交互,在渲染(render)阶段根据数据和状态变化自动更新组件。可以通过states接收父级组件的状态,也可以向下传递本组件的状态变化。
class CounterView {
create (node,model) {
node.on('click',(e)=>{
model.add(i);
node.update()
},'native')
}
render (node) {
node.text(d =>`The count is : ${d}`)
}
}
3.组件定义
通过外部配置实现对组件结构、样式、属性、数据等的初始值进行定义,并可以通过绑定组件视图、用户数据模型,方便的实现数据驱动的响应式组件的构建。
Van.new("label")
.tag("button")
.classes("button-label no-selected")
.styles({
background: 'white',
color: 'red',
'margin-left': '5px'
})
.attrs({value:"1234"})
.html("<b>Label</b>")
组件属性
| 属性名称 | 属性功能 | 说明 | | -------- | ---------------- | ------------------------------------ | | kids | 子组件数组 | | | name | 组件名称 | | | id | 组件id | 组件id实质是一个包含名称的路径字符串 | | views | 所有注册的视图类 | |
组件方法
| 组件方法 | 方法功能 | 说明 | | ----------------------------------- | ------------------------------------------------------------ | -------------------------------------------- | | after (name) | 在本组件后插入指定名称的兄弟组件 | 用于修改第三方组件 | | append (kid) | 添加子组件kid | kid是Van的实例 | | attach (element) | 将组件挂载到HTML Element下 | 组织挂载之后才可以实例化。 | | before (name) | 在本组件前插入指定名称的兄弟组件 | 用于修改第三方组件 | | config(initConfig) | 设定配置信息,initConfig是Object类型。 | 用于配置一些初始化信息。 | | get (name) | 按名称获取子组件 | | | index(idString) | 获取组件id为idString的子组件的索引 | | | insert (kid, index) | 在index位置插入子组件kid | kid是Van的实例 | | kid(name) | 创建并添加名称为name的子组件 | | | model (model) | 设置或返回模型,无model参数时,返回模型。 | 模型始终只有一个实例。参见model模型章节。 | | parent (name) | 获取指定名称为name的父组件 | 未传name参数时,获得上一层父组件 | | tag (tagName) | 指定Html的tag名称,默认‘div’,支持svg各类tag名称。 | 例: ‘button’,‘span’,‘p’,‘input’,‘svg'等 | | attrs(config) | 设置属性,参数config为对象 | 例:comp.attrs({'name':'China'}) | | classes(names) | 设置类型名称 | 例:comp.classes("label left-bar") | | html (text) | 设置html内容 | 例:comp.html(Label) | | select(name) | 按名称name选择子组件,不限制选择层次。 | 例:comp.select(“kidName”) | | repeat (repeatComponent: Van): Van; | 重用repeatComponent组件,渲染位置为本组件所占位置。其中repeatComponent组件已在其它位置定义。 | 例:comp.repeat(comp) | | styles(config) | 设置样式,参数config为对象 | 例:comp.styles({'color':'red'}) | | supply(initdata) | 用于初始化组件数据或传递外部数据提供方法 | intdata传递给model的构造函数 | | view (view) | 设置view类,view可以设置多个,对于第三方组件的使用和扩展特别有用。 | 例:comp.view(class CompView {}) |
4.组件封装
通过配置方式定义一个带标签的按钮,表达式清晰明确,label和button在html中的排列顺序就是定义的顺序,非常符合直觉的声明式。也可以如下方式对组件进行封装以便于重用。
class MyButton extends Van {
constructor(id) {
super(id)
this.kid('label').tag('span').html('Button')
this.kid('button').tag('button')
}
}
5.组件视图模型
组件视图模型用于注册响应事件、渲染组件、管理状态。组件视图允许多个视图组件渲染阶段也可以对内容、样式进行设置,响应数据和状态的变化。组件渲染阶段还可以通过node读取到元素位置、焦点、大小等元素的属性。
| 视图模型支持的方法 | 方法的功能 | 说明 | | ------------------------------------ | ---------------------------------------------------- | -------------------------------------------- | | construct(model, initConfig) | 视图模型的构造函数,可以传入数据模型,和初始化状态。 | | | create(node, model, states, config) | 主要用于注册事件,响应事件,对数据模型进行操作。 | 创建组件时调用create方法,该方法仅调用1次。 | | destroy(node, model, states, config) | 组件移除前释放资源,一般可以用于移除事件。 | 组件移除前调用destroy方法,该方法仅调用1次。 | | render(node, model, states, config) | 组件渲染时对样式、类、属性、文本等进行计算,配置等。 | 组件创建后以及每次更新时均调用该方法。 |
视图模型中的node是Node的实例,不要与HTML中的Node相混淆。Node的属性和API方法如下:
| Node的属性名称 | Node属性功能 | 说明 | | -------------- | ------------------------------------------------ | ---- | | componentId | 组件id | | | datum | 组件数据 | | | document | 获取html dom的document | | | first | 获取第一个兄弟Node实例 | | | model | 获取model的实例 | | | name | 获取组件名称 | | | holder | 获取有兄弟的父节点实例 | | | id | 获取Node实例的id | | | index | 获取组件在兄弟Node实例中的索引。 | | | last | 获取最后一个兄弟Node实例 | | | proxy | 获取Node实例的事件代理 | | | serial | 获取holder的索引,即获取具有兄弟的父组件的索引。 | | | sibline | 获取本实例和兄弟实例的容器 | | | tag | 获取element的tag名称。 | |
| Node的方法 | Node方法的功能 | 说明 | | ----------------------------------------- | ------------------------------------------------------------ | ------------------ | | addEventListener(type, listener, options) | 注册document的事件监听器 | | | attr(key, updateValue) | 设置element的属性,updateValue可以是值或函数。 | | | blur() | 设置失去焦点状态,需要调用node.update()后生效。 | | | call(function) | 调用方法 | | | classed(name, updateValue) | 设置element的类,value可以是值(true/false)或函数。 | | | emit(eventType, data) | 派发事件,data为通过事件携带的数据,可以是任意类型 | | | empty() | 判断node中的element是否为空。 | | | html(updateValue) | 设置element的html内容,value可以是值或函数。 | | | on(type, listener, options) | 注册node事件,其中options为true或非空时,则注册为原生事件监听,既dom的传统事件。 | 详见事件部分的内容 | | parent(name) | 获取指定名称为name的父Node,未传name时,获得上一层父Node | | | style(key, updateValue, priority) | 设置或获取element的html样式,updateValue可以是值或函数。 | | | text(updateValue) | 设置或获取element的文本内容,value可以是值或函数。 | | | toggle(name) | 触发设置或取消element指定的类 | | | update() | 刷新组件,数据或状态变更后调用该函数,重新渲染组件。 | |
注:updateValue的函数形式为:function(datum, index, number),datum为数据,index为数据datum索引,number为数据的总数量
attr、blur、focus、classed、style、text、html方法设置数据或状态后,均需要显示调用node.update()才能更新视图。
6.组件数据模型
组件数据模型用于提供、传递、转换、操作数据等工作,数据模型使用非常自由宽松,对用户约束极少,但与view模型配合,能够轻松的完成各种复杂的任务。
| 数据模型的方法 | 方法的功能 | 说明 | | --------------------------------------- | ------------------------------------------------------------ | ---- | | constructor(initdata, config) | 构造函数,initdata:组件初始化数据,config:组件合并后的配置信息。 | | | supply(parentDatum,parentStates,config) | 提供、传递、转换数据,parentDatum是父组件实例的数据,parentStates为父组件的状态,也可认为是共享状态。返回数据可以是任意数据,当返回非空数组时,则按照数组长度创建组件个数,空数组则不创建组件,其余情况则创建1个组件。 | |
7.重复组件
通过组件的repeat可以非常方便的定义反复重复的多层结构,根据数据的层次自动渲染出dom的层次结构。例如可以方便的渲染如下的结构:
1)定义组件层次
let app = Van.new('app')
let ul = app.kid('ul').tag('ul').model(UlModel)
let li = ul.kid('li').tag('li')
li.kid('text').tag('span').view(TextView)
li.kid('repeat')
.repeat(ul)
.model(RepeatModel)
app.attach(document.querySelector('#app'))
2)视图模型和数据模型
class UlModel {
supply(){ return data }
}
class TextView {
render (node, d) {
node.text(d.name)
}
}
class RepeatModel {
supply(d){
return d?.children ? d.children : []
}
}
3)准备数据
const data = [
{
name:"中国",
children:[
{
name:"江苏",
children:[
{
name:"南京",
children:[
{name:"句容"},
{name:"丹阳"},
]
},
{name:"扬州"},
]
},
{
name:"湖北",
children:[
{name:"武汉"},
{name:"黄石"},
]
},
]
}
]
8.事件模型
Vandf的事件模型包括原生事件(Raw Event)、节点事件(Node Event)、文档事件(Document Event),原生事件通过本地代理生转换成节点事件向组件传递信息。节点事件在按照组件层次逐级向上冒泡,将信息传递给父组件。文档事件(Document Event)仍以原生形态出现。
9.与纯css库结合使用
本框架与tailwind css框架、bulma css框架天然契合,通过类名即可实现无缝衔接,通过控制class名称来控制渲染的样式,而Vandf可以通过classed来的数据和索引以及node的其它属性信息来灵活控制采用什么class应该被设置或被删除。具体操作参照“设置类名”章节。
10.与d3.js结合使用
d3.js是著名的数据驱动的图形库,具有功能强大,使用灵活的特点,但也存在学习曲线陡峭的问题,组件化难度大的特点。通过与Vandf框架的结合使用,可以降低使用难度,易于实现组件化。以下是一个水平树形的构建例子。
- 引入库文件
import Van from "../core/comp";
import * as d3 from "d3";
- 定义组件
export default class HTree extends Van {
constructor(id){
super(id)
const svg = this.kid('layer').tag('svg').view(SvgView).model(SvgModel)
const tree = svg.kid('tree').tag('g').view(TreeView).model(TreeModel)
tree.kid('link').tag('path').view(LinkView).model(LinkModel)
const node = tree.kid('node').tag('g').view(NodeView).model(NodeModel)
node.kid('circle').tag('circle').view(CircleView)
node.kid('text').tag('text').view(TextView)
}
}
- 定义SVG画布的视图和模型
class SvgModel {
supply () {
return d3.hierarchy(data, (d) => d.children);
}
}
class SvgView {
constructor(_, config){
this._states = config || {
top: 20, right: 90, bottom: 30, left: 90,
width: 660, height:500
}
}
states(){
return this._states
}
create (node) {
let s = this._states;
node.attr("width", s.width)
.attr("height", s.height)
}
}
- 定义Tree的视图和模型
class TreeModel {
supply (d, s) {
let height = s.height - s.top - s.bottom;
let width = s.width - s.left - s.right;
const treeMap = d3.tree().size([height, width]);
const tree = treeMap(d);
return {nodes:tree.descendants()}
}
}
class TreeView {
constructor(model){
this.model = model
}
states (s) {
this._config = s;
}
create (node) {
let s = this._config;
node.attr("transform", "translate(" + s.left + "," + s.top + ")");
}
}
- 定义Link连线的视图和模型
class LinkModel {
supply (d) {
return d.nodes.slice(1);
}
}
class LinkView {
render (node) {
node.attr("d", (d) => {
let x = d.x, y = d.y, px = d.parent.x, py = d.parent.y;
return `M${y},${x},C${(y+py)/2},${x} ${(y+py)/2},${px} ${py},${px}`
})
.style("stroke", (d) => d.data.level)
.style('fill','none')
}
}
- 定义Node节点的视图和模型
class NodeModel {
supply (d) {
return d.nodes;
}
}
class NodeView {
render (node) {
node.attr(
"class",
(d) => "node" + (d.children ? " node--internal" : " node--leaf")
)
.attr("transform", (d) => `translate(${d.y},${d.x})`)
}
}
- 定义Circle圆节点视图
class CircleView {
create (node) {
node.on('click', e=>{
console.log(node.datum)
},true)
}
render (node) {
node.attr("r", (d) => d.data.value)
.style("stroke", (d) => d.data.type)
.style("fill", (d) => d.data.level)
.style('stroke-width','3px')
}
}
- 定义节点文本Text视图
class TextView {
render (node) {
node.attr("dy", ".35em")
.attr("x", (d) => (d.children ? (d.data.value + 5) * -1 : d.data.value + 5))
.attr("y", (d) => (d.children && d.depth !== 0 ? -(d.data.value + 5) : d.data.value-10))
.style("text-anchor", (d) => (d.children ? "end" : "start"))
.text((d) => d.data.name)
}
}
- 使用HTree组件
let app = Van.new('app')
HTree.new('htree',app.id)
.model(HtreeModel)
.states({
top: 100, right: 90, bottom: 30, left: 90,
width: 660, height:500
})
.supply(data)
app.attach(document.querySelector('#app'));
- 准备HTree数据
class HtreeModel {
constructor(data){
this._data = data;
}
supply () {
return this._data
}
}
const data = {
"name": "1",
"value": 15,
"type": "black",
"level": "yellow",
"children": [
{
"name": "1-2",
"value": 10,
"type": "grey",
"level": "red"
},
{
"name": "1-3",
"value": 10,
"type": "grey",
"level": "red",
"children": [
{
"name": "1-3-001",
"value": 7.5,
"type": "grey",
"level": "purple"
},
{
"name": "1-3-002",
"value": 7.5,
"type": "grey",
"level": "purple"
}
]
},
{
"name": "1-4",
"value": 10,
"type": "grey",
"level": "blue"
},
{
"name": "1-5",
"value": 10,
"type": "grey",
"level": "green",
"children": [
{
"name": "1-5-001",
"value": 7.5,
"type": "grey",
"level": "orange"
},
{
"name": "1-5-002",
"value": 7.5,
"type": "grey",
"level": "orange"
}
]
},
{
"name": "1-6",
"value": 10,
"type": "grey",
"level": "blue",
"children": [
{
"name": "1-6-001",
"value": 7.5,
"type": "grey",
"level": "purple"
},
{
"name": "1-6-002",
"value": 7.5,
"type": "grey",
"level": "purple"
}
]
}
]
}