侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 144672 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

mvdom-基于node.js小型以DOM为中心的MVC框架

2024-04-03 星期三 / 0 评论 / 0 点赞 / 10 阅读 / 19920 字

这个是mvdom的地址: https://github.com/mvdom/mvdom 思想理念: mvDom是一个非常轻量级的以DOM为中心的MVC库,它的特点就是以DOM作为框架的基础,而不是抛

这个是mvdom的地址:

https://github.com/mvdom/mvdom

 

思想理念:

mvDom是一个非常轻量级的以DOM为中心的MVC库,它的特点就是以DOM作为框架的基础,而不是抛开DOM,也不是排斥DOM。

  • dom是你的朋友,不要同它作斗争, 张开双臂像爱人一样紧紧抱住它
  • DOM对于一些简单可伸缩的MVC模型来说就是天堂,是一部分卓越的基础组成
  • 过度使用组件和不使用 组件一样对有害
  • 黑魔法总暗藏花费
  • 框架 来来去去,不变的只有那些语言和runtimes留了下来 
  • 框架复杂的第一要素往往是 因为它的size,从小的和简单的框架入手,往往得到更好的伸缩性

总而言之: 坦然接受DOM,从小而简单的开始,只加入那些你觉得绝对需要的,这样就可以有一个可伸缩的mini MVC框架,只添加你需要的组件,去琢磨你要运行的环境,不要刻意去使用那些巨屌,这些巨屌往往很复杂又很抽象,最好的方式还是居中注意力在那些小的all-in-one框架上。

 

特征:

  • 零依赖,超小lib(< 15kb min, < 6kb gzip) 
  • 模板不可知论者(不可知论者什么意思请参考相关哲学资料)(字符串模板非常友好,如:Handlebars)
  • 死鬼,很简单的APIS啦(e.g. d.register(name, controller), d.display(name, parent))
  • 异步生命周期管理(hookable)
  • 增强的DOM事件(i.e., d.on(el, type, selector, fn, {ns}) and off/trigger a la jquery, without wrappers)
  • 简单可伸展,且是最优化的DOM数据交换方式(d.push(el, data) & var data = d.pull(el)).
  • 最简化,但是功能强大的带topic和标签选择器的发布、订阅集成平台。(原文:
  • Minimalistic but powerful pub/sub (hub) with topic and label selectors.)

 

兼容性:

  •  在Chrome, Safari, Mobile Safari, Firefox, Edge等等主流浏览器上编写并测试通过
  • 需要权限,Array.forEach(对于IE11, https://polyfill.io是一个不错的腻子)
  • 使用js语法,与IE9+兼容(不需要从一种语言翻译为另一种语言)

 

安装: 

npm install mvdom

在源文件中的标准用法:

var mvdom = require("mvdom");

查看[#building]来了解如何手动构建发布。

 

API预览:

//-------------------------------view APIS-----------//// 注册一个视图控制器(view controller)(异步生命周期async lifecycle)mvdom.register("view name",{create,init,postDisplay,destroy}[,config]);//在这个DOM元素el里显示一个viewmvdom.display("ViewName", parentEl [, config]); // 在这些阶段注册一个hook(willCreate, didCreate, willInit, ...)mvdom.hook("willCreate", fn(view){}); mvdom.empty(el); // 将会清空一个element的所有子元素, 也会破坏相关的viewsmvdom.remove(el); // 移除这个element, 也会破坏与之相关联的views和子views// --------- /View APIs --------- //// --------- DOM Event Helpers --------- //// 在一个或者多个elements上注册一个特定事件类型的监听器mvdom.on(els, types, listener);// 在一个或多个element上注册某种类型和选择器的监听器(with event.selectTarget when selector).  mvdom.on(els, types, selector, listener); // 使用可选名称空间或可选上下文注册侦听器 (this)mvdom.on(els, types, [selector,] {ns,context});// 取消注册的监听器mvdom.off(els, type, [selector,] listener)// 取消注册所有相关联的类型和eventual选择器的监听器mvdom.off(els, type[, selector])// 取消给定命名空间‘ns’的所有监听器mvdom.off(els, {ns})// 触发一个默认给定类型的自定义事件 mvdom.trigger(els, "MyCustomEvent", {detail: "cool", cancelable: false});// --------- DOM Event Helpers --------- //// --------- DOM Query Shortcuts --------- //var nodeList = mvdom.all(el, selector); //el.querySelectorAll的快捷方式var nodeList = mvdom.all(selector); // 快捷方式:document.querySelectorAll from documentvar element = mvdom.first(el, selector); // el.querySelector的快捷方式var element = mvdom.first(selector); //document.querySelector from document的快捷方式var element = mvdom.first(el); // 找到第一个子元素 (even for fragment for browsers that do not support it)var element = mvdom.next(el[, selector]); // 找到下一个与某种可选选择器所关联的兄弟元素(快捷函数)var element = mvdom.prev(el[, selector]); // 同上,找上一个// --------- /DOM Query Shortcuts --------- //// --------- DOM Helpers --------- //// (添加孩子元素,refEl理解为父元素)Append child, refEl interpreted as parentvar newEl = mvdom.append(refEl, newEl); // 标准的refEl.appendChild(newEl)var newEl = mvdom.append(refEl, newEl, "first"); // 插入newEl 作为refEl的第一个子元素.var newEl = mvdom.append(refEl, newEl, "last"); // 对称,与上.var newEl = mvdom.append(refEl, newEl, "empty"); // 在.appendChildrefEl.appendChild(newEL)前清空 refEl// 附加兄弟姐妹, refEl 解释为兄弟姐妹var newEl = mvdom.append(refEl, newEl, "after"); // 附加newEl在refEl之后, 使用 appendChild 如果没有下一个兄弟元素var newEl = mvdom.append(refEl, newEl, "before"); // 这里对称的, refEl.parentNode.insertBefore(newEl, refEl)var frag = mvdom.frag("<div>any</div><td>html</td>"); // 创建一些html文档片段 (使用 '模板' 在可依附的文档上 )// --------- /DOM Helpers --------- //// --------- DOM Data eXchange (dx) push/pull --------- //mvdom.push(el, [selector,] data); // will set the data.property to the matching selector (default ".dx") elementsvar data = mvdom.pull(el[, selector]); // will 提取 the data from the matching elements (default selector ".dx")// 注册 自定义的 pushers / pullers (default ones are for html form elements and simple div innerHTML)mvdom.pusher(selector, pusherFun(value){this /* dom element*/}); // pusher function set a value to a matching elementmvdom.puller(selector, pullerFun(){this /* dom element*/}); // puller function returns the value from a matching element element // --------- /DOM Data eXchange (dx) push/pull --------- //// --------- Hub (pub/sub) --------- //var myHub = mvdom.hub("myHub"); // create new hubmyHub.sub(topics, [labels,] handler[, opts]); // subscribemyHub.pub(topic, [label,] data); // publishmyHub.unsub(opts.ns); // unsubscribe// --------- /Hub (pub/sub) --------- //

 

视图注册:

mvdom.register(viewName,controller [,config])

使用mvdom.register注册一个新的视图,视图控制器在视图整个生命周期都是可回应的(原生异步)。The only require view controller method, is the .create([data, config]) which is reponsible to return the HTML.

// 注册一个视图控制器mvdom.register("MainView",{    // Returns a HTML String, Document Element, or Document Fragment    // Can return a Promise that resolve in one of those three object time    // Must be one Dome Element    create: function(data, config){        return `<div class='MainView'>                  <div class=".but">${data.message}</div>                </div>`;    },     // (optional) init() will be called after the component element is created    // but before it is added to the screen (i.e. added to the parent)    init: function(data, config){        var view = this; // best practice        view.el; // this is the top parent element created for this view        // if return a Promise, the flow will wait until the promise is resolved    },     // (optional) postDisplay() will be called after the component element is added to the dom    // and in another event (used a setTimeout 0).     // Best Practice: This is a good place to add bindings that are not related to UI layout, or need to be done    // after the component is displayed    postDisplay: function(data, config){        // some non UI layout related, or actions that need to be performed after the component is displaye        // if return a promise, the mvdom.display(...).then will resolve when the return promise will be resolve.         // however, the mvdom.display(...) promise resolution will always be this view, regardless of the object or promise returns by this function.    },     // (optional) will be called when this view is deleted (from d.remove or d.empty on a parent)    // will be called after the view.el is removed from parent.    // info: {parentEl} Simple js object containing the parentEl property.    destroy: function(info){            }    // (optional) bind events to this view (support selector)    events: {        "click; .but": function(evt){            var view = this; // this is the view            console.log("click on .but", evt, view);        }    },     // (optional) bind events to the document     // (will be unbind when destroy this view by calling d.remove on this element or parents or d.empty on any parent )    docEvents: {        "click; .do-logoff": function(evt){            var view = this;                    }    },     // (optional) same as above, but on window (good to handle window resize)    winEvents: {        "resize": function(evt){            // do something when window is resize        }    },     // (optional) subscribe to a hub by hub name, topic(s), and optional label(s)    hubEvents: {        dataServiceHub: {            // subscribe on the dataServiceHub on the topic Task and any labels "create" "update" or "delete"            "Task; create, update, delete": function(data, info){                var view = this; // the this is this view object                console.log("Task has been " + info.label + "d");                // if d.hub("dataServiceHub").pub("Task","create",taskEntity)                // this will print "Task has been created"            }        },         // also support flat notation        "dataServiceHub; Task; create, update, delete": function(){           // same binding as above        }    }})

 

视图显示:

mvdom.diasplay(viewName, refEl, [data, config])

使用mvdom.display显示一个视图,例如:

// mvdom.display(viewName, refEl, data)mvdom.display("MainView", mvdom.first("body"), {message:"hello from mvdom"});// Note: mvdom.first is just a shortcut to document.querySelector

 

视图配置:

mvdom view中的可选参数config允许客制化view被操作的方式。这个参数在视图注册阶段就能设置,也能在每一次显示时覆盖.这里现在有一个简单的属性设置 .append,用来告诉我们怎么样增加新的view.el到DOM中.

  •  append:("last", "first", "before", "after").   
    • "last":  refEl 被当成父亲, append 将会使用 refEl.appendChild(view.el)
    • "first": refEl 被当成父亲, append 将会使用 .insertBefore     refEl .firstChild  . 如果没有第一个元素,那么, 将会使用正常的appendChild.
    • "empty": refEl 被当成父亲, 首先, mvdom.empty(refEl) 被调用, 接着 refEl.appendChild(view.el).
    • "before": refEl 被当成兄弟, append 将使用 .insertBefore  refEl.
    • "after": refEl 被当成兄弟, 我们 .insertBefore  efEl 的兄弟. 如果next兄弟是空, 使用 refEl.parentNode.appendChild(view.el) 把它加到最后.

         默认情况下,config.append = "last" 表示refEl是父亲并且view element 将会被添加至最后(refEl.appendChild(view.el))

            如果没有数据,但是有config,输入null,像这样:mvdom.display("MainView", parentEl, null, {append:"first"})

            在mvdom.display的上下文中,config可以是一个string,在这种情况下它将被当成append的属性.因此这上面的等价于 mvdom.display("MainView", parentEl, null, "first")

Dom Event绑定

mvdom.on([el,] eventType, [selector,] eventHander(evt){}[, opts])

给一个或多个特定事件类型或者有可选选择器的dom element绑定一个事件handler.它还支持name spacing,以及绑定时的自定义容器。(如: eventHandler的"this" )

  • el:(可选的,默认document)绑定事件的最基本document element.也可以是一个数组或者装有element的nodeList

  • eventType:(必须得)支持多个,使用 “,” (如 "webkitTransitionEnd, transitionend")

  • selector:(可选的)HTML5选择器,如果设置了,那么只有和这个选择器相关联的目标才能触发eventHandler

  • eventHandler:(必要的)事件控制器,“this”可以通过opts.ctx设置

  • opts(可选的)

    • ctx:事件控制器的容器(如:this)

    • ns:绑定的命名空间

注意: 和jquery.on相似的,除了事件对象是本地的,而选择器是纯粹的H5选择器.

例子:

<div class="item">   <div class="sub-item">text</div></div>
var baseEl = document;mvdom.on(document, "click", ".item", function(evt){  evt.target; // can be the .sub-item or .item depending where the click occurs  evt.currentTarget; // baseEl or document if not specified  evt.selectTarget; // will always be .item element (even when .sub-item get clicked)});

注意:.selectTarget只有当我们使用了selector才会设置

多事件绑定可以用 , 完成:

mvdom.on(someEl, "webkitTransitionEnd, transitionend", ...)

mvdom.off(els, [type, selector, listener][, opts])

取消绑定那些已经被mvdom.on绑定的事件

  • .off(els) 解绑所有通过mvdom.on绑定的事件
  • .off(els, type) 解绑所有通过mvdom.on绑定的type类型的事件
  • .off(els, type, selector)解绑所有通过mvdom.on绑定的type类型和固定选择器的事件
  • .off(els, type, selector, listener)取消 绑定type类型和固定选择器和这类listener的事件
  •  .off(els,{ns}) 从命名空间ns中取消绑定所有事件

Dom 数据 eXchange(push/pull):

mvdom.push 和 mvdom.pull提供一个简单可扩展的方式来从DOM sub tree提取和注入数据.

mvdom.push(el, [selector,] data);

注入数据到选择器所选择的element中,默认选择器是.dx

mvdom.pull(el[,selector]);

提取数据从符合的elements中(默认选择器是.dx)

例如

<div id="myEl">  <fieldset>    <input class="dx" name="firstName" value="Mike">    <input class="dx" name="lastName" value="Donavan">  </fieldset>  <div>    <div class="dx dx-address-street">123 Main Street</div>    <div class="dx" data-dx="address.city">San Francisco</div>  </div></div>
var myEl = mvdom.first("#myEl"); // or document.getElementById// Extract the data from the element. //   Will first do a ".dx" select, and for each element extract the property path and value from the element.var data = mvdom.pull(myEl); // data: {firstName: "Mike", lastName: "Donavan", //        address: {street: "123 Main Street", city: "San Francisco"}}// Update the DOM with some datavar updateData = {address: {street: "124 Second Street"}};mvdom.push(myEl, updateData)

 

更多信息(内部构件):

mvdom.push 和 mvdom.pull分如下四步执行:

1.首先,选择器用于选择所有DOM元素作为候选以进行值提取或注入,默认情况下,我们使用“DX”类选择器,因为这要比其他属性效率高,当然也可以提供自定义选择器.

2.其次,对于每一个候选element,mvdom从其中提取属性路径(从name属性,带dx-前缀的class,或带有html标记的data-dx)

3.Third, it looks default and registered for the appropriate pusher or puller function to inject or extract the value. Default pushers/pullers support html form elements (input, textarea, checkbox, radio) and basic innerHTML set and get, but custom ones can be registered (and will take precedence) by specifying the element matching selector.

  • d.pusher(selector, pusherFun(value){this /* dom element*/}); Register pusher function set a value to a matching dom element
  • d.puller(selector, pullerFun(){this /* dom element*/}); Register puller function returns the value from a matching dom

4.Fourth, it set the value to the appropriate property path (support nested properties as shown above)

 

Hub (pub/sub)

var d = window.mvdom; // just a best practice we have, but feel free to use mvdom as is.var myHub = d.hub("myHub");// Subcribe to a topic// sub(topic,[labels,] handlerFunction, namespace)myHub.sub("Task",function(data, info){    console.log("topic: ", info.topic, ", label: ", info.label, ", data: ", data);},{ns:"namespace"});// pub(topic, [label,] data)var newTask = {id: 123, title: "A first task"};myHub.pub("Task", "create", newTask);// will print: 'topic: Task,label: create, data: {id: 123, title: "A first task"}'// or can subscribe only to the create label (here info.label will always be "create")myHub.sub("Task", "create", function(data, info){...});// unsubscribemyHub.unsub(ns); // if no namespace provided, the ns will be the function, and used as Key// Multiple labels, with common namespacemyHub.sub("Task", "create, delete", function(data){...}, "ns1");myHub.sub("Project", function(data){...}, "ns1");// Then, to remove all subscription with ns1myHub.unsub("ns1");var obj = {name: "myObject"};myHub.sub("Project", function(data){    this.name; // "myObject"}, {ns:"ns1", ctx: obj});

 

构建:

这个库使用gulp-and-webpack-free的构建文件,要求node.js的版本在8.0.0以上。

  • npm run build 生成发布文件:dist/mvdom.js dist/mvdom.js.map 和dist/mvdom.min.js  
  • npm run watch 开发时修改src中的js文件,将会自动重编译, 便于调试.

广告 广告

评论区