使用前端開發工具包WijmoJS - 建立自定義DropDownTre

nintyuui發表於2021-09-09

概述

最近,有客戶向我們請求開發一個前端下拉控制元件,需求是顯示了一個列表,其中包含可由使用者單獨選擇的專案控制元件,該控制元件將在下拉選單中顯示多選TreeView(樹形圖)。

如今WijmoJS已經實現了該控制元件——DropDownTree,本文將主要介紹如何建立自定義DropDownTree控制元件以及其原始碼。

DropDownTree 控制元件原始碼

HTML

<div class="container">
    <h1>
        DropDownTree Control    </h1>
  <p>
    The <b>DropDownTree</b> control is similar to a 
        <b>MultiSelect</b>, but it hosts a <b>TreeView</b> 
        in the drop-down instead of a <b>ListBox</b>.</p>
  <p>
    The <b>DropDownTree</b>'s object model is also 
        similar to the <b>MultiSelect</b>'s: you can listen
        to the <b>checkedItemsChanged</b> event and get/set
        the selection using the <b>checkedItems</b> property:</p>

  <h3>
        Drop-Down-Tree    </h3>
    <input id="ddTree" placeholder="multi tree">

    <h3>
        Multi-Select    </h3>
    <input id="multiSelect" placeholder="multi select"></div>

JavaScript

  = function() {
  // create the DropDownTree
    var ddTree = new wijmo.input.DropDownTree('#ddTree', {
      displayMemberPath: 'header',
    childItemsPath: 'items',
    showCheckboxes: true,
    itemsSource: getTreeData(),
    checkedItemsChanged: function (s, e) {
        console.log('dropDownTree.checkedItemsChanged:');
      s.checkedItems.forEach(function (item, index) {
          console.log(index, item[s.displayMemberPath])
            })
        }
    });

  // create the MultiSelect
    var multiSelect = new wijmo.input.MultiSelect('#multiSelect', {
        itemsSource: 'Austria,Belgium,Chile,Denmark'.split(','),
        checkedItemsChanged: function (s, e) {
            console.log('multiSelect.checkedItemsChanged:');
            s.checkedItems.forEach(function (item, index) {
                console.log(index, item)
            })
        }
    });

  // get the tree data
    function getTreeData() {
      return [
        { header: 'Electronics', img: 'resources/electronics.png', items: [
              { header: 'Trimmers/Shavers' },
          { header: 'Tablets' },
          { header: 'Phones', img: 'resources/phones.png', items: [
              { header: 'Apple' },
            { header: 'Motorola', newItem: true },
            { header: 'Nokia' },
            { header: 'Samsung' }
                    ]},
          { header: 'Speakers', newItem: true },
          { header: 'Monitors' }
            ]},
      { header: 'Toys', img: 'resources/toys.png', items: [
          { header: 'Shopkins' },
        { header: 'Train Sets' },
        { header: 'Science Kit', newItem: true },
        { header: 'Play-Doh' },
        { header: 'Crayola' }
            ]},
      { header: 'Home', img: 'resources/home.png', items: [
          { header: 'Coffeee Maker' },
        { header: 'Breadmaker', newItem: true },
        { header: 'Solar Panel', newItem: true },
        { header: 'Work Table' },
        { header: 'Propane Grill' }
            ]}
        ];
    }
}

// DropDownTree: transpiled TypeScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var wijmo;
(function (wijmo) {
    var input;
    (function (input) {
        var DropDownTree = /** @class */ (function (_super) {
            __extends(DropDownTree, _super);
            /**
             * Initializes a new instance of the @see:DropDownTree class.
             *
             * @param element The DOM element that hosts the control, or a CSS selector for the host element (e.g. '#theCtrl').
             * @param options The JavaScript object containing initialization data for the control.
             */
            function DropDownTree(element, options) {
                var _this = _super.call(this, element) || this;
                _this._maxHdrItems = 2;
                _this._hdrFmt = wijmo.culture.MultiSelect.itemsSelected;
                /**
                 * Occurs when the value of the @see:checkedItems property changes.
                 */
                _this.checkedItemsChanged = new wijmo.Event();
                wijmo.addClass(_this.hostElement, 'wj-dropdowntree');
                // make header element read-only
                _this._tbx.readOnly = true;
                // toggle drop-down when clicking on the header element
                // (and not on a containing label element)
                _this.addEventListener(_this.inputElement, 'click', function (e) {
                    if (document.elementFromPoint(e.clientX, e.clientY) == _this.inputElement) {
                        _this.isDroppedDown = !_this.isDroppedDown;
                    }
                });
                // update header now, when the itemsSource changes, and when items are selected
                _this._updateHeader();
                var tree = _this._tree;
                tree.checkedItemsChanged.addHandler(function () {
                    _this._updateHeader();
                    _this.onCheckedItemsChanged();
                });
                tree.selectedItemChanged.addHandler(function () {
                    if (!tree.showCheckboxes) {
                        _this._updateHeader();
                        _this.onCheckedItemsChanged();
                    }
                });
                tree.loadedItems.addHandler(function () {
                    _this._updateHeader();
                });
                // close tree on enter/escape
                tree.addEventListener(tree.hostElement, 'keydown', function (e) {
                    switch (e.keyCode) {
                        case wijmo.Key.Enter:
                        case wijmo.Key.Escape:
                            _this.isDroppedDown = false;
                            break;
                    }
                });
                // initialize control options
                _this.initialize(options);
                return _this;
            }
            Object.defineProperty(DropDownTree.prototype, "treeView", {
                /**
                 * Gets the @see:TreeView control shown in the drop-down.
                 */
                get: function () {
                    return this._tree;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "itemsSource", {
                /**
                 * Gets or sets the array that contains the @see:TreeView items.
                 *
                 * @see:TreeView #see:itemsSource arrays usually have a hierarchical
                 * structure with items that contain child items. There is no fixed
                 * limit to the depth of the items.
                 *
                 * For details, see the @see:TreeView.itemsSource property.
                 */
                get: function () {
                    return this._tree.itemsSource;
                },
                set: function (value) {
                    this._tree.itemsSource = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "displayMemberPath", {
                /**
                 * Gets or sets the name of the property (or properties) to use as
                 * the visual representation of the nodes.
                 *
                 * The default value for this property is the string 'header'.
                 *
                 * For details, see the @see:TreeView.displayMemberPath property.
                 */
                get: function () {
                    return this._tree.displayMemberPath;
                },
                set: function (value) {
                    this._tree.displayMemberPath = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "childItemsPath", {
                /**
                 * Gets or sets the name of the property (or properties) that contains
                 * the child items for each node.
                 *
                 * The default value for this property is the string 'items'.
                 *
                 * For details, see the @see:TreeView.childItemsPath property.
                 */
                get: function () {
                    return this._tree.childItemsPath;
                },
                set: function (value) {
                    this._tree.childItemsPath = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "showCheckboxes", {
                /**
                 * Gets or sets a value that determines whether the @see:TreeView should
                 * add checkboxes to nodes and manage their state.
                 *
                 * For details, see the @see:TreeView.showCheckboxes property.
                 */
                get: function () {
                    return this._tree.showCheckboxes;
                },
                set: function (value) {
                    this._tree.showCheckboxes = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "checkedItems", {
                /**
                 * Gets or sets an array containing the items that are currently checked.
                 */
                get: function () {
                    var tree = this._tree;
                    if (tree.showCheckboxes) {
                        return tree.checkedItems;
                    }
                    else {
                        return tree.selectedItem ? [tree.selectedItem] : [];
                    }
                },
                set: function (value) {
                    var tree = this._tree;
                    if (tree.showCheckboxes) {
                        tree.checkedItems = wijmo.asArray(value);
                    }
                    else {
                        tree.selectedItem = wijmo.isArray(value) ? value[0] : value;
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "maxHeaderItems", {
                /**
                 * Gets or sets the maximum number of items to display on the control header.
                 *
                 * If no items are selected, the header displays the text specified by the
                 * @see:placeholder property.
                 *
                 * If the number of selected items is smaller than or equal to the value of the
                 * @see:maxHeaderItems property, the selected items are shown in the header.
                 *
                 * If the number of selected items is greater than @see:maxHeaderItems, the
                 * header displays the selected item count instead.
                 */
                get: function () {
                    return this._maxHdrItems;
                },
                set: function (value) {
                    if (this._maxHdrItems != value) {
                        this._maxHdrItems = wijmo.asNumber(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "headerFormat", {
                /**
                 * Gets or sets the format string used to create the header content
                 * when the control has more than @see:maxHeaderItems items checked.
                 *
                 * The format string may contain the '{count}' replacement string
                 * which gets replaced with the number of items currently checked.
                 * The default value for this property in the English culture is
                 * '{count:n0} items selected'.
                 */
                get: function () {
                    return this._hdrFmt;
                },
                set: function (value) {
                    if (value != this._hdrFmt) {
                        this._hdrFmt = wijmo.asString(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "headerFormatter", {
                /**
                 * Gets or sets a function that gets the HTML in the control header.
                 *
                 * By default, the control header content is determined based on the
                 * @see:placeholder, @see:maxHeaderItems, and on the current selection.
                 *
                 * You may customize the header content by specifying a function that
                 * returns a custom string based on whatever criteria your application
                 * requires.
                 */
                get: function () {
                    return this._hdrFormatter;
                },
                set: function (value) {
                    if (value != this._hdrFormatter) {
                        this._hdrFormatter = wijmo.asFunction(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            /**
             * Raises the @see:checkedItemsChanged event.
             */
            DropDownTree.prototype.onCheckedItemsChanged = function (e) {
                this.checkedItemsChanged.raise(this, e);
            };
            //** overrides
            // switch focus to the tree when the drop-down opens
            DropDownTree.prototype.onIsDroppedDownChanged = function (e) {
                if (this.containsFocus() && this.isDroppedDown) {
                    this._tree.focus();
                }
                _super.prototype.onIsDroppedDownChanged.call(this, e);
            };
            // create the drop-down element
            DropDownTree.prototype._createDropDown = function () {
                // create child TreeView control
                var lbHost = wijmo.createElement('<div style="width:100%;border:none"></div>', this._dropDown);
                this._tree = new wijmo.nav.TreeView(lbHost, {
                    showCheckboxes: true,
                });
                // let base class do its thing
                _super.prototype._createDropDown.call(this);
            };
            Object.defineProperty(DropDownTree.prototype, "isReadOnly", {
                // override since our input is always read-only
                get: function () {
                    return this._readOnly;
                },
                set: function (value) {
                    this._readOnly = wijmo.asBoolean(value);
                    wijmo.toggleClass(this.hostElement, 'wj-state-readonly', this.isReadOnly);
                },
                enumerable: true,
                configurable: true
            });
            // update header when refreshing
            DropDownTree.prototype.refresh = function (fullUpdate) {
                if (fullUpdate === void 0) { fullUpdate = true; }
                _super.prototype.refresh.call(this, fullUpdate);
                this._updateHeader();
            };
            //** implementation
            // update the value of the control header
            DropDownTree.prototype._updateHeader = function () {
                // get selected items
                var items = this.checkedItems;
                // update the header
                if (wijmo.isFunction(this._hdrFormatter)) {
                    this.inputElement.value = this._hdrFormatter();
                }
                else {
                    var hdr = '';
                    if (items.length > 0) {
                        if (items.length <= this._maxHdrItems) {
                            if (this.displayMemberPath) {
                                var binding_1 = new wijmo.Binding(this.displayMemberPath);
                                items = items.map(function (item) {
                                    return binding_1.getValue(item);
                                });
                            }
                            hdr = items.join(', ');
                        }
                        else {
                            hdr = wijmo.format(this.headerFormat, {
                                count: items.length
                            });
                        }
                    }
                    this.inputElement.value = hdr;
                }
                // update wj-state attributes
                this._updateState();
            };
            return DropDownTree;
        }(input.DropDown));
        input.DropDownTree = DropDownTree;
    })(input = wijmo.input || (wijmo.input = {}));
})(wijmo || (wijmo = {}));
//# sourceMappingURL=DropDownTree.js.map

CSS

body {margin-bottom: 24pt;

}

控制元件準備就緒後,它將如下所示:

圖片描述

圖片描述

本控制元件使用兩個獨立的WijmoJS模組:輸入和導航。所需的步驟與開發MultiSelect控制元件時所採用的步驟相同:

選擇基類

在這種場景下,我們可以將DropDown控制元件進行擴充套件,該控制元件包含使用下拉按鈕實現輸入元素所需的所有邏輯以及可用於託管任何控制元件的通用下拉選單。 DropDown控制元件用作ComboBox,InputColor和InputDate控制元件的基類。

定義物件模型

因為DropDownTree控制元件在其下拉選單中託管TreeView,所以我們決定直接從DropDownTree公開TreeView控制元件的主要屬性:

  • TreeView獲取對下拉選單中顯示的TreeView控制元件的引用。

  • ItemsSource獲取或設定對用於填充TreeView的物件陣列的引用。

  • DisplayMemberPath獲取或設定用作專案視覺化表示的屬性名稱(預設為“header”)。

  • ChildItemsPath獲取或設定包含資料來源中每個項的子項的屬性的名稱(預設為“items”)。

  • ShowCheckboxes獲取或設定一個值,該值確定控制元件是否應為每個項新增核取方塊,以便使用者可以選擇多個項(預設為true)。

我們還新增了一些額外的屬性和事件來定義當前選擇以及它在控制頭中的表示方式。這些屬性映象MultiSelect控制元件中的相應屬性:

  • CheckedItems獲取或設定包含當前所選專案的陣列。

  • CheckedItemsChanged是CheckedItems屬性值更改時發生的事件。

  • MaxHeaderItems是控制元件頭中顯示的最大選定項數。

  • 當控制元件具有超過*maxHeaderItems專案選項時,headerFormat獲取或設定用於建立標題內容的格式字串。

  • HeaderFormatter獲取或設定一個函式,該函式獲取控制元件頭中顯示的文字。 這將覆蓋maxHeaderItems和headerFormat屬性的設定。

實現控制元件

我們首先將控制元件宣告為基類的擴充套件:

namespace wijmo.input {
    export class DropDownTree extends DropDown {
    }
}

“extendsDropDown”語句確保我們的控制元件繼承基本DropDown類的所有功能,包括屬性,事件,方法和所有內部/私有成員。

建立樹檢視

接下來,我們覆蓋DropDown類中的_createDropDown方法,以建立將在下拉選單中顯示的TreeView控制元件。

除了建立TreeView之外,我們還會覆蓋onIsDroppedDownChanged方法,以便在下拉選單開啟且控制元件具有焦點時將焦點轉移到樹。 這允許使用者使用鍵盤導航樹。 他們可以透過鍵入內容來搜尋專案,透過按空格鍵來檢查專案,或使用游標鍵導航樹。

namespace wijmo.input {
    export class DropDownTree extends DropDown {        private _tree: wijmo.nav.TreeView;        // create the drop-down element
        protected _createDropDown() {            // create child TreeView control
            let lbHost = document.createElement('div');
            setCss(lbHost, {
                width: '100%',
                border: 'none'
            });            this._tree = new wijmo.nav.TreeView(lbHost, {
                showCheckboxes: true
            });
        }        // switch focus to the tree when the drop-down opens
        onIsDroppedDownChanged(e?: EventArgs) {            if (this.containsFocus() && this.isDroppedDown) {                this._tree.focus();
            }            super.onIsDroppedDownChanged(e);
        }
    }
}

公開TreeView及其屬性

下一步是新增公開TreeView及其主要屬性:

namespace wijmo.input {
    export class DropDownTree extends DropDown {        private _tree: wijmo.nav.TreeView;

        get treeView(): wijmo.nav.TreeView {            return this._tree;
        }
        get itemsSource(): any[] {            return this._tree.itemsSource;
        }
        set itemsSource(value: any[]) {            this._tree.itemsSource = value;
        }        // same for displayMemberPath, childItemsPath, 
        // and showCheckboxes

        // create the drop-down element
        protected _createDropDown() {…}
    }
}

這些屬性只是獲取或設定包含的TreeView上的相應屬性的快捷方式。 因此,它們非常簡單,我們甚至不啟用型別檢查,因為TreeView將為我們處理。

CheckedItems屬性

控制元件的主要屬性是CheckedItems,它用來表示使用者當前已獲取和自定義的陣列。 我們可以用它實現上面那樣的傳遞屬性,也可以實現多選和單選功能。比如想實現其單選功能時,我們需要檢查ShowCheckboxes屬性的值並使用樹的checkedItems或selectedItem屬性。

除了CheckedItems屬性,我們還實現了checkedItemsChanged事件及其伴隨方法onCheckedItemsChanged。 這是WijmoJS事件的標準模式。 每個事件X都有一個相應的onX方法,負責觸發事件。

namespace wijmo.input {
    export class DropDownTree extends DropDown {        private _tree: wijmo.nav.TreeView;        // TreeView pass-through properties…

        get checkedItems(): any[] {            let tree = this._tree;            if (tree.showCheckboxes) {                return tree.checkedItems;
            } else {                return tree.selectedItem
                   ? [tree.selectedItem] : [];
            }
        }        set checkedItems(value: any[]) {            let tree = this._tree;            if (tree.showCheckboxes) {
                tree.checkedItems = asArray(value);
            } else {
                tree.selectedItem = isArray(value)
                    ? value[0] : value;
            }
        }        readonly checkedItemsChanged = new Event();
        onCheckedItemsChanged(e?: EventArgs) {            this.checkedItemsChanged.raise(this, e);
        }        // create the drop-down element
        protected _createDropDown() {…}
}

請注意,即使在單個選擇的情況下,checkedItems屬性也會返回一個陣列(該陣列為空或包含單個元素)。

更新控制元件頭

這裡不會重點討論maxHeaderItems,headerFormat或headerFormatter屬性的實現方式,因為它們很簡單。我們需要將目光聚焦在_updateHeader函式的邏輯中,該函式使用這些屬性,並在其值或選擇更改時自動呼叫以更新控制元件頭:

namespace wijmo.input {
    export class DropDownTree extends DropDown {        private _tree: wijmo.nav.TreeView;        // TreeView pass-through properties…
        // checketItems property…

        private _updateHeader() {
            let items = this.checkedItems;            if (isFunction(this._hdrFormatter)) {                this.inputElement.value = this._hdrFormatter();
            } else {
                let hdr = '';                if (items.length > 0) {                    if (items.length <= this._maxHdrItems) {                        if (this.displayMemberPath) {
                            let dmp = this.displayMemberPath,
                                binding = new Binding(dmp);
                            items = items.map((item) => {                                return binding.getValue(item);
                            });
                        }
                        hdr = items.join(', ');
                    } else {
                        hdr = format(this.headerFormat, {
                            count: items.length
                        });
                    }
                }                this.inputElement.value = hdr;
            }
        }        // create the drop-down element
        protected _createDropDown() {…}
    }
}

建構函式

到此為止,我們幾乎已經完成了控制元件架構。最後一步是實現建構函式,該建構函式將部件與事件偵聽器連線,並呼叫initialize方法以使用options引數中的使用者提供的值初始化屬性和事件處理程式:

namespace wijmo.input {    export class DropDownTree extends DropDown {        private _tree: wijmo.nav.TreeView;        private _readOnly: boolean;        private _maxHdrItems = 2;        private _hdrFmt = wijmo.culture.MultiSelect.itemsSelected;        private _hdrFormatter: Function;        constructor(element: HTMLElement, options?: any) {            super(element);
            addClass(this.hostElement, 'wj-dropdowntree');            // make header element read-only
            this._tbx.readOnly = true;            // update header now, when the itemsSource changes, 
            // and when items are selected
            this._updateHeader();            let tree = this._tree;
            tree.checkedItemsChanged.addHandler(() => {                this._updateHeader();                this.onCheckedItemsChanged();
            });
            tree.selectedItemChanged.addHandler(() => {                if (!tree.showCheckboxes) {                    this._updateHeader();                    this.onCheckedItemsChanged();
                }
            });
            tree.loadedItems.addHandler(() => {                this._updateHeader();
            });            // initialize control options
            this.initialize(options);
        }        // TreeView pass-through properties…
        // checketItems property…
        // _updateHeader implementation…
        // _createDropDown implementation…
    }
}

測試控制元件

現在控制元件已準備好,我們可以測試它,並檢查它是否按照我們想要的方式執行。

執行DropDownTree 控制元件原始碼,單擊下拉按鈕以開啟TreeView。 開啟後,單擊幾個專案以選擇它們,並注意控制元件頭的更新方式:

圖片描述

我們由衷希望DropDownTree控制元件對您產生幫助。更重要的是,我們希望您現在可以放心地將DropDown控制元件擴充套件為託管其他型別的元素,同時建立自己的自定義控制元件。



圖片描述


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3209/viewspace-2816361/,如需轉載,請註明出處,否則將追究法律責任。

相關文章