摘要:如今已经实现了该控件,本文将主要介绍如何创建自定义控件以及其源代码。我们需要将目光聚焦在函数的逻辑中,该函数使用这些属性,并在其值或选择更改时自动调用以更新控件头构造函数到此为止,我们几乎已经完成了控件架构。
概述
最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户多带带选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图)。
如今WijmoJS已经实现了该控件——DropDownTree,本文将主要介绍如何创建自定义DropDownTree控件以及其源代码。
DropDownTree 控件源代码HTML
DropDownTree Control
The DropDownTree control is similar to a MultiSelect, but it hosts a TreeView in the drop-down instead of a ListBox.
The DropDownTree"s object model is also similar to the MultiSelect"s: you can listen to the checkedItemsChanged event and get/set the selection using the checkedItems property:
Drop-Down-Tree
Multi-Select
JavaScript
onload = 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("", 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控件扩展为托管其他类型的元素,同时创建自己的自定义控件。
WijmoJS:灵活高效的前端开发工具包,可快速搭建企业 Web 应用程序
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98921.html
摘要:年末促销葡萄城岁末福利火热放送中前端开发工具包年度第三个大版本已经正式发布,本次更新除了全面支持之外,还允许用户使用在前端更高效地导出智能的分组表头属性全新的主题示例以及功能增强。 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 【年末促销】葡萄城 2018 岁末福利火热放送中 WijmoJS(前端开发工具包)2018年度第三个大版本已经正...
摘要:相反,我们将专注于将添加到用编写的简单应用程序中。使用创建应用程序。示例应用程序有两个组件应用程序和。除在全球率先支持外,现已全面应用于等主流框架中。 showImg(https://segmentfault.com/img/bVbcvaQ?w=500&h=278); 概述 在本文中,我们将展示如何将WijmoJS与NPM和Webpack一起使用来创建最流行的基于JavaScript应...
摘要:全球最大的控件提供商葡萄城宣布,新一代纯前端控件发布版本,进一步增强产品功能,并支持在上的安装和发布,极大的提升了产品的易用性。葡萄城的控件和软件产品在国内外屡获殊荣,在全球被数十万家企业学校和政府机构广泛应用。 全球最大的控件提供商葡萄城宣布,新一代纯前端控件 WijmoJS 发布2018 v1 版本,进一步增强产品功能,并支持在 Npm 上的安装和发布,极大的提升了产品的易用性。 ...
阅读 2436·2021-11-15 11:36
阅读 1174·2019-08-30 15:56
阅读 2244·2019-08-30 15:53
阅读 1042·2019-08-30 15:44
阅读 653·2019-08-30 14:13
阅读 999·2019-08-30 10:58
阅读 478·2019-08-29 15:35
阅读 1294·2019-08-29 13:58