当前位置: 首页 > news >正文

Ext-js4-扩展开发指南-全-

Ext.js4 扩展开发指南(全)

原文:zh.annas-archive.org/md5/a5206065e508c907085190958356327e

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

在这个现代的 JavaScript 世界中,Ext JS 提供了一大批跨浏览器实用工具、一个庞大的 UI 小部件集合、图表、数据对象存储等等。在开发应用程序时,我们通常寻找对功能和组件的最佳支持。但我们几乎总是面临框架没有我们需要的特定功能或组件的情况。幸运的是,Ext JS 有一个强大的类系统,这使得扩展现有功能或组件,或构建新的功能或组件变得容易。

在本书中,我们首先通过示例提供 Ext JS 插件和扩展的非常清晰的概念,然后介绍几个 Ext JS 库和社区提供的插件和扩展,以及几个真实、有用的 Ext JS 插件和扩展的动手开发。

本书涵盖的内容

第一章, 插件和扩展,介绍了并定义了 Ext JS 插件和扩展,它们之间的区别,并通过示例展示了如何开发插件和扩展。

第二章, Ext JS 提供的插件和扩展,介绍了 Ext JS 库中一些非常有用且受欢迎的插件和扩展。

第三章, Ext JS 社区扩展和插件,介绍了一些流行的 Ext JS 社区扩展和插件。

第四章, 带标签的旋转按钮,介绍了名为 Labeled spinner field 的 Ext JS 扩展的动手开发。本章展示了我们如何扩展 Ext.form.field.Spinner 类,并添加功能,以便此扩展可以在旋转按钮字段旁边显示一个可配置的标签,以及一些更高级的功能。

第五章, 图表下载器,介绍了 Ext JS 插件的一个动手开发,该插件将帮助将图表作为图像下载。此插件将生成一个按钮,当点击时,将执行所需的功能,以便插件容器中的图表项可以作为图像下载。

第六章, 网格搜索,介绍了 Ext JS 插件的一个动手开发,该插件将在网格内提供搜索功能。此插件最初由 Ing. Jozef Sakáloš开发,是一个非常有用且受欢迎的插件。在本章中,此插件将被重新编写以适应 Ext JS 4x 版本。

第七章, 带清除按钮的输入字段,介绍了斯蒂芬·弗里德里希(Stephen Friedrich)的 ClearButton 插件的动手开发。此插件针对显示在文本字段上方的“清除”按钮的文本组件。当点击清除按钮时,文本字段将被设置为空。

第八章,消息栏,介绍了 Ext JS 扩展的动手开发,这将是一个花哨的动画消息栏。消息栏将提供具有可配置持续时间计时器的功能来显示消息,一个关闭按钮,并且还可以自定义外观,并且可以为不同类型的运行时状态提供可选图标,例如有效或无效,或信息。

第九章,直观的多选组合框,探讨了由 Kevin Vaughan 开发的优秀 Ext JS 扩展 BoxSelect,它原本是专门开发的。这个扩展真的非常实用,并为多选提供了一个更友好的组合框,为每个选择创建易于移除的标签,还有更多功能。

您需要这本书什么

本书中的示例使用 Ext JS 4.1.3 SDK,可在 Ext JS 网站上找到www.sencha.com/products/extjs/download

本书面向的对象

本书面向熟悉 Ext JS 基础的 Web 应用程序开发者,他们希望构建自定义的 Ext JS 插件和扩展。经验丰富的 Ext JS 开发者也可以提高他们在 Ext JS 插件和扩展领域的技能。

术语约定

在这本书中,您会发现许多不同风格的文本,用于区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词如下所示:"我们将扩展 Ext.form.field.Spinner 类,这将添加功能"。

代码块设置如下:

onSpinUp : function() {this.setValue(++this.value);
},onSpinDown : function() {this.setValue(--this.value);
}

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:"我们可以在窗口中找到获取值按钮"。

注意

警告或重要注意事项如下所示。

小贴士

小贴士和技巧如下所示。

读者反馈

我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大收益的标题非常重要。

要发送给我们一般性的反馈,只需发送一封电子邮件到 <feedback@packtpub.com>,并在邮件的主题中提及书名。

如果您在某个主题领域有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。

客户支持

现在你已经是 Packt 书籍的骄傲拥有者,我们有许多事情可以帮助你从购买中获得最大收益。

下载示例代码

您可以从www.packtpub.com购买的所有 Packt 书籍的示例代码文件下载。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误

尽管我们已经尽一切努力确保内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详情来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的现有错误列表中,在“错误”部分下。任何现有错误都可以通过从www.packtpub.com/support选择您的标题来查看。

盗版

互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过<copyright@packtpub.com>与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者和提供有价值内容方面的帮助。

问题

如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>联系我们,我们将尽力解决。

第一章. 插件和扩展

本章介绍了 Ext JS 插件和扩展的定义,它们之间的区别,并最终展示了如何开发插件和扩展。

在本章中,我们将涵盖:

  • 插件是什么

  • 扩展是什么

  • 扩展和插件之间的区别,以及选择最佳选项

  • 构建 Ext JS 插件

  • 构建 Ext JS 扩展

在这个现代的 JavaScript 世界中,Ext JS 是包含大量跨浏览器实用工具、UI 小部件、图表、数据对象存储等众多功能的最佳 JavaScript 框架。

在开发应用程序时,我们通常寻找最佳功能支持和提供该功能的组件。但通常我们会遇到框架缺乏我们需要的特定功能或组件的情况。幸运的是,Ext JS 拥有一个强大的类系统,这使得扩展现有功能或组件或完全构建新的功能变得容易。

插件是什么?

Ext JS 插件是一个用于向现有组件提供额外功能的类。插件必须实现一个名为 init 的方法,该方法在组件初始化时由组件调用,并在组件的生命周期开始时作为参数传递。destroy 方法在插件的所有者组件在组件销毁时调用。我们不需要实例化插件类。插件通过使用该组件的插件配置选项插入到组件中。

插件不仅被它们附加的组件使用,还被从该组件派生的所有子类使用。我们还可以在单个组件中使用多个插件,但我们需要意识到,在单个组件中使用多个插件不应让插件相互冲突。

扩展是什么?

Ext JS 扩展是一个现有 Ext JS 类的派生类或子类,旨在允许包含额外的功能。Ext JS 扩展主要用于添加自定义功能或修改现有 Ext JS 类的行为。Ext JS 扩展可以像预配置的 Ext JS 类一样基本,基本上为现有类配置提供一组默认值。这种类型的扩展在需要的功能在多个地方重复出现的情况下非常有用。让我们假设我们有一个应用程序,其中几个 Ext JS 窗口在底部栏中都有相同的帮助按钮。因此,我们可以创建一个 Ext JS 窗口的扩展,其中我们可以添加这个帮助按钮,并可以使用这个扩展窗口而不需要为按钮提供重复的代码。优势在于我们可以轻松地在单个地方维护帮助按钮的代码,并且可以在所有地方获得更改。

扩展和插件之间的区别

Ext JS 扩展和插件用于相同的目的;它们向 Ext JS 类添加扩展功能。但它们主要在编写方式和使用原因方面有所不同。

Ext JS 扩展是扩展类或 Ext JS 类的子类。为了使用这些扩展,我们需要通过创建一个对象来实例化这些扩展。我们可以提供额外的属性、函数,甚至可以覆盖任何父成员以改变其行为。这些扩展与它们派生的类紧密耦合。Ext JS 扩展主要用于我们需要修改现有类或组件的行为,或者我们需要创建一个全新的类或组件。

Ext JS 插件也是 Ext JS 类,但它们包括init函数。为了使用插件,我们不需要直接实例化这些类;相反,我们需要在组件的插件配置选项中注册插件。添加后,选项和函数将可用于组件本身。插件与它们插入的组件松散耦合,并且可以轻松地与多个组件和派生组件交互。插件用于我们需要向组件添加功能时。由于插件必须附加到现有组件,因此像扩展那样创建一个全新的组件是没有用的。

选择最佳选项

当我们需要增强或更改现有 Ext JS 组件的功能时,我们有几种方法可以实现,每种方法都有其优点和缺点。

让我们假设我们需要开发一个具有将文本颜色更改为红色以表示文本长度超过消息分配长度的简单功能的短信文本字段;这样用户就可以看到他们正在输入多于一条消息。现在,在 Ext JS 中,这种功能可以通过三种不同的方式实现,以下章节将讨论这一点。

通过配置现有类

我们可以选择将配置应用于现有类。例如,我们可以在监听器的配置中提供一个作为配置的所需短信功能来创建一个文本字段,或者我们可以在使用on方法实例化文本字段后提供事件处理器。

这是在同一功能只在少数地方使用时最简单的选项。但是,一旦功能在多个地方或多种情况下重复出现,就可能出现代码重复。

通过创建子类或扩展

通过创建一个扩展,我们可以轻松解决上一节中讨论的问题。因此,如果我们通过扩展 Ext JS 文本字段来创建一个 SMS 文本字段的扩展,我们就可以在需要的地方使用这个扩展,也可以通过使用这个扩展来创建其他扩展。所以,这个扩展的代码是集中的,更改一个地方可以在使用这个扩展的所有地方反映出来。

但存在一个问题:当在其他 Ext JS 文本字段子类(如 Ext JS 文本区域字段)中需要相同的功能时,我们无法使用开发的 SMS 文本字段扩展来利用 SMS 功能。此外,假设有一个基类的两个子类,每个子类都提供了自己的功能,而我们想在单个类中使用这两个功能,那么在这个实现中这是不可能的。

通过创建一个插件

通过创建一个插件,我们可以最大限度地重用代码。作为一个类的插件,它可以被该类的子类使用,而且我们还有灵活性,可以在单个组件中使用多个插件。这就是为什么如果我们为 SMS 功能创建一个插件,我们可以在文本字段和文本区域字段中使用 SMS 插件。此外,我们还可以在其他类中使用其他插件,包括这个 SMS 插件。

构建 Ext JS 插件

让我们开始开发一个 Ext JS 插件。在本节中,我们将开发一个简单的 SMS 插件,针对 Ext JS 的 textareafield 组件。我们希望为 SMS 功能提供的特性是,它应该在包含字段的底部显示字符数和消息数。此外,消息文本的颜色应该改变,以便在用户超过消息允许长度时通知他们。

在以下代码中,SMS 插件类已被创建在 Ext JS 应用程序的 Examples 命名空间内:

Ext.define('Examples.plugin.Sms', {alias : 'plugin.sms',config : {perMessageLength : 160,defaultColor : '#000000',warningColor : '#ff0000'},constructor : function(cfg) {Ext.apply(this, cfg);this.callParent(arguments);},init : function(textField) {this.textField = textField;if (!textField.rendered) {textField.on('afterrender', this.handleAfterRender, this);}else {this.handleAfterRender();}},handleAfterRender : function() {this.textField.on({scope : this,change : this.handleChange});var dom = Ext.get(this.textField.bodyEl.dom);Ext.DomHelper.append(dom, {tag : 'div',cls : 'plugin-sms'});},handleChange : function(field, newValue) {if (newValue.length > this.getPerMessageLength()) {field.setFieldStyle('color:' + this.getWarningColor());}else {field.setFieldStyle('color:' + this.getDefaultColor());}this.updateMessageInfo(newValue.length);},updateMessageInfo : function(length) {var tpl = ['Characters: {length}<br/>', 'Messages:{messages}'].join('');var text = new Ext.XTemplate(tpl);var messages = parseInt(length / this.getPerMessageLength());if ((length / this.getPerMessageLength()) - messages > 0) {++messages;}Ext.get(this.getInfoPanel()).update(text.apply({length : length,messages : messages}));},getInfoPanel : function() {return this.textField.el.select('.plugin-sms');}
});

小贴士

下载示例代码

您可以从您在 www.packtpub.com 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

在前面的插件类中,你可以看到在这个类中我们定义了一个名为 "必须实现" 的函数 init。在 init 函数中,我们检查此插件附加的组件是否已渲染,然后在渲染时调用 handleAfterRender 函数。在这个函数中,提供了一段代码,当 change 事件触发 textareafield 组件时,此类的 handleChange 函数应该被执行;同时,在 handleAfterRender 函数中创建一个 HTML <div> 元素,以便显示有关字符和消息计数器的信息。handleChange 函数是计算消息长度以显示彩色警告文本并调用 updateMessageInfo 函数以更新字符长度和消息数量的处理器。

现在我们可以轻松地将以下插件添加到组件中:

{xtype : 'textareafield',plugins : ['sms']
}

此外,在将插件插入到 plugins 配置选项中时,我们可以提供配置选项来覆盖默认值,如下所示:

plugins : [Ext.create('Examples.plugin.Sms', {perMessageLength : 20,defaultColor : '#0000ff',warningColor : "#00ff00"
})]

构建 Ext JS 扩展

让我们开始开发 Ext JS 扩展。在本节中,我们将开发一个 SMS 扩展,该扩展完全满足先前开发的 SMS 插件的要求。

我们已经知道 Ext JS 扩展是现有 Ext JS 类的派生类,我们将扩展 Ext JS 的 textarea 字段,该字段便于输入多行文本并提供多个事件处理、渲染和其他功能。

以下是我们创建的 Extension 类的代码,该类位于 Ext JS 应用程序的 Examples 命名空间下的 SMS 视图中:

Ext.define('Examples.view.sms.Extension', {extend : 'Ext.form.field.TextArea',alias : 'widget.sms',config : {perMessageLength : 160,defaultColor : '#000000',warningColor : '#ff0000'},constructor : function(cfg) {Ext.apply(this, cfg);this.callParent(arguments);},afterRender : function() {this.on({scope : this,change : this.handleChange});var dom = Ext.get(this.bodyEl.dom);Ext.DomHelper.append(dom, {tag : 'div',cls : 'extension-sms'});},handleChange : function(field, newValue) {if (newValue.length > this.getPerMessageLength()) {field.setFieldStyle('color:' + this.getWarningColor());}else {field.setFieldStyle('color:' + this.getDefaultColor());}this.updateMessageInfo(newValue.length);},updateMessageInfo : function(length) {var tpl = ['Characters: {length}<br/>', 'Messages:{messages}'].join('');var text = new Ext.XTemplate(tpl);var messages = parseInt(length / this.getPerMessageLength());if ((length / this.getPerMessageLength()) - messages > 0) {++messages;}Ext.get(this.getInfoPanel()).update(text.apply({length : length,messages : messages}));},getInfoPanel : function() {return this.el.select('.extension-sms');}
});

如前述代码所示,extend 关键字用作类属性,用于扩展 Ext.form.field.TextArea 类以创建扩展类。在 afterRender 事件处理器中,我们提供了一段代码,以便当 change 事件触发 textarea 字段时,我们可以执行此类的 handleChange 函数,并在 afterRender 事件处理器中创建一个 Html <div> 元素,以便显示有关字符计数器和消息计数器的信息。并且从这一部分开始,显示警告、消息字符计数器和消息计数器的逻辑与我们在 SMS 插件中使用的是相同的。

现在我们可以轻松地创建此扩展的实例:

Ext.create('Examples.view.sms.Extension');

此外,在创建此类的实例时,我们可以提供配置选项来覆盖默认值:

Ext.create('Examples.view.sms.Extension', {perMessageLength : 20,defaultColor : '#0000ff',warningColor : "#00ff00"
});

下面的截图显示了我们已经使用了 SMS 插件和扩展:

构建 Ext JS 扩展

在上面的截图中,我们创建了一个 Ext JS 窗口,并集成了 SMS 扩展和 SMS 插件。正如我们之前讨论编写插件的好处时提到的,我们不仅可以在文本区域字段中使用 SMS 插件,还可以在文本字段中使用它。

摘要

通过本章,我们学习了插件和扩展是什么,两者之间的区别,它们提供的便利,如何使用它们,以及如何根据所需功能选择扩展或插件。在本章中,我们还开发了一个简单的 SMS 插件和 SMS 扩展。

第二章. Ext JS 提供的插件和扩展

本章介绍了 Ext JS 库中一些非常有用且流行的插件和扩展。

在本章中,我们将涵盖:

  • MultiSelect

  • ItemSelector

  • TreeViewDragDrop

  • CheckColumn

  • CellEditing

  • RowEditing

  • LiveSearchGridPanel

The MultiSelect extension

Ext.ux.form.MultiSelect 是一种表单字段类型,允许从列表中选择一个或多个项目。列表使用数据存储进行填充。如果此类的 ddReorder 属性设置为 true,则可以通过拖放方法重新排序项目。

在以下代码中,定义了一个表单面板类,其中使用了 MultiSelect 扩展作为此表单的一项:

Ext.define('Examples.view.multiselect.MultiSelectFormPanel', {extend : 'Ext.form.Panel',alias : 'widget.multiselectformpanel',requires : ['Ext.ux.form.MultiSelect'],constructor : function(config) {Ext.apply(this, {bodyPadding : 10,items : [{anchor : '100%',xtype : 'multiselect',fieldLabel : 'Multi Select',name : 'multiselect',store : Ext.create('Examples.store.DummyStore'),valueField : 'name',displayField : 'name',ddReorder : true,listeners : {change : {fn : this.getMultiSelectValue},scope : this}}]});this.callParent(arguments);},getMultiSelectValue : function() {var title = "Multiselect values",value = this.getForm().findField('multiselect').getValue();Ext.Msg.alert(title, value);}
});

在前面的代码中,您可以看到 ddReorder 选项设置为 true 以通过拖放方法重新排序项目。此外,通过使用 getMultiSelectValue 函数作为 multiselect 字段的 change 事件处理程序,可以显示一个包含 multiselect 字段已选值的消息。

在以下屏幕截图中,您可以看到我们定义的 MultiSelectFormPanel 类的结果,该类用于窗口中:

The MultiSelect extension

您可以看到我们可以选择多个值,一旦我们在列表中选择项目,多选字段的已选值就会显示为消息,multiselect 字段的已选值也会显示为消息。

该扩展的可用配置选项、属性、方法和事件在 docs.sencha.com/extjs/4.1.3/#!/api/Ext.ux.form.MultiSelect 中有文档说明。

The ItemSelector extension

ItemSelector 是一个专门的 MultiSelect 字段,它以一对的形式呈现,与 MultiSelect 字段一起;一个包含可用选项,另一个包含已选选项。中间有一组按钮,允许项目在字段之间移动并在选择中进行重新排序。此外,它们还可以通过拖放方法移动。

在以下代码中,定义了一个表单面板类,其中我们使用 ItemSelector 扩展作为此表单的一项:

Ext.define('Examples.view.itemselector.ItemSelectorFormPanel', {extend : 'Ext.form.Panel',alias : 'widget.itemselectorformpanel',requires : ['Ext.ux.form.ItemSelector'],constructor : function(config) {Ext.apply(this, {bodyPadding : 10,items : [{anchor : '100%',xtype: 'itemselector',name: 'itemselector',store : Ext.create('Examples.store.DummyStore'),valueField : 'name',displayField : 'name',fromTitle: 'Available',toTitle: 'Selected'}]});this.callParent(arguments);}
});

您可以在以下屏幕截图中看到 ItemSelector 扩展是一对 MultiSelect 字段,其中一个加载了数据存储:

The ItemSelector extension

我们可以从这个可用字段中选择值并将这些值移动到 已选 字段。我们可以使用箭头按钮或拖放来移动这些字段内的值或在这些字段之间进行重新排序。如果我们使用此 ItemSelector 扩展的 getValue 函数,它将返回 已选 字段中可用的值集合。

此扩展的文档可在 docs.sencha.com/extjs/4.1.3/#!/api/Ext.ux.form.ItemSelector 上找到,在那里您可以获取此扩展的全部可用配置选项、属性、方法和事件。

TreeViewDragDrop 插件

此插件为 TreeView 类提供了拖放功能。它创建了一个专门的 DragZone 实例,该实例知道如何从 TreeView 类中拖动,并加载传递给 DragZone 协作方法的数据对象,具有以下属性:

  • 复制: 布尔值

    它是 TreeViewcopy 属性的值,或者如果 TreeView 类配置了 allowCopy 设置为 true 并且在拖动操作开始时按下了 Ctrl 键,则为 true

  • 视图: TreeView

    它是拖动操作的源 TreeView

  • ddel: HtmlElement

    它是随着鼠标移动的拖动代理元素。

  • item: HtmlElement

    它是注册了 mousedown 事件的 TreeView 节点。

  • 记录: 数组

    它是一个表示从源 TreeView 拖动的选中数据的模型数组。

它还创建了一个专门的 Ext.dd.DropZone 实例,与其他 DropZone 类协作。这些 DropZone 类是同一 ddGroup 的成员,处理这些数据对象。将此插件添加到视图中意味着在拖放之前,客户端 TreeView 可能会触发两个新事件。

注意

注意,插件必须添加到树视图中,而不是树面板中。例如,通过使用 viewConfig

viewConfig: {plugins: { ptype: 'treeviewdragdrop' }
}

在下面的代码片段中,定义了一个树类,其中使用了 TreeViewDragDrop 插件来实现节点拖放:

Ext.define('Examples.view.treeviewdragdrop.TreeViewDragDropTree', {extend : 'Ext.tree.Panel',alias : 'widget.treeviewdragdroptree',requires : ['Examples.store.SampleTreeStore','Ext.tree.plugin.TreeViewDragDrop'],constructor : function(config) {Ext.apply(this, {border : false,store : Ext.create('Examples.store.SampleTreeStore'),viewConfig : {plugins : ['treeviewdragdrop']},            useArrows : true});this.callParent(arguments);}
});

在下面的屏幕截图中,您可以查看我们定义的 TreeViewDragDropTree 类的结果,该类用于窗口中:

TreeViewDragDrop 插件

您可以看到,当我们拖动 09/29/2006 节点时,会显示一个可见的浮动消息,表明已选择一个节点,然后我们可以轻松地将该节点拖放到其他节点中。

此插件在 docs.sencha.com/extjs/4.1.3/#!/api/Ext.tree.plugin.TreeViewDragDrop 上有很好的文档,在那里您可以获取此插件的全部可用配置选项、属性、方法和事件。

CheckColumn 扩展

Ext.ux.CheckColumnExt.grid.column.Column 的一个扩展,它会在每个列单元格中渲染一个复选框。这个复选框在点击时切换关联数据字段的真值。除了在记录数据中切换布尔值之外,这个类还会根据是否选中在 <td> 元素上添加或移除 CSS 类 x-grid-checked,以改变用于列的背景图像。

在下面的代码中,我们定义了一个网格类,在其中我们使用CheckColumn扩展为列中的每个单元格提供复选框:

Ext.define('Examples.view.checkcolumn.CheckColumnGrid', {extend : 'Ext.grid.Panel',alias : 'widget.checkcolumngrid',requires : ['Examples.store.DummyStore','Ext.ux.CheckColumn'],constructor : function(config) {Ext.apply(this, {border : false,store : Ext.create('Examples.store.DummyStore'),columns : [{header : 'Name',dataIndex : 'name',flex : 1},{header : 'Birth date',dataIndex : 'birthdate',renderer : Ext.util.Format.dateRenderer('m/d/Y')},{xtype : 'checkcolumn',header : 'Attending?',dataIndex : 'attending'}]});this.callParent(arguments); }
});

在下面的屏幕截图中,您可以查看我们定义的CheckColumnGrid类所得到的结果,该类在窗口中使用:

The CheckColumn extension

您可以看到参加?列,其中CheckColumn扩展生成了复选框,这些复选框使用存储值来确定复选框是否应该被选中。

该扩展的可用配置选项、属性、方法和事件在docs.sencha.com/extjs/4.1.3/#!/api/Ext.ux.CheckColumn中有文档说明。

CellEditing插件

Ext.grid.plugin.CellEditing插件在网格的单元格级别注入编辑功能。editor字段可以是一个字段实例或一个需要提供在columns定义中的editor配置选项内的字段配置。使用CellEditing插件,我们可以在任何时间编辑单元格。如果未为特定列指定编辑器,则该单元格不能被编辑,并且当通过鼠标或键盘激活时将被跳过。

当我们配置列使用编辑器进行单元格编辑时,我们应该选择一个合适的字段类型来匹配此编辑器字段将要编辑的数据类型。例如,要编辑单元格中的日期值,指定Ext.form.field.Date作为编辑器将是有用的。

在下面的代码中,我们定义了一个网格类,在其中我们使用CellEditing插件来编辑单元格:

Ext.define('Examples.view.cellediting.CellEditingGrid', {extend : 'Ext.grid.Panel',alias : 'widget.celleditingGrid',requires : ['Examples.store.DummyStore','Ext.grid.plugin.CellEditing', 'Ext.form.field.Date'],constructor : function(config) {Ext.apply(this, {store : Ext.create('Examples.store.DummyStore'),columns : [{header : 'Name',dataIndex : 'name',flex : 1,editor : 'textfield'},{header : 'Birth date',dataIndex : 'birthdate',renderer : Ext.util.Format.dateRenderer('m/d/Y'),flex : 1,editor : {xtype : 'datefield',allowBlank : false}}],selType : 'cellmodel',plugins : [Ext.create('Ext.grid.plugin.CellEditing', {clicksToEdit : 1})]});this.callParent(arguments); }
});

您可以在代码中看到,在columns定义中,editor配置已经提供了textfield选项来编辑名称单元格,以及datefield选项来编辑出生日期单元格。为了支持单元格编辑,指定网格应使用cellmodel选项的selType,并创建一个CellEditing插件的实例。该插件已被配置为通过设置clicksToEdit配置选项为1,在单次点击后激活每个编辑器。该值也可以设置为2,以便通过双击激活编辑器。还有一个名为triggerEvent的配置选项,它也会触发编辑,并覆盖clicksToEdit配置选项。triggerEvent选项的值可以设置为cellclickcelldblclickcellfocusrowfocus

在下面的屏幕截图中,您可以查看我们定义的CellEditingGrid类所得到的结果,该类在窗口中使用:

The CellEditing plugin

您可以看到,日期字段允许您在单元格被点击后立即从日期选择器中选择一个日期。

该插件在 docs.sencha.com/extjs/4.1.3/#!/api/Ext.grid.plugin.CellEditing 中有很好的文档,在那里你可以获取该插件的所有可配置选项、属性、方法和事件。

RowEditing 插件

Ext.grid.plugin.RowEditing 插件在 Grid 的行级别注入编辑功能。当开始编辑时,将显示一个小浮动对话框,用于适当的行。每个可编辑列将显示一个用于编辑的字段。有一个按钮可以保存或取消所有编辑更改。editor 字段可以是字段实例或字段配置,我们需要在列定义中的 editor 配置选项中提供它。如果未为特定列指定编辑器,则该列的单元格将不可编辑,单元格的值将显示。

当我们配置一个列以使用行编辑的编辑器时,我们应该选择一个合适的字段类型来匹配该编辑器字段将要编辑的数据类型。例如,要编辑单元格中的日期值,指定 Ext.form.field.Date 作为编辑器会很有用。

在下面的代码中,我们正在定义一个网格类,在其中我们使用 RowEditing 插件来编辑行:

Ext.define('Examples.view.rowediting.RowEditingGrid',{extend : 'Ext.grid.Panel',alias : 'widget.roweditingGrid',requires : ['Examples.store.DummyStore','Ext.grid.plugin.RowEditing', 'Ext.form.field.Date'],constructor : function(config) {Ext.apply(this, {store : Ext.create('Examples.store.DummyStore'),columns : [{header : 'Name',dataIndex : 'name',flex : 1,editor : 'textfield'},{header : 'Birth date',dataIndex : 'birthdate',renderer : Ext.util.Format.dateRenderer('m/d/Y'),flex : 1,editor : {xtype : 'datefield',allowBlank : false}}],selType : 'rowmodel',plugins : [Ext.create('Ext.grid.plugin.RowEditing',{clicksToEdit : 1})]});this.callParent(arguments);}
});

在代码中可以看到,在 columns 定义中,已经提供了 editor 配置,使用 textfield 选项编辑名称单元格,使用 datefield 选项编辑出生日期单元格。为了支持行编辑,指定网格应使用 rowmodel 作为 selType 配置的值。已创建 RowEditing 插件的实例,并将其配置为在单击一次后激活每个编辑器。

在下面的屏幕截图中,你可以看到我们定义的 RowEditing 网格类在窗口中的结果:

RowEditing 插件

你可以看到,一个浮动对话框正好显示在编辑行上方,其中包含提供的编辑器,包括更新取消按钮。

该插件的可配置选项、属性、方法和事件文档位于 docs.sencha.com/extjs/4.1.3/#!/api/Ext.grid.plugin.RowEditing

LiveSearchGridPanel 扩展

Ext.ux.LiveSearchGridPanel 是一个支持实时搜索的 GridPanel 类。

在下面的代码中,通过扩展 LiveSearchGridPanel 扩展定义了一个网格面板类:

Ext.define('Examples.view.livesearch.LiveSearchGrid', {extend : 'Ext.ux.LiveSearchGridPanel',alias : 'widget.livesearchgrid',requires : ['Examples.store.DummyStore'],constructor : function(config) {Ext.apply(this, { border : false,store : Ext.create('Examples.store.DummyStore'),columns : [{header : 'Name',dataIndex : 'name',flex : 1},{header : 'Birth date',dataIndex : 'birthdate',renderer : Ext.util.Format.dateRenderer('m/d/Y'),flex : 1}]});this.callParent(arguments);}
});

在下面的屏幕截图中,你可以看到我们定义的 LiveSearchGrid 类的结果,该类在窗口中使用:

LiveSearchGridPanel 扩展

您可以看到,生成了一个带有搜索输入框、上一页和下一页按钮、正则表达式大小写敏感选项的网格面板,以及用于适当消息的状态栏。此扩展GridPanel突出显示匹配文本并选择匹配文本行的第一行。然后我们还可以使用上一页和下一页按钮在这些行之间移动选择。

该扩展的文档可在docs.sencha.com/extjs/4.1.3/#!/api/Ext.ux.LiveSearchGridPanel找到,其中详细记录了该扩展的所有可用配置选项、属性、方法和事件。

摘要

Ext JS 确实是一个功能丰富的库,提供了多个现成且实用的扩展和插件。在本章中,我们介绍了一些流行的扩展和插件,并学习了如何使用它们。

在下一章中,我们将通过扩展Ext.form.field.Spinner类来实际开发一个名为 Labeled Spinner 的扩展。

第三章. Ext JS 社区扩展和插件

Ext JS 社区拥有丰富的扩展和插件集合。本章介绍了 Ext JS 社区的一些流行扩展和插件。

在本章中,我们将介绍以下内容:

  • Callout

  • SmartLegend

  • TitleChart

  • BoxSelect

  • MultiDate

  • MultiMonth

  • MultiSelect

  • TinyMCETextArea

  • FilterBar

  • DragSelector

Callout 扩展

Callout 是一个扩展类,它是一个可 CSS 样式的浮动调用容器,带有可选的箭头,由约翰·亚纳雷拉开发。它用于创建提示覆盖和交互式调用窗口/弹出窗口。

我们可以在以下屏幕截图看到一个 callout 弹出窗口:

Callout 扩展

一个 Ext.ux.callout.Callout 扩展可以轻松配置以:

  • 在各种位置显示其关联的调用箭头,包括顶部、底部、左侧、右侧、左上角、右上角、左下角、右下角、左上、左下、右上、右下

  • 将其相对于目标 Ext.ElementExt.Component 定位,当目标移动或浏览器调整大小时,它将保持相对位置

  • 当鼠标点击调用区域外时,它会自动隐藏

  • 在可配置的延迟后自动消失

  • 显示时淡入,隐藏时淡出

该扩展的实时演示可在 lab.codecatalyst.com/Ext.ux.callout.Callout 获得。此扩展受 麻省理工学院MIT)许可。此扩展的下载链接、版权详情和许可信息可在 github.com/CodeCatalyst/Ext.ux.callout.Callout 获取。

SmartLegend 扩展

SmartLegend 是一个扩展,它实现了具有更高级行为的图表图例,由亚历山大·托卡列夫开发。这个扩展类基本上与 Ext.chart.Legend 相同,除了它的一些方法被重构以提高重用性。

以下是一个使用 SmartLegend 扩展的图表的屏幕截图:

SmartLegend 扩展

以下是一些 SmartLegend 的功能:

  • 图例项的大小调整到字体大小

  • 图例项可以配置为显示任何特定文本,而不仅仅是固定的系列标题或值

  • 图例根据方向在多行或多列中绘制自身

该扩展的实时演示位于 nohuhu.org/demos/demo_smartlegend.html。此扩展受 GPLv3 许可。此扩展的下载链接、版权详情和许可信息可在 github.com/nohuhu/Ext.ux.chart.SmartLegend 获取。

TitleChart 扩展

此类是 Ext.chart.Chart 的扩展,实现了带标题的图表,由亚历山大·托卡列夫开发。通过使用此扩展,我们可以轻松配置我们的图表标题。

以下是一个使用 TitleChart 扩展的图表的屏幕截图:

The TitleChart extension

可用的配置选项包括:

  • titleLocationleftrighttopbottom。当位置为 rightleft 时,标题文本将相应旋转。

  • titleFont:图表标题的字体属性,采用 CSS 格式。

  • titlePadding:图表画布边缘与标题之间的空间,以像素为单位。

  • titleMargin:标题与实际图表区域之间的空间。

该扩展程序的实时演示位于 nohuhu.org/demos/demo_titlechart.html。本扩展程序采用 GPLv3 许可证。该扩展程序的下载链接、版权详情和许可证信息可在 github.com/nohuhu/Ext.ux.chart.TitleChart 找到。

The BoxSelect extension

Ext.ux.form.field.BoxSelect 是一个组合框,通过使用单独标记的选中项扩展了更直观的多选功能,由凯文·沃恩开发。

在以下屏幕截图中,您可以看到如何在 BoxSelect 组合框内选择多个项目:

The BoxSelect extension

在以下屏幕截图中,您可以看到如何通过模板配置选中值和下拉列表:

The BoxSelect extension

以下是一些 BoxSelect 的功能:

  • 每个选中值都有可单独移除的标记项。

  • 可定制的项目模板,除了组合框支持的可定制的下拉列表模板外。

  • 基于键盘的选中值和导航选择(左右键、ShiftCtrl + ABackspaceDelete)。

  • 当设置未知值时,从远程存储按需加载值,即设置 queryMode = 'remote'forceSelection = true

  • forceSelection = false 创建新的值记录。

  • 可配置 multiSelect = true 的组合选择列表的固定。

  • 可配置标记项的渲染(自动大小或堆叠)。

  • 由于 BoxSelect 扩展了 ComboBox,大多数(如果不是所有)的功能和配置选项应该按预期工作。

本扩展程序采用 MIT 许可证。本扩展程序的示例和参考信息可在 kveeiv.github.io/extjs-boxselect/examples/boxselect.html 找到。该扩展程序的下载链接、版权详情和许可证信息可在 github.com/kveeiv/extjs-boxselect 找到。

MultiDate 扩展

MultiDate 是一个表单字段扩展,它扩展了 Ext.form.field.Date,允许输入多个日期和日期范围,具有灵活的格式匹配和强大的下拉选择器,由亚历山大·托卡列夫开发。

以下是一个 MultiDate 字段的截图:

MultiDate 扩展

以下是一些 MultiDate 的功能:

  • 日期或日期范围的数量没有限制。

  • 分别设置范围值的输入格式、显示格式和提交格式。

  • 完全使用提供的 CSS 表格主题化。

  • 向后兼容性:可以通过设置一个选项来关闭多值输入;在这种情况下,行为类似于 stockDate 字段。

  • 支持可配置的工作周选择。

  • 在选择器中按 Space 键选择/取消选择单个日期。

  • 在选择器中 Shift + SpaceShift + 点击选择工作周。

  • 在选择器中 Ctrl + Backspace 清除选择。

  • 在选择器中 Ctrl + 点击选择自由范围:Ctrl + 点击一次设置开始日期,Ctrl + 点击再次设置结束日期并选择所有之间的日期。这也适用于多个月份/年份。

  • 在选择器中 Ctrl + Shift + 点击选择自由范围,但只包括工作日。

  • 在选择器中按 Enter 键以确认选择。

  • 在选择器中按 Esc 键取消选择。

此扩展的实时演示位于 nohuhu.org/demos/demo_uxmultidate.html。此扩展受 GPLv3 许可证保护。此扩展的下载链接、版权详情和许可证信息可在 github.com/nohuhu/Ext.ux.form.field.MultiDate 获取。

MultiMonth 扩展

MultiMonth 是一个允许输入月份范围的表单字段扩展,具有灵活的格式匹配和自定义的下拉选择器,由亚历山大·托卡列夫开发。

以下是一个 MultiMonth 字段的截图:

多个月份扩展

以下是一些 MultiMonth 的功能:

  • 允许输入开始和结束月份。

  • 分别设置输入格式、显示格式和提交格式。

  • 完全使用提供的 CSS 表格主题化。

  • 切换行为:将 multiValue 属性设置为 false,字段将只允许输入单个月份。

此扩展的实时演示位于 nohuhu.org/demos/demo_uxmultimonth.html。此扩展受 GPLv3 许可证保护。此扩展的下载链接、版权详情和许可证信息可在 github.com/nohuhu/Ext.ux.form.field.MultiDate 获取。

MultiSelect 扩展

MultiSelect 是一个用于输入任意类型值及其值范围的表单字段,具有包含实时搜索、智能匹配、视觉选择等功能的选择器,由亚历山大·托卡列夫开发。

以下是一个 MultiSelect 字段的截图:

多选扩展

此扩展程序实现了一个表单字段,允许输入任意类型的多个值和值范围,并带有提供实时搜索和可视项目选择的下拉选择器。

以下是 MultiSelect 的一些功能:

  • 项目数量和项目范围没有限制。

  • 单个和范围值的输入格式、显示格式和提交格式有单独的设置。

  • 提供 CSS 样式的全主题表单字段。

  • 两种操作模式:多值和单值。

  • 带有实时搜索和智能匹配的下拉选择器。

  • 优化用于大数据集。

  • 接受预配置的存储库,其中包含要显示的值列表。

  • 支持延迟存储填充;只有当选择器被激活时才会加载数据。

  • 可配置列定义;列的数量和宽度没有任意限制。

  • 在选择器中按下 Enter 键或点击加号图标以选择单个项目。

  • 按下 Tab 键选择一个列表,然后按下 Enter 键,或者点击减号图标取消选择一个项目。

  • 在选择器中按下 Ctrl + Enter 键确认选择。

  • 在选择器中按下 Ctrl + Backspace 键清除选择。

  • 在选择器中按下 Esc 键取消选择。

此扩展程序的实时演示位于 nohuhu.org/demos/demo_uxmultiselect.html。此扩展程序受 GPLv3 许可协议保护。此扩展程序的下载链接、版权详情和许可信息可在 github.com/nohuhu/Ext.ux.form.field.MultiSelect 找到。

TinyMCETextArea 扩展

TinyMCETextArea 是一个集成了 TinyMCE WYSIWYG 编辑器的 Ext JS 文本区域,由 Oleg Schildt 开发。

以下是一个 TinyMCETextArea 文本区域的截图:

TinyMCETextArea 扩展

此扩展程序的实时演示位于 www.point-constructor.com/tinyta_demo。此扩展程序受 GPLv3 许可协议保护。您可以在 market.sencha.com/extensions/ext-ux-form-tinymcetextarea/versions/2.6/download 下载此扩展程序。

FilterBar 插件

FilterBar 是一个插件,由 ldonofrio 开发,它可以在网格标题上启用过滤器。

以下是一个使用 FilterBar 插件的网格的截图:

FilterBar 插件

以下是 FilterBar 插件的一些功能:

  • 允许预配置的过滤器类型,并基于存储字段的数据类型自动设置。

  • 条件运算符选择以实现更好的查询。

  • 自动生成组合和列表过滤器的存储库(本地收集或服务器在 autoStoresRemoteProperty 响应属性中)。

  • 支持在操作列或新生成的小列中渲染的 clearAllshowHide 按钮。

此插件采用 GPLv3 许可。此插件的下载链接可在market.sencha.com/extensions/ext-ux-grid-filterbar/versions/218/download找到。

The DragSelector plugin

DragSelector是一个通过在行上拖动鼠标来帮助选择网格行的插件,由 Harald Hanek 开发。原始代码的初始开发者是 Claudio Walser。此插件非常有用,因为它可以非常快速地选择多行。它支持以下方式选择网格行:

  • 通过在行上拖动来选择

  • 按住Ctrl键并通过拖动来选择,同时保持现有的选择

  • 按住Ctrl键并通过拖动鼠标交叉现有选择来取消现有选择。

在下面的屏幕截图中,我们可以看到DragSelector插件的实际应用:

The DragSelector plugin

此插件的实时演示位于harrydeluxe.github.io/extjs-ux/example/grid/dragselector.html。此插件采用 LGPLv3 许可。您可以在github.com/harrydeluxe/extjs-ux/blob/master/ux/grid/plugin/DragSelector.js下载此插件。

摘要

在本章中,我们介绍了一些流行的 Ext JS 社区扩展和插件。Ext JS 社区中有许多扩展和插件,其中我们可能会找到我们需要的;而且,社区扩展和插件每天都在增长。

第四章:带标签的旋转按钮

在本章中,我们将开发一个名为带标签的旋转按钮的 Ext JS 扩展。为了开发这个扩展,我们将扩展Ext.form.field.Spinner类,这将添加一个功能,在旋转按钮字段内显示可配置的标签,以及一些更高级的功能。

在本章中,我们将涵盖:

  • 功能需求

  • 规划和编写带标签的旋转按钮代码

功能需求

我们希望开发一个用于数值的字段,该字段将提供具有上下旋转按钮的功能,以便增加或减少数值。此外,用户还可以在字段内编辑数值。还有一个配置选项,可以在字段内数值旁边显示用户定义的标签作为单位名称。还将提供选项从该字段获取值,这将仅是数值,也可以是包括单位名称的数值,正如它在字段内显示的那样。

规划和编写带标签的旋转按钮代码

为了满足功能需求,我们将通过扩展 Ext JS 的Ext.form.field.Spinner类来创建一个扩展类,从而获取我们提供所需的大部分功能。我们需要实现Ext.form.field.Spinner类的onSpinUponSpinDown函数,以处理旋转按钮点击事件,提供我们的逻辑来增加或减少数值。默认情况下,按下上下箭头键也会触发onSpinUponSpinDown方法。现在,让我们开始编写代码:

Ext.define('Examples.ux.LabeledSpinner', {extend : 'Ext.form.field.Spinner',alias : 'widget.labeledspinner',onSpinUp : function() {this.setValue(++this.value);},onSpinDown : function() {this.setValue(--this.value);}
});

现在我们有一个可以增加或减少其值的可工作扩展。以下是使用此扩展的屏幕截图:

规划并编写带标签的旋转按钮

你可以看到我们现在有一个带有上下旋转按钮的字段,我们可以通过它增加或减少数值 1。

现在,我们将添加一个功能,该字段可以显示单位标签文本,直接位于数值旁边。我们将定义setValue函数,通过它可以设置包括标签单位的值。我们还将向此类添加一些config属性,以便我们可以设置所需的值:

Ext.define('Examples.ux.LabeledSpinner', {extend : 'Ext.form.field.Spinner',alias : 'widget.labeledspinner',config : {labelText : ",minValue: 0,value: 0},onSpinUp : function() {var value = parseFloat(this.getValue().split(' ')[0]);this.setValue(++value);},onSpinDown : function() {var value = parseFloat(this.getValue().split(' ')[0]);this.setValue(--value);},setValue : function(value) {value = (value ||this.minValue) + ' ' +this.getLabelText();this.callParent(arguments);}
}); 

在下面的屏幕截图中,我们可以看到标签直接位于字段内的数值旁边:

规划并编写带标签的旋转按钮

在代码中,你可以发现我们提供了一些config选项,添加了setValue函数,并对onSpinUponSpinDown函数进行了一些修改。

现在,我们将定义getValue函数,以便我们可以从该字段获取数值,并定义getLabeledValue函数,该函数将返回数值,包括单位标签,正如它在字段内显示的那样。我们还将定义onBlur处理程序来检查并修复任何错误输入,并对现有代码进行一些更改。以下是我们的扩展的完整代码:

Ext.define('Examples.ux.LabeledSpinner', {extend : 'Ext.form.field.Spinner',alias : 'widget.labeledspinner',config : {labelText : '',minValue : 0,maxValue : Number.MAX_VALUE,step : 1,value : 0},onBlur : function() {if (isNaN(this.getValue())) {this.setValue(this.getLabeledValue(this.getMinValue()));}else{this.setValue(this.getLabeledValue());}},onSpinUp : function() {var val = this.getValue() || this.getMinValue();this.setChangedValue(val + this.step);},onSpinDown : function() {var val = this.getValue() || this.getMinValue();this.setChangedValue(val - this.step);},getLabeledValue : function(value) {value = Ext.isDefined(value) ? value : this.getValue();if (value.toString().indexOf(this.getLabelText()) == -1) {return value + ' ' + this.getLabelText();} else {return value;}},setValue : function(value) {if(!this.readOnly){value = this.getLabeledValue(value);}this.callParent(arguments);},getValue : function() {var me = this, val = me.rawToValue(me.processRawValue(me.getRawValue()));val = parseFloat(val.split(' ')[0]);return val;},setChangedValue : function(value){        if(!isNaN(value)){this.setValue(Ext.Number.constrain(value, this.getMinValue(), this.getMaxValue()));}}}); 

以下是我们的工作“标签式旋转框”扩展的屏幕截图:

规划和编码标签式旋转框

在前面的屏幕截图中,我们可以在窗口中找到“获取值”按钮。当点击此按钮时,我们通过调用 getValuegetLabeledValue 函数来打印窗口上的值。

摘要

在本章中,我们通过扩展 Ext JS 的现有类开发了一个新的组件,并学习了如何轻松创建 Ext JS 扩展以及注入所需的功能。在下一章中,我们将开发一个名为“图表下载”的 Ext JS 插件,该插件在容器工具栏上生成一个按钮,当点击此按钮时,可以将容器的图表项作为图片下载。

第五章:图表下载器

在本章中,我们将开发一个 Ext JS 插件,该插件将帮助我们下载图表作为图片。此插件将生成一个按钮,当按钮被点击时,它将执行所需的功能,使得插件容器中的图表项将被下载为图片。

在本章中,我们将涵盖:

  • 功能需求

  • 规划并编写图表下载器

功能需求

我们希望开发一个插件,该插件将简化将图表作为图片下载的功能。该插件将在容器的底部工具栏中生成一个按钮。如果容器底部没有工具栏,则此插件将为容器创建一个底部工具栏,然后在该工具栏中生成按钮。当此按钮被点击时,插件将在容器内搜索图表项,并将图表作为图片下载。

规划并编写图表下载器

插件的容器可能包含或不包含底部栏,因此我们需要在容器内搜索底部栏。如果找到,我们将使用它,否则我们需要创建底部栏,然后我们可以将下载按钮添加到该底部栏。

现在,让我们开始编写插件代码。

Ext.define('Examples.plugin.ChartDownload', {alias : 'plugin.chartdownload',config : {chartXtype: 'chart',downloadButtonText: 'Download as image',chartNotFoundErrorMsg: 'No valid chart type found!',errorText: 'Error'}
…

我们在这里提供了一个配置选项chartXtype,这样我们就可以使用我们想要下载为图片的图表的正确 xtype 来配置此插件。现在让我们定义此插件所需的init函数:

init : function(container) {this.container = container;if (!container.rendered) {container.on('afterrender', this.handleAfterRender, this);} else {this.handleAfterRender();}}

现在让我们定义handleAfterRender函数:

handleAfterRender : function(container) {this.chart = this.container.down(this.getChartXtype());if(!Ext.isDefined(this.chart) || this.chart ==null){Ext.Function.defer(function(){this.showErrorMessage({title: this.getErrorText(),text: this.getChartNotFoundErrorMsg()})}, 1000, this);}else{this.addDownloadButton();}},

在此函数中,我们试图获取图表组件,如果找不到图表组件,我们将显示错误消息。如果找到图表组件,我们将调用addDownloadButton函数,该函数将创建并添加下载按钮。现在让我们定义addDownloadButton函数:

addDownloadButton: function(){var toolbar = this.getToolbar(),itemsToAdd = [],placeholder = '->',button = {iconCls : 'icon-export',text : this.getDownloadButtonText(),handler: this.saveChart,scope : this};if(toolbar.items.items.length === 0){itemsToAdd.push(placeholder);}itemsToAdd.push(button);toolbar.add(itemsToAdd); 
}

在此函数中,首先我们试图通过调用getToolbar函数获取底部工具栏,然后将下载按钮添加到该工具栏中。现在让我们定义getToolbar函数:

getToolbar: function(){var dockedItems = this.container.getDockedItems(),toolbar = null,hasToolbar = false;if(dockedItems.length>0){Ext.each(dockedItems, function(item){if(item.xtype ==='toolbar' && item.dock == 'bottom'){hasToolbar = true;toolbar = item;return false;}});}if(!hasToolbar){toolbar = this.container.addDocked({xtype: 'toolbar',dock: 'bottom'})[0];}return toolbar;}

在此函数中,我们试图获取容器的底部工具栏,如果找到工具栏,我们使用它,如果没有找到,我们创建一个新的底部工具栏。现在让我们定义saveChart函数,该函数将在点击下载按钮时被调用:

saveChart: function(){this.chart.save({type : 'image/png'});}

在这里,我们在一个窗口中使用此插件:

Ext.define('Examples.view.chartdownloadplugin.ChartDownloadPluginWindow', {extend : 'Ext.Window',alias : 'widget.chartdownloadpluginwindow',requires : ['Examples.view.chartdownloadplugin.Chart','Examples.plugin.ChartDownload'],constructor : function(config) {Ext.apply(this, {modal : true,width : 400,height : 300,title : 'ChartDownloadPlugin',layout : {type:'fit'},plugins:['chartdownload'],items : [Ext.create('Examples.view.chartdownloadplugin.Chart')],buttons : [{text : 'OK',handler : function() {this.close();},scope : this}]});this.callParent(arguments);}
});

以下截图是使用我们的图表下载器插件生成的输出:

规划并编写图表下载器

您可以看到“下载为图片”按钮生成在窗口的底部栏中,用户可以通过点击此按钮下载图片。

现在,让我们使用没有定义底部栏的另一个容器进行测试:

Ext.define('Examples.view.chartdownloadplugin.ChartDownloadPluginWindow', {extend : 'Ext.Window',alias : 'widget.chartdownloadpluginwindow',requires : ['Examples.view.chartdownloadplugin.Chart','Examples.plugin.ChartDownload'],constructor : function(config) {Ext.apply(this, {modal : true,width : 400,height : 300,title : 'ChartDownloadPlugin',layout : {type:'fit'},items : [{xtype:'panel',plugins:['chartdownload'],layout:'fit',items:[Ext.create('Examples.view.chartdownloadplugin.Chart')]}],buttons : [{text : 'OK',handler : function() {this.close();},scope : this}]});this.callParent(arguments);}
});

以下截图是输出结果:

规划并编写图表下载器

您可以看到,下载按钮现在生成在嵌套面板的底部栏中。

概述

在本章中,我们开发了一个可以下载图表为图片的 Ext JS 插件。通过本章,我们学习了如何创建 Ext JS 插件以及如何通过 Ext JS 插件轻松注入功能。在下一章,我们将介绍一个非常流行的用于网格搜索的插件,用户可以选择或取消选择他们想要应用搜索的网格列。

第六章 网格搜索

在本章中,我们将开发一个 Ext JS 插件,该插件将在网格内提供搜索功能。这个插件最初是由 Ing. Jozef Sakáloš开发的,它是一个非常实用且受欢迎的插件。我们将为 Ext JS 4x 版本重写这个插件。

在本章中,我们将涵盖:

  • 功能需求

  • 网格搜索的规划和编码

功能需求

我们希望开发一个插件,该插件将帮助用户通过文本字段在网格面板中进行搜索。该插件还将为用户提供选择或取消选择他们想要应用搜索的网格列的选项。将有一个清除按钮来清除搜索文本。将有一个配置选项,用户可以设置他们想要在搜索文本框中输入以触发搜索的字符数。

网格搜索的规划和编码

为了开发这个插件,我们将创建一个菜单,用户可以在其中选择和取消选择网格的列,一个文本字段,用户可以在其中输入他们的搜索查询,以及一个清除按钮,它将帮助清除搜索查询。最初,我们将开发所需的 UI 字段,然后我们将添加相应的功能到这些字段中。现在让我们开始编码:

Ext.define("Examples.plugin.GridSearch", {extend : 'Ext.util.Observable',alias : 'plugin.gridsearch',config : {iconCls : 'icon-zoom',checkIndexes : "all",mode : 'local',minChars : 1,width : 100,searchText : 'Search',selectAllText : 'Select all',position: 'bottom' ,paramNames: {fields:'fields',query:'query'}},init : function(cmp) {this.grid = cmp.view.up('gridpanel');if (this.grid.rendered)this.onRender();else {this.grid.on('render', this.onRender, this);}},
…

您可以看到,我们已经定义了几个配置选项,以及插件所需的init函数。现在让我们定义onRender函数:

onRender : function() {var tb = this.getToolbar();this.menu = new Ext.menu.Menu();this.field = Ext.create("Ext.form.field.Trigger", {width : this.width,selectOnFocus : undefined === this.selectOnFocus ? true : this.selectOnFocus,triggerCls : 'x-form-clear-trigger', minLength : this.minLength});tb.add('->', {text : this.searchText,menu : this.menu,iconCls : this.iconCls}, this.field);}

在这个函数中,首先我们尝试通过调用getToolbar函数来获取工具栏,因为我们需要在工具栏上渲染我们的插件 UI。然后我们创建一个菜单字段,该字段将包含列选择,然后是搜索字段。在此之后,我们将菜单字段和搜索字段添加到该工具栏中。现在让我们定义getToolbar函数:

getToolbar: function(){var me = this,dockedItems = this.grid.getDockedItems(),toolbar = null,hasToolbar = false;if(dockedItems.length>0){Ext.each(dockedItems, function(item){if(item.xtype ==='toolbar' && item.dock == me.position){hasToolbar = true;toolbar = item;return false;}});}if(!hasToolbar){toolbar = this.grid.addDocked({xtype: 'toolbar',dock: this.position})[0];}return toolbar;}

在这个函数中,我们正在寻找一个工具栏项,该工具栏项位于位置配置选项中定义的位置。我们将在返回的工具栏上渲染我们的插件 UI。

现在让我们在这个网格中使用这个插件,插件输出应该看起来像以下截图:

网格搜索的规划和编码

因此,现在我们的插件看起来完全符合要求。现在让我们开始添加功能。首先让我们修改插件代码中的onRender函数:

this.field = Ext.create("Ext.form.field.Trigger", {width : this.width,selectOnFocus : undefined === this.selectOnFocus ?true : this.selectOnFocus,triggerCls : 'x-form-clear-trigger',onTriggerClick : Ext.bind(this.onTriggerClear, this),minLength : this.minLength
});

您可以看到,我们已经为onTriggerClick事件提供了onTriggerClear处理程序来清除搜索。我们需要添加和处理一些键盘事件:按下Enter键将触发搜索,按下Esc键将触发清除搜索。因此,我们需要在定义触发字段后添加以下代码:

this.field.on('render', function() {if (this.minChars) {this.field.el.on({scope : this,buffer : 300,keyup : this.onKeyUp});}var map = new Ext.KeyMap(this.field.el, [{key : Ext.EventObject.ENTER,scope : this,fn : this.onTriggerSearch}, {key : Ext.EventObject.ESC,scope : this,fn : this.onTriggerClear}]);map.stopEvent = true;
}, this, {single : true
});

现在,我们需要准备菜单以加载列名,我们将调用initMenu函数来完成这个操作。这就是我们在onRender函数中需要做的所有事情。

现在让我们定义onKeyUp处理程序:

onKeyUp : function(e) {if (e.isNavKeyPress()) {return;}var length = this.field.getValue().toString().length;if (0 === length || this.minChars <= length) {this.onTriggerSearch();}}

让我们继续定义initMenu函数:

initMenu : function() {var menu = this.menu;menu.removeAll();menu.add(new Ext.menu.CheckItem({text : this.selectAllText,checked : !(this.checkIndexes instanceof Array),hideOnClick : false,handler : function(item) {var checked = item.checked;menu.items.each(function(i) {if (item !== i && i.setChecked && !i.disabled) {i.setChecked(checked);}});}}), '-');var cm = this.grid.headerCt.items.items;var group = undefined;Ext.each(cm, function(item) {var config = item.initialConfig;var disable = false;if (config.header && config.dataIndex) {Ext.each(this.disableIndexes, function(item) {disable = disable ? disable : item === config.dataIndex;});if (!disable) {menu.add(new Ext.menu.CheckItem({text : config.header,hideOnClick : false,group : group,checked : 'all' === this.checkIndexes,dataIndex : config.dataIndex}));}}}, this);if (this.checkIndexes instanceof Array) {Ext.each(this.checkIndexes, function(di) {var item = menu.items.find(function(itm) {return itm.dataIndex === di;});if (item) {item.setChecked(true, true);}}, this);}}

您可以看到我们是如何在先前的initMenu函数中准备菜单以选择和取消选择列的。现在让我们定义onTriggerClear函数,该函数负责清除搜索查询:

onTriggerClear : function() {if (this.field.getValue()) {this.field.reset();this.field.focus();this.onTriggerSearch();}}

接下来,我们定义onTriggerSearch函数:

onTriggerSearch : function() {if (!this.field.isValid()) {return;}var val = this.field.getValue(),store = this.grid.store,proxy = store.getProxy();
…

我们需要检查为mode配置选项设置的值,如果设置为'local'或者存储代理是服务器代理,我们需要提供单独的逻辑。现在,当模式设置为'local'时,我们需要在onTriggerSearch函数中添加以下代码:

if ('local' === this.mode) {store.clearFilter();if (val) {store.filterBy(function(r) {var retval = false;this.menu.items.each(function(item) {if (!item.dataIndex || !item.checked || retval) {return;}var rv = r.get(item.dataIndex), rv = rv instanceof Date ?Ext.Date.format(rv, this.getDateFormat(item)) : rv;var re = new RegExp(Ext.String.escape(val), 'gi');retval = re.test(rv);}, this);if (retval) {return true;}return retval;}, this);}
}

如果值不是设置为local,我们需要检查代理是否是服务器代理。以下是需要在onTriggerSearch函数中添加的代码,在if ('local' === this.mode)块之后:

else if(proxy instanceof Ext.data.proxy.Server) {if(store.lastOptions && store.lastOptions.params) {store.lastOptions.params[store.paramNames.start] = 0;} var fields = [];this.menu.items.each(function(item) {if(item.checked && item.dataIndex) {fields.push(item.dataIndex);}});delete(proxy.extraParams[this.paramNames.fields]);delete(proxy.extraParams[this.paramNames.query]);if (store.lastOptions && store.lastOptions.params) {delete(proxy.lastOptions.params[this.paramNames.fields]);delete(proxy.lastOptions.params[this.paramNames.query]);}if(fields.length) {proxy.extraParams[this.paramNames.fields] = Ext.encode(fields);proxy.extraParams[this.paramNames.query] = val;}store.load();
}

现在我们定义getDateFormat函数:

getDateFormat : function(menuItem) {var columnNames = Ext.Array.pluck(this.grid.columns, 'dataIndex'),columnIndex = Ext.Array.indexOf(columnNames, menuItem.dataIndex),format = this.grid.columns[columnIndex].format;return this.dateFormat || format;
}

下面是我们工作插件的截图:

规划与编写网格搜索代码

您可以看到我们的插件是根据搜索查询过滤数据的。

摘要

在本章中,我们开发了一个 Ext JS 插件,以在网格内提供搜索功能。现在我们清楚地了解了 Ext JS 插件是多么强大。我们可以在网格中轻松使用此插件,并在需要时提供此出色的搜索功能。

在下一章中,我们将介绍另一个针对文本组件的有用插件,该插件在文本字段上方显示一个清晰的按钮,我们将看到点击按钮将如何清除文本字段中的文本。

第七章:带清除按钮的输入字段

在本章中,我们将介绍 Stephen Friedrich 的ClearButton插件。这个插件针对显示在文本字段上方的“清除”按钮的文本组件。当点击清除按钮时,文本字段将被清空。此外,可以使用 CSS 控制图标图像及其定位。

涵盖的主题包括:

  • 功能需求

  • 清除按钮的规划和编码

功能需求

我们希望有一个插件,可以帮助用户清除文本组件中的文本,例如Ext.form.field.TextExt.form.field.TextAreaExt.form.field.ComboBoxExt.form.field.Date。这个插件应该提供一个按钮,用户可以点击以清除文本组件中的文本。这个插件应该有几个有用的配置选项和 CSS 样式,我们可以设置我们的要求,例如清除按钮应该始终/仅在鼠标进入输入字段时显示,或者当输入字段为空时显示,或者当用户按下Esc键时清除。清除按钮可以通过 CSS 自定义按钮图像/位置等。

清除按钮的规划和编码

为了开发这个插件,我们首先将创建一个将在文本组件上渲染的清除按钮,并根据配置选项应用 CSS 样式。之后,我们需要为清除按钮添加几个事件处理器,例如clickmouseovermouseoutmouseupmousedown,以及为文本组件添加几个事件处理器,例如destroyresizechangemouseovermouseout。现在让我们开始编码:

Ext.define('Examples.plugin.ClearButton', {alias : 'plugin.clearbutton',hideClearButtonWhenEmpty : true,hideClearButtonWhenMouseOut : true,animateClearButton : true,clearOnEscape : true,clearButtonCls : 'ext-ux-clearbutton',textField : null,animateWithCss3 : false,constructor : function(cfg) {Ext.apply(this, cfg);this.callParent(arguments);},init : function(textField) {this.textField = textField;if (!textField.rendered) {textField.on('afterrender', this.handleAfterRender, this);}else {this.handleAfterRender();}}

在前面的代码片段中,你可以看到我们定义了几个配置选项和必需的init函数。

现在,让我们定义handleAfterRender函数:

handleAfterRender : function(textField) {this.isTextArea = (this.textField.inputEl.dom.type.toLowerCase() == 'textarea');this.createClearButtonEl();this.addListeners();this.repositionClearButton();this.updateClearButtonVisibility();this.addEscListener();
}

在这个handleAfterRender函数中,首先,我们检查文本字段是否是textarea,因为我们需要处理可能带有滚动条的textarea的自定义功能。然后我们调用createClearButtonEl函数来创建清除按钮的元素和 DOM。

现在,让我们定义createClearButtonEl函数:

createClearButtonEl : function() {var animateWithClass = this.animateClearButton &&this.animateWithCss3;this.clearButtonEl = this.textField.bodyEl.createChild({tag : 'div',cls : this.clearButtonCls});if (this.animateClearButton) {this.animateWithCss3 = this.supportsCssTransition(this.clearButtonEl);}if (this.animateWithCss3) {this.clearButtonEl.addCls(this.clearButtonCls + '-off');}else {this.clearButtonEl.setStyle('visibility', 'hidden');}
}

在前面的函数中,清除按钮已经被创建并分配了一个基于配置选项的动画。在这个函数中,我们还通过调用supportsCssTransition函数来检查浏览器是否支持 CSS3 过渡。

现在,让我们定义supportsCssTransition函数:

supportsCssTransition: function(el) {var styles = ['transitionProperty', 'WebkitTransitionProperty','MozTransitionProperty', 'OTransitionProperty','msTransitionProperty', 'KhtmlTransitionProperty'];var style = el.dom.style;for(var i = 0, length = styles.length; i < length; ++i) {if(style[styles[i]] !== 'undefined') { return true;}}return false;
}

handleAfterRender函数中,我们接下来调用的函数是addListeners函数,用于向字段、其输入元素和清除按钮添加监听器,以处理如mouseovermouseoutclick等调整大小的事件。

现在,让我们定义addListeners函数:

addListeners: function() { var textField = this.textField;var bodyEl = textField.bodyEl;bodyEl.on('mouseover', this.handleMouseOverInputField, this);bodyEl.on('mouseout', this.handleMouseOutOfInputField, this);textField.on('destroy', this.handleDestroy, this);textField.on('resize', this.repositionClearButton, this);textField.on('change', function() {this.repositionClearButton();this.updateClearButtonVisibility();}, this);var clearButtonEl = this.clearButtonEl;clearButtonEl.on('mouseover', this.handleMouseOverClearButton,this);clearButtonEl.on('mouseout', this.handleMouseOutOfClearButton,this);clearButtonEl.on('mousedown', this.handleMouseDownOnClearButton,this);clearButtonEl.on('mouseup', this.handleMouseUpOnClearButton,this);clearButtonEl.on('click', this.handleMouseClickOnClearButton,this);
}

接下来我们定义mouseover事件处理器handleMouseOverInputFieldmouseout事件处理器handleMouseOutOfInputField,用于textFieldbodyEl

handleMouseOverInputField: function(event, htmlElement, object) {this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-input');if (event.getRelatedTarget() == this.clearButtonEl.dom) {this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-button');this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down');}this.updateClearButtonVisibility();
},
handleMouseOutOfInputField: function(event, htmlElement, object) {this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-input');if (event.getRelatedTarget() == this.clearButtonEl.dom) { this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-button');}this.updateClearButtonVisibility();
}

现在,让我们定义 textField 的 "destroy" 事件处理器,因为当字段被销毁时,我们还需要销毁清除按钮元素以防止内存泄漏:

handleDestroy: function() {this.clearButtonEl.destroy();
}

现在,让我们开始定义清除按钮的 mouseovermouseoutmousedownmouseupclick 事件的处理程序:

handleMouseOverClearButton: function(event, htmlElement, object) {event.stopEvent();if (this.textField.bodyEl.contains(event.getRelatedTarget())) {return;}this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-over-button');this.updateClearButtonVisibility();
},handleMouseOutOfClearButton: function(event, htmlElement, object){event.stopEvent();if (this.textField.bodyEl.contains(event.getRelatedTarget())) { return;}this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-over-button');this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down');this.updateClearButtonVisibility();
},handleMouseDownOnClearButton: function(event, htmlElement,object){if (!this.isLeftButton(event)) {return;}this.clearButtonEl.addCls(this.clearButtonCls +'-mouse-down');
},handleMouseUpOnClearButton: function(event, htmlElement, object) {if (!this.isLeftButton(event)) {return;}this.clearButtonEl.removeCls(this.clearButtonCls +'-mouse-down');
},handleMouseClickOnClearButton: function(event, htmlElement, object) {if (!this.isLeftButton(event)) {return;}this.textField.setValue('');this.textField.focus();
}

handleAfterRender 函数中,我们将调用的下一个函数是 repositionClearButton 函数,根据 textFieldinputEl 元素重新定位清除按钮元素。现在,让我们定义这个函数:

repositionClearButton: function() {var clearButtonEl = this.clearButtonEl;if (!clearButtonEl) {return;}var clearButtonPosition = this.calculateClearButtonPosition(this.textField);clearButtonEl.dom.style.right = clearButtonPosition.right +'px';clearButtonEl.dom.style.top = clearButtonPosition.top + 'px';
}

您可以看到,我们通过调用 calculateClearButtonPosition 函数来获取清除按钮的位置值。此函数根据 textFieldinputEl 元素计算清除按钮的位置。现在,让我们定义这个函数:

calculateClearButtonPosition: function(textField) {var positions = textField.inputEl.getBox(true, true);var top = positions.y;var right = positions.x;if (this.fieldHasScrollBar()) {right += Ext.getScrollBarWidth();}if (this.textField.triggerWrap) {right += this.textField.getTriggerWidth();}return {right: right,top: top};
}

您可以看到,我们检查了字段是否有滚动条,如果有滚动条,我们将 Ext.getScrollBarWidth 函数的值添加到 right 位置。现在,让我们定义 fieldHasScrollBar 函数:

fieldHasScrollBar: function() {if (!this.isTextArea) {return false;}var inputEl = this.textField.inputEl;var overflowY = inputEl.getStyle('overflow-y');if (overflowY == 'hidden' || overflowY == 'visible') {return false;}if (overflowY == 'scroll') {return true;}if (inputEl.dom.scrollHeight <= inputEl.dom.clientHeight) {return false;}return true;
}

然后,我们在 handleAfterRender 函数中调用了 updateClearButtonVisibility 函数来修复清除按钮的可见性:

updateClearButtonVisibility: function() {var oldVisible = this.isButtonCurrentlyVisible();var newVisible = this.shouldButtonBeVisible();var clearButtonEl = this.clearButtonEl;if (oldVisible != newVisible) {if(this.animateClearButton && this.animateWithCss3) {this.clearButtonEl.removeCls(this.clearButtonCls +(oldVisible ? '-on' : '-off'));clearButtonEl.addCls(this.clearButtonCls + (newVisible ? '-on' : '-off'));}else {clearButtonEl.stopAnimation();clearButtonEl.setVisible(newVisible,this.animateClearButton);}clearButtonEl.setStyle('background-color',this.textField.inputEl.getStyle('background-color'));if (!(this.isTextArea && Ext.isGecko) && !Ext.isIE) {var deltaPaddingRight = clearButtonEl.getWidth() - this.clearButtonEl.getMargin('l');var currentPaddingRight = this.textField.inputEl.getPadding('r');var factor = (newVisible ? +1 : -1);this.textField.inputEl.dom.style.paddingRight = (currentPaddingRight + factor * deltaPaddingRight) + 'px';}}
}

您可以看到,我们通过调用 isButtonCurrentlyVisibleshouldButtonBeVisible 函数来获取当前可见状态值以及新的可见状态。isButtonCurrentlyVisible 函数是 clearButtonEl.isVisible() 的包装器,用于处理可能仍在进行的 setVisible 动画,而 shouldButtonBeVisible 函数检查配置选项和当前鼠标状态,以确定清除按钮是否应该可见。现在,让我们定义这些函数:

isButtonCurrentlyVisible: function() {if (this.animateClearButton && this.animateWithCss3) {return this.clearButtonEl.hasCls(this.clearButtonCls + '-on');} var cachedVisible = Ext.core.Element.data(this.clearButtonEl.dom, 'isVisible');if (typeof(cachedVisible) == 'boolean') {return cachedVisible;}return this.clearButtonEl.isVisible();
},shouldButtonBeVisible: function() {if (this.hideClearButtonWhenEmpty && Ext.isEmpty(this.textField.getValue())) {return false;}var clearButtonEl = this.clearButtonEl;if (this.hideClearButtonWhenMouseOut && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-button') && !clearButtonEl.hasCls(this.clearButtonCls + '-mouse-over-input')) {return false;}return true;
}

handleAfterRender 函数中,我们调用的最后一个函数是 addEscListener 函数。我们需要做的是,如果配置选项 clearOnEscape 设置为 true,则添加一个键监听器来清除此字段。现在,让我们定义这个函数:

addEscListener: function() {if (!this.clearOnEscape) {return;}this.textField.inputEl.on('keydown', function(e) {if (e.getKey() == Ext.EventObject.ESC) {if (this.textField.isExpanded) {return;}Ext.Function.defer(this.textField.setValue, 1,this.textField, ['']);e.stopEvent();}},this);
}

以下截图显示了使用此插件为 textfieldtextareafieldcombobox 和日期字段生成的输出:

清除按钮的规划和编码

您可以看到,在悬停在 textfield 组件上时,清除按钮是可见的,并且点击此按钮将清除相应的字段。

摘要

在本章中,我们介绍了一个 Ext JS 插件,该插件为文本组件提供了一个清晰的清除按钮,用于清除其内部的内容。我们可以看到,单个插件可以用于多种类型的组件,以及我们如何轻松地注入插件的功能。

在下一章中,我们将开发一个用于花哨动画消息栏的 Ext JS 扩展。消息栏将提供一个可配置的持续时间计时器来显示消息。它将有一个关闭按钮,并且它还可以在消息旁边显示 "错误" 和 "成功" 图标。

第八章:消息栏

在本章中,我们将开发一个 Ext JS 扩展,它将是一个花哨的动画消息栏。消息栏将提供显示消息的可配置持续时间计时器、关闭按钮,并且还可以在运行时自定义外观,并为不同类型的状态提供可选的图标,如有效、无效或信息。

在本章中,我们将涵盖:

  • 功能需求

  • 消息栏的规划和编码

功能需求

我们的目标是开发一个 Ext JS 扩展,它可以用来显示消息。这个扩展可以在容器内作为一个停靠项来显示消息。当配置的计时器完成后,消息栏将自动关闭。这个消息栏还将提供一个关闭按钮,点击这个按钮,消息栏将被关闭。消息栏的打开和关闭将以平滑的动画形式进行。这个消息栏还可以在运行时接受配置,以显示它可以改变外观的几种状态,并可以显示适当的图标。

消息栏的规划和编码

为了开发这个扩展,我们可以使用 Ext JS 工具栏并将其停靠到容器的底部。然后我们可以添加一个功能,使这个工具栏可以显示消息文本。然后我们需要添加一个功能,当调用消息栏来显示消息时;消息栏在点击关闭按钮或配置的计时器完成后出现和消失。根据我们的功能需求,我们可以看到 Ext JS 库提供的StatusBar扩展做了很多我们需要的,我们需要为这个消息栏扩展提供功能。因此,我们可以修改这个扩展并添加我们自己的功能和 CSS 样式来满足我们的需求。现在让我们开始编码:

Ext.define('Examples.ux.MessageBar', {extend: 'Ext.toolbar.Toolbar',alias: 'widget.ux-msgbar',activeThreadId: 0,dock: 'bottom',config: {cls: 'x-messagebar',emptyText: '',defaultText: '',autoClear: 5000},initComponent: function () {this.callParent(arguments);},
…

在定义了类配置之后,现在让我们创建一个元素,消息文本将在其中显示——图标和关闭按钮。现在让我们定义afterRender处理程序,我们将在这里创建这些元素:

afterRender: function () {this.el.addCls('x-message-msgbar-body');this.currIconCls = this.iconCls || this.defaultIconCls;var me = this;setTimeout(function () {var tpl = new Ext.XTemplate('<div id="{id}-bar" class="{bodyCls}"',' style="width: {width}px; {left}">','<div class="{msgCls}"></div>','<div style="float:right" class="{closeCls}">X</div>','</div>');tpl = tpl.apply({id: me.id,bodyCls: 'x-message-msgbar-body',width: me.ownerCt.getWidth() - 10,left: Ext.isIE8 ? 'left:5px' : '',msgCls: 'x-message-bar-msg',closeCls: 'x-message-bar-close'});me.ownerCt.el.createChild(tpl);Ext.select('.x-message-bar-close').on('click', function () {me.clearMessage();});}, 500);this.hide();this.callParent(arguments);
}

您可以看到我们是如何创建这些元素的。我们还定义了关闭按钮的点击处理程序。每当关闭按钮被点击时,我们都会调用clearMessage函数。我们将在本章后面定义这个函数。我们需要注意,每当容器被调整大小时,我们也需要调整消息栏元素的大小。因此,现在让我们在afterRender处理程序中添加一些代码来处理这个问题:

this.ownerCt.on('resize', function (ownerContainer, width, height) {if (width == this.parentWidth && height == this.parentHeight) {return;}var bar = Ext.get(this.id + '-bar');if (bar) {bar.setStyle('width', (this.ownerCt.getWidth()-10)  + 'px');}
}, this);

现在让我们定义showMessage函数,它将被调用来显示带有提供配置的消息:

showMessage: function (msg) {if (Ext.isString(msg)) {msg = {text: msg}}this.setMessage({text: msg.text,iconCls: 'x-message-'+ (msg.type || '') + ' ',clear: Ext.isDefined(msg.clear) ? msg.clear : true});}

在这个函数中,我们正在检查提供的配置,并正确地准备配置,然后将其发送到setMessage函数。现在让我们定义setMessage函数:

setMessage: function (o) {if (o && (o.text == '' || o.text == '&nbsp;')) {return;} else {var cmp = Ext.get(this.id + '-bar');if (cmp) {cmp.slideIn('b', {duration: 300,easing: 'easeIn',callback: function () {this.setMessageData(o);},scope: this});}}
}

在这个函数中,我们正在检查消息文本是否为空,如果不为空,我们将打开消息栏并调用 setMessageData 函数来设置消息文本、UI 和消息栏的适当图标。现在让我们定义 setMessageData 函数:

setMessageData: function (o) {o = o || {};if (o.text !== undefined) {this.setText(o.text);}if (o.iconCls !== undefined) {var bar = Ext.get(this.id + '-bar');if (o.iconCls == 'x-message-error ') {bar.removeCls('x-message-msg-body');bar.addCls('x-message-error-body');} else {bar.removeCls('x-message-error-body');bar.addCls('x-message-msg-body');}this.setIcon(o.iconCls);}if (o.clear) {var c = o.clear, wait = this.autoClear, defaults = {useDefaults: true,anim: true};if (Ext.isObject(c)) {c = Ext.applyIf(c, defaults);if (c.wait) {wait = c.wait;}} else if (Ext.isNumber(c)) {wait = c;c = defaults;} else if (Ext.isBoolean(c)) {c = defaults;}c.threadId = this.activeThreadId;if (this.clearTimer) {clearTimeout(this.clearTimer);}this.clearTimer = Ext.defer(this.clearMessage, wait, this, [c]);}
}

你可以看到,在这个函数中,我们正在设置消息文本、图标和 UI。要设置消息文本,我们调用 setText 函数;要更改 UI,我们添加和移除相应的 CSS 类;要设置图标,我们调用 setIcon 函数。此外,当清除配置选项设置为 true 时,我们应用配置的计时器,在计时器完成后隐藏消息栏。现在让我们定义 clearMessage 函数:

clearMessage: function (o) {o = o || {};if (o.threadId && o.threadId !== this.activeThreadId) {return this;}var bar = Ext.get(this.id + '-bar');if (bar) {Ext.get(this.id + '-bar').slideOut('b', {duration: 300,easing: 'easeOut',callback: function () {var text = o.useDefaults ? this.defaultText : this.emptyText,iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : '';this.setMessage({text: text,iconCls: iconCls});},scope: this});}return this;
}

在这里,在这个 clearMessage 函数中,我们正在隐藏消息栏。

现在,让我们对这个扩展进行一些测试。在下面的屏幕截图中,你可以看到我们的消息栏扩展正在运行。我们创建了一个 Ext JS 窗口,并将消息栏作为一个停靠项添加进去。

规划与编码消息栏

你可以看到,当你点击 显示消息 按钮时,消息栏会显示配置的数据。当我们选择 无效图标 选项时,我们可以看到消息栏外观的变化。这里你可以看到我们选择 无效图标 时的结果:

规划与编码消息栏

从前面的屏幕截图中,我们可以看到我们的扩展确实按照我们的预期工作。

摘要

在本章中,我们开发了一个 Ext JS 扩展,一个花哨的动画消息栏。在本章中,我们学习了如何轻松地创建我们自己的自定义控件。正如我们已经学到的,Ext JS 扩展的力量以及我们如何轻松地创建 Ext JS 扩展,我们可以看到创建我们自己的控件确实非常容易,这有助于满足我们的定制需求。

在下一章中,我们将介绍另一个 Ext JS 扩展 BoxSelect,该扩展最初由 Kevin Vaughan 开发。这个扩展非常有用,它提供了一个更友好的 combobox,可以轻松地为每个选择创建可移除的标签。

第九章. 直观的多选 Combobox

在本章中,我们将探讨一个优秀的 Ext JS 扩展:BoxSelect,该扩展最初由 Kevin Vaughan 开发。这个扩展非常实用,并为多选提供了更友好的 combobox,可以轻松地为每个选择创建可移除的标签,还有很多其他功能。

涵盖的主题包括:

  • BoxSelect 的功能

  • BoxSelect扩展:

    • 基本配置

    • 模板

    • 单值选择

    • 使用未知值进行远程查询

    • 使用自动建议添加新记录

    • BoxSelect 特定配置

    • 值处理和事件

BoxSelect 的功能

BoxSelect是一个为更直观的多选功能开发的扩展ComboBox组件。BoxSelect附带大量示例和适当的文档。以下是由BoxSelect扩展提供的功能:

  • 可以单独移除所选项目。

  • 可定制的项目模板,用于控制所选值的显示。

  • 支持基于键盘的选择和导航所选值。

  • 当设置未知值时,支持从远程存储按需加载值,并且queryMode选项设置为remoteforceSelection设置为true

  • forceSelection设置为false将创建新记录。

  • multiSelect设置为true时,选择列表可以配置为在做出选择后是否应该折叠。

  • 可以配置所选项目以堆叠或自动调整大小。

  • 大多数现有的ComboBox功能性和配置选项都应该与BoxSelect一起工作。

使用 BoxSelect

BoxSelect扩展了ComboBox控件,以提供更友好的多选ComboBox控件。本章包含的示例显示了默认ComboBox控件和此扩展之间的差异,并提供了关于BoxSelect高级使用的通用信息。

基本配置

BoxSelect应支持所有配置值,如ComboBox支持。对于此扩展,默认值有一些变化:

  • multiSelect选项默认设置为true

  • forceSelection选项默认设置为true

  • 在大多数情况下,多选是从预定义的列表中进行的,但我们可以配置BoxSelect扩展以添加带有自动建议列表的新记录。

  • multiSelect选项设置为true时,ComboBox组件不支持typeAhead,尽管BoxSelect扩展中typeAhead的值默认设置为false,但已添加对此功能的支持。

  • 可以使用value选项来初始化multiSelect值。setValue方法接受相同的值格式。

现在,让我们使用以下配置开始使用BoxSelect扩展:

{"value": ["TX","CA"],"fieldLabel": "Select multiple states","displayField": "name","valueField": "abbr","width": 500,"labelWidth": 130,"emptyText": "Pick a state, any state","store": "States","queryMode": "local"
}

截图应如下所示:

基本配置

在先前的屏幕截图中,我们可以看到如何轻松地在BoxSelect组合框扩展中选择多个值。

模板

我们可以通过模板轻松配置选中值和下拉列表项的显示:

  • labelTpl:它是模板配置选项,用于控制输入字段中选中值的显示。

  • listConfig:它是模板配置选项,用于控制下拉列表项的显示。此选项在默认的ComboBox字段中可用,并由BoxSelect支持。

现在,让我们看看如何设置配置以自定义labelTpllistConfig选项:

{"delimiter": ", ","value": "AZ, CA, NC","labelTpl": "<img src=\"{flagUrl}\"style=\"height: 25px;vertical-align: middle;margin: 2px;\" /> {name} ({abbr})","listConfig": {"tpl": ["<ul><tpl for=\".\">","<li role=\"option\"class=\"x-boundlist-item\"style=\"background-image:url({flagUrl});background-repeat: no-repeat;background-size: 25px;padding-left: 30px;\">{name}: {slogan}</li>","</tpl></ul>"]},"fieldLabel": "Select multiple states","displayField": "name","valueField": "abbr","width": 500,"labelWidth": 130,"store": "States","queryMode": "local"
}

以下截图显示了使用先前的labelTpllistConfig配置的BoxSelect扩展:

模板

在先前的屏幕截图中,我们可以看到BoxSelect扩展正常工作,并使用配置的labelTpllistConfig组合框显示选中的项目。

单值选择

BoxSelect扩展针对多选,但它也支持单选,通过将multiSelect选项设置为false。如果我们需要默认的单选选项,我们可以在创建BoxSelect扩展之前添加以下代码行:

Ext.ux.form.field.BoxSelect.prototype.multiSelect = false;

现在,让我们为单选配置BoxSelect扩展:

{"fieldLabel": "Select a state","multiSelect": false,"filterPickList": true,"displayField": "name","valueField": "abbr","width": 500,"labelWidth": 130,"emptyText": "Pick a state, any state","store": "States","queryMode": "local"
}

输出结果应如下所示:

单值选择

在先前的屏幕截图中,当multiSelect选项设置为false时,我们可以在组合框中仅选择单个值。

带有未知值的远程查询

当我们将queryMode选项设置为remote并将forceSelection选项设置为true,并且我们传递给BoxSelect扩展的值不在存储中时,将向存储配置的代理“x”发送一个查询,其中包含valueField选项的名称和一组由配置的delimiter分隔的未知值作为参数。例如,如果valueField选项是abbrdelimiter值是|,并且设置了未知值'NC''VA''ZZ',则将传递以下参数到存储配置的代理:

{ abbr: 'NC|VA|ZZ' }

这种尝试加载未知值的操作将在每次initValue/setValue调用中只执行一次。在此请求之后仍然未知的数据记录将从字段值中删除,但所有已知值都将保留。在先前的示例中,'ZZ'条目被丢弃。

现在,让我们为远程存储配置BoxSelect扩展:

{"fieldLabel": "With Remote Store","store": "RemoteStates","pageSize": 25,"queryMode": "remote","delimiter": "|","value": "NC|VA|ZZ","triggerOnClick": false,"labelTpl": "{name} ({abbr})","listConfig": {"tpl": ["<ul><tpl for=\".\">","<li role=\"option\"class=\"x-boundlist-item\">{name}: {slogan}</li>","</tpl></ul>"]},"displayField": "name","valueField": "abbr","width": 500,"labelWidth": 130
}

以下截图显示了我们对BoxSelect扩展使用此配置:

带有未知值的远程查询

在先前的屏幕截图中,我们可以看到我们配置的BoxSelect在远程存储中工作正常,并且检索到了'NC''VA'的值,而'ZZ'的值被丢弃。

使用自动建议添加新记录

在此示例中,我们将展示当forceSelection设置为falseforceSelection的使用,以启用使用从附加存储提供的自动建议输入新值。新记录将使用用户输入的配置displayFieldvalueField来创建。这些新记录不会自动添加到ComboBox存储中。

可以通过以下四种方式中的任何一种创建新条目:

  • 当我们输入配置的delimiter(默认为逗号,)时,delimiter之前输入的值将用于创建新的记录。

  • 当我们将文本粘贴到字段中时,值将根据配置的delimiter进行分割,默认为逗号,,输入的任何值都将解析到新的/现有的记录中。

  • 默认情况下,createNewOnEnter选项设置为false。如果设置为true,则在按下Enter键时将创建一个新的条目。此配置选项仅适用于forceSelection选项设置为false的情况。

  • 默认情况下,createNewOnBlur选项设置为false。如果设置为true,则在焦点离开输入字段时将创建一个新的条目。此配置选项仅适用于forceSelection设置为false的情况,并且被autoSelectselectOnTab所取代。

现在,让我们配置BoxSelect扩展以实现自动建议:

{"fieldLabel": "Enter multiple email addresses","width": 500,"growMin": 75,"growMax": 120,"labelWidth": 130,"store": ["test@example.com","somebody@somewhere.net","johnjacob@jingleheimerschmidts.org","rumpelstiltskin@guessmyname.com","fakeaddresses@arefake.com","bob@thejoneses.com"],"queryMode": "local","forceSelection": false,"createNewOnEnter": true,"createNewOnBlur": true,"filterPickList": true,"displayField": "name","valueField": "abbr"
}

使用此配置,我们将得到以下BoxSelect组件:

使用自动建议添加新记录

在前面的屏幕截图中,我们可以看到BoxSelect组件提供了一个自动建议列表,我们可以从中选择列表项或创建新的记录。

BoxSelect特定配置

以下配置选项是针对BoxSelect扩展的特定选项:

  • 默认情况下,createNewOnEnter选项设置为false。如果此选项设置为trueforceSelection选项设置为false,则用户按下Enter键时将立即创建一个新的条目。

  • 默认情况下,createNewOnBlur选项设置为false。如果此选项设置为trueforceSelection选项设置为false,则在焦点离开输入字段时将创建一个新的条目。此配置选项被autoSelectselectOnTab所取代。

  • 默认情况下,stacked选项设置为false。如果此选项设置为true,则标签项将填充列表的可用宽度,而不是仅与显示的值一样宽。

  • 默认情况下,pinList选项设置为true。如果此选项设置为false,则在multiSelecttrue时,选择列表将在选择后自动折叠,这模仿了multiSelectfalse时的默认行为。

  • 默认情况下,triggerOnClick选项设置为true。当选项设置为true时,选择列表将模拟在字段中点击时的触发器,就像当ComboBox组件的editable选项设置为false时一样。

  • grow选项默认设置为true。如果将此选项设置为false,当需要时,选择列表将滚动,字段的高度将不会改变。如果为字段设置了固定高度,无论是直接设置(例如,通过高度配置)还是通过包含的布局设置,此设置将没有效果。

  • growMin选项默认设置为false。如果将此选项设置为true,任何数值都将用于字段的最小高度。

  • growMax选项默认设置为false。如果将此选项设置为true,任何数值都将用于字段的最高高度,并且当需要时,选择列表将滚动。

  • filterPickList选项默认设置为false。如果将此选项设置为true,当前选定的值将从展开的拾取列表中隐藏。

现在,让我们通过更改一些默认值来配置BoxSelect组件,以查看其效果:

{"fieldLabel": "Select multiple states","displayField": "name","width": 500,"labelWidth": 130,"store": "States","queryMode": "local","valueField": "abbr","value": "WA, TX","stacked": true,"pinList": false,"filterPickList": true
}

以下是在使用此配置的BoxSelect组件的截图:

BoxSelect 特定配置

在前面的截图中,我们可以看到,由于我们将stacked选项设置为true,标记的项目填充了可用的全部宽度。由于我们将pinList选项设置为false,一旦做出选择,拾取列表就会自动折叠,并且由于我们将filterPickList选项设置为true,当前选定的值将从展开的拾取列表中隐藏。

值处理和事件

BoxSelect中,以下方法可用于处理组合框的值:

  • addValue(mixedValue): 向字段的当前值添加一个或多个值。

  • removeValue(mixedValue): 从字段的当前值中移除一个或多个值。

  • getValueRecords(): 返回字段当前值的记录。

  • getSubmitData(): 允许将字段作为 JSON 编码的数组提交。

此外,BoxSelect组件还提供了以下两个事件来管理选定的项目:

  • valueSelectionChange

  • valueFocusChange

摘要

在本章中,我们探讨了BoxSelect组合框扩展的功能,并介绍了其使用方法。我们学习了如何配置BoxSelect扩展及其正确使用。我们可以看到,通过使用 Ext JS 的扩展功能,我们可以轻松地使用 Ext JS 库的ComboBox字段的全部功能,并可以添加我们自己的自定义功能以满足我们的需求。

在整本书中,我们学习了 Ext JS 插件和扩展的基础知识,介绍了几个流行的 Ext JS 库和社区提供的插件和扩展,我们还提供了几个带有适当解释和代码的实战插件和扩展开发示例。现在,我们对 Ext JS 插件和扩展的正确使用和开发有了清晰的认识。

http://www.hskmm.com/?act=detail&tid=18370

相关文章:

  • ECMAScript6-学习指南-全-
  • JSP征婚信息实用的系统3kx16代码+源码+数据库+调试部署+开发环境
  • CSS属性
  • 基于大数据的水产品安全信息可视化分析框架【Hadoop、spark、可视化大屏、课程毕设、毕业选题、数据分析、资料爬取、数据可视化】
  • CSS值
  • 2025_Polar秋季赛_web全解
  • QT:如何初始化窗体尺寸大小
  • 题2
  • linux命令-rm
  • 2025.9.26
  • 基于Amazon S3设置AWS Transfer Family Web 应用程序 - 实践
  • 作为 PHP 开发者,我第一次用 Go 写了个桌面应用
  • JBoltAI智能出题助手:助力高效学习与知识巩固 - 那年-冬季
  • JBoltAI设备智能检测:为设备管理维护提供高效辅助 - 那年-冬季
  • JBoltAI:Java与AI的完美融合,赋能技术团队新未来 - 那年-冬季
  • AIGS与AIGC:人工智能时代的范式跃迁与价值重构 - 那年-冬季
  • 5
  • ?模拟赛(3) 赛后总结
  • 用鼠标滚轮缩放原理图界面的小工具
  • 实验任务1
  • OI界的梗(继 @CCCsuper 2.0 版本)
  • 9/26
  • Python 私有属性深度解析
  • 菜鸟记录:c语言实现洛谷P1219 ———八皇后
  • 当危机爆发时,所有网络安全都是本地的
  • crc校验原理是什么?
  • CF1385D a-Good String
  • 9月23日(日记里有)
  • 9月25日(日记里有)
  • Git 提交代码前,一定要做的两件事