使用avalon ms-ui綁定實現基於組件的開發


讓日子過得輕松,必須讓代碼不斷往上抽象。avalon的一切就是為這個崇高的目的而迸進——操作數據即操作DOM,遠離DOM進行前端開發。javascript之所以在生命的前十年碌碌無為,都是因為開發者被這些兼容性問題搞怕了。在前十年,人們都是用着那些很底層的原生DOM方法,JS方法進行編程,效率異常低下。隨着Prototype.js, jQuery等庫的出現, 把幾乎所有能封裝都給封裝了, 我們的生活才變得美好起來。但開發企業內部管理系統,卻鮮有聽說用jQuery來搞,大家都愛用開箱即用的EXT UI框架。UI組件無疑是比jQuery這些DOM操作對象強上一截,是更高程度的抽象與封裝。但組件的制定性一般很差,比如說zTree,它就使用插件化來進行擴展。對於目前業界給出的方案,avalon是怎么做的呢?

首先,像ms-class, ms-hover, ms-on其實是在jQuery的事情,不同的是,它沒有依賴Sizzle這些動則上千行的選擇器引擎,而是通過掃描與轉換綁定屬性實現。綁定屬性會轉換一個求值函數,求值函數內部與ViewModel掛鈎,因此能即時同步,外部套着一個DOM操作函數,比如說ms-class,就是根據求值函數的返回值的真假來調用toggleClass, ms-hover則是通過綁定mouseenter, mouseleave來toggleClass, ms-checked是處理表單元素的checked屬性, ms-visible負責元素的顯示隱藏, ms-if負責將元素移出或插入DOM……短短一個綁定屬性就包含非常高密度的DOM操作,比jQuery更加write less, do more, do simple!這個園子已經有人在用我的avalon,反映都是很不錯的,當然也提了不少改進見意。

其次是組件層面,avalon與angular一樣,提供面向組件的開發,但比angular用法簡單多了。avalon參考了bootstrap這個twitter開發的UI庫,利用data-*屬性進行功能定制(當然,它還有其他形式的配置方式)。並且像bootstrap那樣,只要引入JS,在相關元素上添加ms-ui="組件名",元素節點就會轉換為UI組件,一行代碼也不用你寫。

ms-ui綁定能給我們帶來以下好處:將元素上的綁定屬性減到最少,只需添加ms-ui="組件名",或最多添加data-id="xxx", 保證對HTML最少限度的干擾。也就是說,美工與前端有了非常明確的分工。而data-id中的值則是對應ViewModel的ID,我們可以通過此ID在avalon.models[ID]中取得ViewModel,從而實現操作數據即操作DOM。

基於組件,使得我們的表現層變成由一個個“積木”堆積而成。Page規則轉向如何協調這些組件的運作,而不是盯着一個個元素節點。不過avalon已經幫你搞定了大部分了。比如說,基本URL遠程請求不同的資源,然后轉換為ViewModel,然后生成某個頁面的區域。還有消息中心(也叫事件中心),由於ViewModel本來就是用觀察者模式實現的,一個活生生的消息中心,你不需再造輪子。隨着我們項目的深入,我們積累的各種積木也就越來越多,也就是,我們的工作就越輕松,工作效率也就越來越高。

與其他UI庫不一樣的,avalon編寫UI太簡單,太方便了,這得益於它強大的雙向綁定機制。

拿avalon.tabs組件的源碼舉例吧:

(function(avalon) {
    var defaults = {
        active: 0,
        event: "click", //可以使用click, mouseover
        collapsible: false,
        bottom: false,
        removable: false
    };
    avalon.ui.tabs = function(element, id, vmodels, opts) {
        var el, tabsParent, tabs = [], tabpanels = [];
        var $element = avalon(element);
        //1,設置參數對象options = defaults + opts + $element.data()
        var options = avalon.mix({}, defaults);
        if (typeof opts === "object") {
            avalon.mix(options, opts.$json || opts);
        }
        avalon.mix(options, $element.data());
        
        $element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");

        //2, 清空它內部所有節點,並收集其內容,構建成tabs與tabpanels兩個數組
        while (el = element.firstChild) {
            if (!tablist && (el.tagName === "UL" || el.tagName === "OL")) {
                tabsParent = el;
            }
            if (el.tagName === "DIV") {
                tabpanels.push(el.innerHTML);
            }
            element.removeChild(el);
        }

        for (var i = 0; el = tabsParent.children[i++]; ) {
            tabs.push(el.innerHTML);
        }
        //3 設置動態模板
        var tablist = '<ul class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header"' +
                ' ms-class-ui-corner-all="!bottom"  ms-class-ui-corner-bottom="bottom" ms-each-tab="tabs">' +
                '<li class="ui-state-default" ' +
                ' ms-class-ui-corner-top="!bottom"' +
                ' ms-class-ui-corner-bottom="bottom"' +
                ' ms-class-ui-tabs-active="active == $index"' +
                ' ms-class-ui-state-active="active == $index"' +
                ' ms-' + options.event + '="activate"' +
                ' ms-hover="ui-state-hover"' + // float: left; margin: 0.4em 0.2em 0 0; cursor: pointer;這樣jquery ui沒有封裝進去
                ' >{{tab|html}}<span class="ui-icon ui-icon-close" style="float: left; margin: 0.4em 0.2em 0 0; cursor: pointer;" ms-if="true" ms-click="remove"></span></li></ul>';
        var panels = '<div ms-each-panel="tabpanels" ><div class="ui-tabs-panel ui-widget-content"' +
                ' ms-class-ui-corner-bottom="!bottom"' +
                ' ms-visible="active == $index" >{{panel|html}}</div></div>';
        //4 構建組建的ViewModel
        var model = avalon.define(id, function(vm) {
            vm.active = options.active;
            vm.collapsible = options.collapsible;
            vm.tabs = tabs;
            vm.tabpanels = tabpanels;
            vm.removable = options.removable;
            vm.activate = function(e) {
                e.preventDefault();
                vm.active = this.$scope.$index;
            };
            vm.remove = function(e) {
                e.preventDefault();
                var index = this.$scope.$index;
                vm.tabs.removeAt(index);
                vm.tabpanels.removeAt(index);
                avalon.nextTick(function() {
                    vm.active = 0;
                });
            };
            vm.bottom = options.bottom;
        });
        
        avalon.nextTick(function() {
            //5 當這一波掃描過來,再將組建的DOM結構插入DOM樹,並綁定ms-*屬性,然后開始掃描
            element.innerHTML = options.bottom ? panels + tablist : tablist + panels;
            element.setAttribute("ms-class-ui-tabs-collapsible", "collapsible");
            element.setAttribute("ms-class-tabs-bottom", "bottom");
            avalon.scan(element, model);
        });
        return model;
    };
})(window.avalon);

它基本分為五個步驟,首先怎么也有5個參數給你傳過來,element為綁定了ms-ui的元素節點,id為data-id的值,沒有框架為你隨機生成一個,opts,這個是ms-ui-optsName="uiName"的一個參數,opts就是框架在眾多ViewModel通過hasOwnerProperty操作分別出來的參數對象。然后第一步,就是設置配置對象,它由defaults + opts + $element.data()組成,制定性應該非常強與靈活。

第二步,將element元素的內部節點進行處理,有時它們也有綁定屬性,但我們不需要立即處理它們——掃描器是從上到下掃描,掃描過后來移除它們,因此阻止它們此時被掃描到——最好方法是將它們移出DOM樹。這個過程可能還要做些操作,隨你喜歡。

第三步,編寫UI的HTML結構。

第四步,定義ViewModel,這時它的ID為我們上面的傳參。

第五步,重新開始掃描,但這時,我們不需要整個頁面都掃描了,只從這個元素開始就行。

有關這個tabs的示例可到這里查看。

這里有兩個重點,一個是掃描。掃描總是從上到下,從左到右,想讓它不被掃描,有三個辦法,ms-skip,這幾乎是永久性的,ms-important,只有ViewModel匹配才進行此區域,最后一個是移出DOM樹。掃描不但是為了轉換綁定,還起到存儲這些關鍵節點的作用(竟然這些節點做了綁定,說明要通過JS處理,而在以前這些都是通過選擇器引擎來做,但一般的jQueryer很少自覺將它們緩存起來,每次用到時都重新選擇,不斷地遍歷DOM樹)。第二個是UI組件的ViewModel的構建,它是我們遠離DOM編程的關鍵。它擁有所有關鍵參數與方法,相當於后端的XML配置文件。當然你也可以什么也不做,defaults其實已經調試好一切了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM