使用app的JavaScript用户界面控件套件

分享于 

22分钟阅读

Web开发

  繁體 雙語

介绍

附加是我开发的JavaScript UI控件的'套件'。 这些文档包括如何使用它们,每个控件都可以在 。ts文件中找到。 目前,有四个控件: 列表框,复选框 List,日历和网格。 它们都支持一些基本功能,比如弹出选项。滚动条和将选择写到'输出'html元素。 此外,所有这些控件都在三个主要浏览器中工作: IE 8和 上面,Firefox 和 Chrome。

这些控件的主要目标是通过屏蔽开发人员的HTML复杂度和浏览器之间的兼容性问题来简化客户端开发。 结果,我也成了第一次进入文稿,当我想到,因为打字稿是新的,我的经验可以能帮助它的他开发人员。

我既包括了app代码,也包括了编译时生成的相应JavaScript文件。

Background

几个月前,我开始完全用纯 JavaScript (。例如,没有像jQuery这样的框架或者库) 开发这个项目。 当我突然turned的时候,我的was fine。 这时我打开了我的电子邮箱,发现微软刚刚发布了一个新的'语言'名字簿。

无论你想称它为什么,不管你是否认为是新的,我立刻就知道我的生活变得更容易了。

一般来说,当一种看起来有期望的新语言时,我把我的决策帽分析出来是否值得。 影响这一决定的因素包括使用easy动态( 意思是,一个动态的运行时) 动态开发工具( 用于类型检查。调试。等等 ),以及它是否有可能成为流行的,是否有可能会出现在a。 幸运的是,TypeScript通过了所有这些要求,如 below 所示:

  • 因为to的主要目标是使应用程序比例开发更容易,而且我已经知道JavaScript是足够的了。
  • JavaScript是一种动态语言,因为每个that代码都被认为是字典代码(。例如,可以与新的app构造一起编译),很明显,我可以使用。
  • 作为一个微软产品,有很多必要的开发工具可以使用( 即使你的钱包在一天结束时可能有点小)。 果然,在 public 提供了一个可以供使用的当天,我可以下载一个 Visual Studio 插件,提供智能代码。代码导航。static 错误消息和重构。 ( 当然,这要求你有 Visual Studio,因为我做了,我的主要关心是我,要求传递。)
  • finally,也许最重要的是,如果有一个经理有线索,问题就变成了长期。 换句话说,如果我决定在 43年退出,我的老板可以找到另一个? 虽然不可以能预测未来的( 我也不清楚我在讨论什么),但是很明显,JavaScript已经越来越流行于它的流行行为。 for是 Windows 8中开发的标准语言之一,并且JavaScript开始被认为是服务器端开发的有价值语言( 比如 )。 Node.js。

对我来说,这个决定很简单,。 即使我的微软仇恨老板也会很难找到这个逻辑。

因这里,现在我几乎已经开发了两个月,并且发现我想分享我的经验和感觉是这种新语言的最好方面。

重要的是要认识到,因为in只是一个外观,so-to-speak上的编译的输出只是可以运行的JavaScript。 但是,除非你是专家级JavaScript程序员,否则在纯JavaScript中编写大量的功能会更难。 这对于已经熟悉 C#。Java和PHP等几乎所有主流语言的开发人员来说都是 true的。 这就是关键。 使JavaScript更易于编写代码 !

使用代码

这些文档包括如何使用它们,每个控件都可以在 。ts文件中找到。 下面是使用其中一个控件的示例,但它们都是相似的。

var cbList = new Zenith.CheckBoxList('baseElement');
cbList.NumColumns = 2;
cbList.ColumnSpace = 15;
cbList.MaximumHeight = 100;
cbList.PopUpControlId = 'testPopup';
cbList.PopUpPosition = 'right';
cbList.PopUpDirection = 'down';
cbList.OutputElementId = 'output1';
cbList.addZenithEventListener(Zenith.ZenithEvent.EventType.Selected, 
 function (value, text, checked) { alert(text + ' ' + (checked? 'selected' : 'unselected')); });
cbList.addZenithEventListener(Zenith.ZenithEvent.EventType.Close, function () { });// There are multiple ways to add the data to this control. This is the simplest way:cbList.AddItem(1, "Blue");
cbList.AddItem(2, "Yellow");
cbList.AddItem(3, "Red");
cbList.AddItem(4, "Green");
cbList.AddItem(5, "Turqoise");
cbList.AddItem(6, "Orange");
cbList.AddItem(7, "Black");
cbList.AddItem(8, "White");
cbList.AddItem(9, "Aqua");
cbList.AddItem(10, "Gray");
cbList.AddItem(11, "Purple");
cbList.Build();

使用指导

这里,我们将只分析其中一个控件,CheckBoxList 控件。 其他的控件都是类似的。

所有控件从名为 ControlBase的基类派生( 继承),它处理大多数常见逻辑,如popup逻辑,添加滚动条,执行通用事件和派生类所需的一些'protected'方法。

这就是类定义的样子:

exportclass CheckBoxList extends Zenith.ControlBase

" Export'本质上指示该类应在封闭 MODULE 外部可以用。'Zenith.ControlBase'引用 ControlBase 类中的'Zenith"MODULE。 注意,尽管每个控件都位于自己的文件中,但所有控件都位于'Zenith'MODULE 中。

Each类构造函数接受HTML元素 id this这个元素用作控件的元素,这意味着组成控件的所有用户界面元素都是这个 DIV 元素的inside。 每个构造函数调用基类的构造函数,它在这里从文档中检索元素并在这里 DIV 元素中创建边框。

下面是基类构造函数实现:

constructor (baseDivElementId: string)
{
 if (baseDivElementId.length <= 0)
 throw Error("The id of a 'div' HTML element must be passed to the Zenith control when 
 creating.");
 this.BaseElement = document.getElementById(baseDivElementId);
 if (!this.BaseElement)
 throw Error("The id of the 'div' HTML element passed in is not valid.");
 if (!(this.BaseElement instanceof HTMLDivElement))
 throw Error("The element associated with the Zenith control must be a div element.");
 this.BaseElement.style.borderColor = '#B6B8BA';
 this.BaseElement.style.borderWidth = '1px';
 this.BaseElement.style.borderStyle = 'solid';
}

每个控件还有一个 Build 方法,在构造控件之后应该调用它,并在创建的对象( 参见示例 上面 ) 上设置适当的属性。 Build 方法可能与其他 private 方法一起创建并定位控制控件的所有元素。 元素的位置使用HTML元素处理;换句话说,元素( DIV ) 下的'parent'元素是 <table> 元素。 below 是 CheckBoxList 控件的Build 方法的实现:

public Build(): void{
 if (this.ItemList.Count() <= 0)
 thrownew Error("The item list is empty.");
 this.Clear();
 var table: HTMLTableElement = <HTMLTableElement>document.createElement('table');
 this.BaseElement.appendChild(table);
 table.className = 'ZenithCheckBoxTable';
 var tbody: HTMLElement = document.createElement('tbody');
 table.appendChild(tbody);
 var trow: HTMLTableRowElement, tcell: HTMLTableCellElement;
 var colIndex: number = 0;
 for (var index = 0; index <this.ItemList.Count(); index++)
 {
 if (!trow || colIndex >= this.NumColumns)
 {
 trow = <HTMLTableRowElement>document.createElement('tr');
 tbody.appendChild(trow);
 colIndex = 0;
 }
 tcell = <HTMLTableCellElement>document.createElement('td');
 trow.appendChild(tcell);
 if (colIndex >0)
 tcell.style.paddingLeft = this.ColumnSpace + "px";
 this.addEventListener(tcell, 'click', (event) => { this.selectedEventHandler(event); });
 var itemCheckbox: HTMLInputElement = <HTMLInputElement>document.createElement('input');
 itemCheckbox.type = 'checkbox';
 itemCheckbox.name = 'ZenithControlCheckBox';
 itemCheckbox.value = this.ItemList.ElementAt(index).Value;
 itemCheckbox.id = 'chk_' + this.ItemList.ElementAt(index).Value;
 tcell.appendChild(itemCheckbox);
 var label:HTMLLabelElement = <HTMLLabelElement>document.createElement('label');
 label.htmlFor = 'chk_' + this.ItemList.ElementAt(index).Value;
 label.className = 'ZenithCheckBoxLabel_Unselected';
 label.textContent = this.ItemList.ElementAt(index).Text;
 label.innerHTML = this.ItemList.ElementAt(index).Text;
 tcell.appendChild(label);
 colIndex++;
 }
 this.ParentElement = table;
 if (this.IsPopup())
 super.SetPopup();
 super.Build();
} 

在构建UI之后,注意该控件是否应该是弹出控件,如果是这样,则调用基类中的SetPopup 方法。 这两行需要包含在每个控件的Build 方法中,以便提供弹出功能。 SetPopup 方法包括处理 popup 功能所需的所有逻辑,例如将控件 relative 放置到指定的'popup'控件和事件处理到'open'( 显示) 和'close'( 隐藏) 控件。 当关联的popup 控件是'单击'且在下列任何事件中关闭时,将显示该控件: 鼠标移出控制客户端区域,用户在控制客户端区域外按下鼠标按钮,或者按下'ctrl'键。

事件处理是打字的箭头函数构造真正有用的地方。 请在 SetPopup 方法中查看以下代码:

this.addEventListener(this.BaseElement, 'mouseout', (event) =>{
 var mouseEvent: MouseEvent = <MouseEvent>event;
 var targetElement: HTMLElement = <HTMLElement>mouseEvent.toElement;
 if (!targetElement)
 targetElement = <HTMLElement>document.elementFromPoint(mouseEvent.clientX, 
 mouseEvent.clientY);
 if (targetElement)
 {
 // The onmouseout event will happen event when leaving an element inside the element // the event is tied to.while (targetElement && targetElement!= this.BaseElement)
 targetElement = targetElement.parentElement;
 if (targetElement!= this.BaseElement)
 this.Close();
 }
}); 

我们可以使用箭头函数作为事件的监听器,使用'this'关键字调用类的close 方法,使用关键字来隐藏该控件。

基本类 Build 方法的主要用途是将基本 DIV 元素的大小设置为父级 TABLE 元素的大小,否则 DIV 元素的宽度将扩展到页客户端宽度的宽度。 这就是为什么需要在派生类的Build 方法末尾调用 Build 基类方法,以便在调整外部 DIV 元素之前构建 UI。

每个控件也有自己的自定义事件和自定义事件处理。 当前唯一自定义事件
支持'Selected'和'close',可以在 ZenithEvent 类中找到。 下面是 ZenithEvent 类的完整实现:

export class ZenithEvent
{
 publicstatic EventType = { Selected: 1, Close: 2 };
 public eventType: number;
 public listener: Function;
 constructor (eventType: number, listener: Function)
 {
 this.eventType = eventType; this.listener = listener;
 }
} 

EventType 对象文本充当一种 enum,这样可以将事件类型标识为前面定义的类型。
以下代码中的文本,用于将函数处理程序添加到'cbList'CheckBoxList:

cbList.addZenithEventListener(Zenith.ZenithEvent.EventType.Selected, 
 function (value, text, checked)
 { 
 alert(text + ' ' + (checked? 'selected' : 'unselected')); 
 }
); 

'Selected'自定义事件支持不同的侦听器参数集,具体取决于控件。 例如对于示例 上面 中的CheckBoxList 控件,三个参数被传递给侦听器函数: 选定项的值,选定项的文本,以及该项是否被选中或者取消选中。 'close'事件不会将任何参数传递给所有控件的关联侦听器。

其他一些东西值得注意。

Calendar 控件使用一个自定义的DateHelper 类,可以在 Calendar 代码文件 ZenithCalendar.ts 中找到它。 这个小类提供了一些'缺少'Date 类型的特性,比如长周和月份的名称和一个函数。 这里代码的全部代码为 below。

class DateHelper
{
 publicstatic MonthNames: string[] = ['January', 'February', 'March', 'April', 'May', 
 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
 publicstatic MonthShortNames: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 publicstatic DayOfWeekNames: string[] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 
 'Thursday', 'Friday', 'Saturday'];
 publicstatic DayOfWeekShortNames: string[] = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
 publicstatic DaysInMonth(iMonth, iYear): number
 {
 return32 - new Date(iYear, iMonth, 32).getDate();
 }
 publicstatic toShortDate(date: Date): string {
 return date.getFullYear().toString() + '-' + (date.getMonth() + 1).toString() + '-' +
 date.getDate().toString();
 }
 publicstatic toLongDate(date: Date): string {
 return MonthNames[date.getMonth()] + ' ' + date.getDate().toString() + ', ' + 
 date.getFullYear().toString();
 }
 publicstatic toShortDisplayDate(date: Date): string {
 return (date.getMonth() + 1).toString() + '/' + date.getDate().toString() + '/' + 
 date.getFullYear().toString();
 }
} 

CheckBoxList 类使用自定义类调用 List,它可以在 ZenithList.ts 代码文件中找到。 这是我今年早些时候在JavaScript中创建并移植到app中的类。 since控件并不真正使用这个类的大部分功能,所以我不打算详细介绍它,而不是使用内置的一些linq功能来模拟 List Collection 类。

Points of Interest

以下主题并不一定按任何顺序进行;但是,它们通常按照我认为是最重要的优点( 尽管这是一个部分主观的主题)的顺序。 此外,这显然不打算成为TypeScript的完整教程或者参考。

名称空间

尽管字典规范中几乎没有使用'namespace',但是本文中的'module'构造实际上提供了这种能力。

例如:

module Zenith 
{ 
 export class ListBox extends Zenith.ControlBase 
 { 
 public NumColumns: number = 1; 
 } 
}

在这个例子中,类 ListBox 不是全局命名空间的一部分,而是Zenith名称空间中的一种类型。 因这里,在这里 MODULE 外引用这里类需要将'Zenith'预先设置为类型,如下所示:

var lstList = new Zenith.ListBox('baseElement'); 

铸造

为了提供完整的类型检查和智能感知,语言必须具有某种类型的Casting 类型。 实际上,app使用 <> deliminators提供这里功能,如下所示:

var table: HTMLTableElement = <HTMLTableElement>document.createElement('table'); 

当然,IntelliSense可以为 HTML table 提供可用的属性集和函数集。

引用其他文件

提供类型检查和智能感知所必需的另一个方面是一种方法,它可以对包含代码所使用的类型。变量。方法。等等的另一个文件。 这与'reference'声明一起提供。

///<referencepath='ZenithControlBase.ts'/>

OOP

这里时,重新声明 上面 与对象方向相关的声明可以能是一个好主意: 这里描述的任何OOP功能都可以用纯 JavaScript。 但是,在这里,大多数开发人员(。非JavaScript专家) 都会感到混淆,因为JavaScript提供面向对象功能的方式如这里。

例如:

class ListBox extends Zenith.ControlBase 
{ 
 static x: string = 'Test'; 
 public NumColumns: number = 1; 
 public ColumnSpace: number = 10; 
 private ItemList = new Zenith.List(); 
 constructor (baseDivElementId: string) 
 { 
 super(baseDivElementId); 
 } 
}

大多数熟悉OOP的开发人员应该能够轻松理解这段代码和它的OOP构造。 'class'关键字标识类,'extends'提供继承,'constructor'标识构造函数,'super'引用基类,'static'提供 static 类成员,'public'和'private'关键字提供封装。

这里有一些东西是毫无价值的:

  • 只允许一个构造函数。
  • 无法对基类的成员进行'保护'操作。 这是我真正怀念的事情,但是我想这将是实现ECMAScript语言规范的第1 版的第一件事。

函数范围

app的一个非常方便的特性涉及函数作用域和'this'关键字。 当'this'关键字引用类的对象实例时,作为类的一部分的正常函数声明就像预期的那样。 更意外的是,app提供了所谓的'箭头'函数,其中'this'的含义被更改为引用封闭脚本。 你可能会问自己,这与在普通类函数中使用'this'有什么不同。 当在类中使用回调函数时,答案变得很清楚;例如在为事件声明回调函数时。 从类函数中获取这里示例。

使用JavaScript时,第二个'this'引用指定给事件的HTML元素,在本例中为'tcell',在使用面向对象的方法时,这将不容易获得对对象实例的引用。

历史记录

  • 上传的初始版本- 11/26/2012
  • 添加了'工作原理'节- 11/30/2012
  • 新控件 02/11/2013

我已经向这里套件添加了三个新控件:

  • 日期选择器- 允许用户使用月。日和年下拉列表选择日期
  • 菜单- ( 仅对当前水平) 层次菜单,具有无限级别
  • 上下文菜单- 具有无限级别的分层上下文菜单,使用鼠标右键单击以启动

菜单控件使用与默认菜单相同的菜单类,但是你可以告诉它是使用构造函数第二个参数的上下文菜单。

和这个套件中所有其他控件一样,它们来自同一基类,并且与所有其他控件类似,它们与其他控件类似。


相关文章