[译]使用KnockoutJS的股票投资组合应用程序
By robot-v1.0
本文链接 https://www.kyfws.com/applications/a-stock-portfolio-application-using-knockoutjs-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 40 分钟阅读 - 19622 个词 阅读量 0[译]使用KnockoutJS的股票投资组合应用程序
原文地址:https://www.codeproject.com/Articles/424656/A-Stock-Portfolio-Application-Using-KnockoutJS
原文作者:Bernardo Castilho
译文由本站 robot-v1.0 翻译
前言
An article that shows how to implement MVVM apps with KnockoutJS and custom controls.
这篇文章展示了如何使用KnockoutJS和自定义控件来实现MVVM应用程序.
- 下载演示项目(新的TypeScript版本)-83.5 KB(Download demo project (New TypeScript version) - 83.5 KB)
- 下载演示项目-78.5 KB(Download demo project - 78.5 KB)
介绍(Introduction)
随着平板电脑和智能手机的普及,开发人员面临着越来越大的需求,要求他们编写可在这些设备以及台式机上运行的应用程序. HTML5和JavaScript可以通过单个代码库帮助开发人员实现此目标.(As tablets and SmartPhones become more popular, developers face increased demand to write applications that can run on those devices as well as on desktops. HTML5 and JavaScript can help developers achieve this goal with a single code base.)
直到最近,使用JavaScript和HTML编写应用程序还是相当困难,因为开发人员必须创建所有业务逻辑,用户界面,然后使用JavaScript代码和HTML文档对象模型连接所有这些元素.相比之下,在Silverlight中编写Web应用程序要容易得多,因为逻辑和用户界面可以在很大程度上独立实现,然后使用声明性绑定进行连接.(Until recently, writing applications using JavaScript and HTML was quite difficult, because developers had to create all the business logic, the user interface, and then connect all these elements using JavaScript code and the HTML document object model. By comparison, writing web applications in Silverlight was much easier, because the logic and the user interface could be implemented largely independently, and later connected using declarative bindings.)
随着诸如jQuery,jQuery UI和KnockoutJS之类的JavaScript库的引入,这种情况开始改变.这些库引入了标准方法来处理HTML文档(jQuery),将用户界面小部件添加到HTML文档(jQueryUI),以及将业务逻辑以声明方式绑定到UI(KnockoutJS).(This situation started to change with the introduction of JavaScript libraries such as jQuery, jQuery UI, and KnockoutJS. These libraries introduce standard ways to manipulate the HTML document (jQuery), to add user interface widgets to HTML documents (jQueryUI), and to bind the business logic to the UI declaratively (KnockoutJS).)
在引入这些流行的JavaScript库的同时,最受欢迎的浏览器也逐渐增加了对HTML5功能的支持,例如本地存储,地理位置,富媒体等.这些功能也使HTML/JavaScript组合成为Web应用程序的绝佳平台.(In parallel with the introduction of these popular JavaScript libraries, mostpopular browsers are gradually adding support for HTML5 features such as local storage, geo-location, rich media, and others. These features also contributeto make the HTML/JavaScript combination a great platform for web applications.)
本文介绍了实现(This article describes the implementation of)入侵者(Invexplorer),类似于Google财务的股票投资组合应用程序.该应用程序是使用上述库以HTML5和JavaScript编写的.(, a stock portfolio application similar to Google finance. The application was written in HTML5 and JavaScript, using the libraries mentioned above.)
要在浏览器中运行该应用程序,请单击(To run the application in your browser, click) 这里(here) .(.)
用户可以使用智能自动完成文本框将股票添加到投资组合中.例如,如果他们键入" ford mot",则文本框将显示一个列表,其中包含两个与"福特汽车公司"相对应的符号.然后,他们可以选择一种符号,然后单击"添加到投资组合"按钮.将项目添加到投资组合后,用户可以通过单击复选框来绘制其价格历史记录,并可以通过直接在网格中键入来编辑交易信息.(Users can add stocks to the portfolio using a smart auto-complete textbox. If they type “ford mot” for example, the textbox will show a list with two symbols that correspond to the “Ford Motor Company”. They can then select one of the symbols and click the “Add to Portfolio” button. Once items have been added to the portfolio, users can chart their price history by clicking a checkbox, and can edit the trading information by typing directly into the grid.)
投资组合会自动保存到本地存储中.用户打开应用程序时,将自动加载其投资组合.(Portfolios are automatically persisted to local storage. When users open the application, their portfolio is loaded automatically.)
该应用程序遵循MVVM模式.视图模型类是使用(The application follows the MVVM pattern. The view model classes were implemented using the)淘汰赛(KnockoutJS)图书馆.它们可以独立于视图进行测试,并且可以重复使用,以防我们想要为平板电脑或手机创建新视图.属性以KnockoutJS" observable"和"(library. They can be tested independently of the view, and could be re-used in case we wanted to create new views for tablets or phones. Properties are implemented as KnockoutJS “observable” and “) observableArray
“对象,从而可以将它们用作KnockoutJS绑定中的源和目标.(” objects, which allows them to be used as sources and targets in KnockoutJS bindings.)
该视图是使用HTML5和两个自定义控件实现的:(The view was implemented using HTML5 and two custom controls: an auto-search box from the)jQueryUI(jQueryUI)库和来自(library and a chart control from the)维莫(Wijmo)图书馆.这两个库都具有支持KnockoutJS的扩展,并且都可以通过在HTML页面中包含适当的链接来简单地使用它们(无需下载/安装).(library. Both libraries have extensions that support KnockoutJS and both can be used simply by including the appropriate links in your HTML pages (no download/install is required).)
在本文结束前不久,CodeProject发表了Colin Eberhardt的出色文章,标题为” KnockoutJS vs. Silverlight". Colin专注于KnockoutJS,并使用了一个不需要自定义控件的简单示例.这篇文章很棒-绝对推荐阅读.你可以找到它(Shortly before this article was finished, CodeProject published an excellent article by Colin Eberhardt entitled “KnockoutJS vs. Silverlight”. Colin focuses on KnockoutJS, and uses a simple sample that requires no custom controls. The article is excellent - definitely recommended reading. You can find it) 这里(here) .(.)
如果您希望对Invexplorer应用程序的Silverlight和KnockoutJS实现进行比较,以及对JavaScript和MVVM的快速介绍,则可以发现(If you would like to see a comparison between Silverlight and KnockoutJS implementations of the Invexplorer application, along with a quick introduction to JavaScript and MVVM, you can find that) [这里(here)](http://publicfiles.componentone.com/Bernardo/MVVM in Silverlight and in HTML5 IX.pdf) .(.)
本文的最新版本添加了Invexplorer应用程序的TypeScript版本.本文最后一部分将讨论TypeScript版本.(The latest revision of this article added a TypeScript version of the Invexplorer application. The TypeScript version is discussed in the last section of the article.)
重要免责声明:(Important disclaimer:)
入侵者(Invexplorer)是一个示例应用程序,旨在显示如何使用KnockoutJS和自定义控件.它使用来自Yahoo Finance的财务数据,这不是免费服务.如果您想使用提供的代码作为实际应用程序的基础,则必须与Yahoo或其他金融数据提供商联系以获得所需的许可证.(is a sample application designed to show how to use KnockoutJS and custom controls. It uses financial data from Yahoo Finance, which is not a free service. If you would like to use the code provided as a basis for actual applications, you must contact Yahoo or some other financial data provider to obtain the licenses required.)## MVVM非常简短的介绍(Very Brief Introduction to MVVM)
MVVM模式(模型/视图/视图模型)是Microsoft引入的,是对传统MVC模式(模型/视图/控制器)的一种变体.(The MVVM pattern (Model/View/ViewModel) was introduced by Microsoft as a variation of the more traditional MVC pattern (Model/View/Controller).)
MVVM将应用程序逻辑封装在一组ViewModel类中,这些类公开了对View友好的对象模型.视图通常集中在用户界面上,并依赖于绑定将UI元素连接到ViewModel中的属性和方法.逻辑和标记之间的这种分离带来了以下重要好处:(MVVM encapsulates the application logic in a set of ViewModel classes that expose an object model that is View-friendly. Views typically focus on the user interface, and rely on bindings to connect UI elements to properties and methods in the ViewModel. This separation between logic and markup brings the following important benefits:)
- 可测性(Testability):ViewModel不包含任何用户界面元素,因此易于使用单元测试进行测试.(: The ViewModel does not contain any user interface elements, and is therefore easy to test using unit tests.)
- 关注点分离(Separation of Concerns):业务逻辑是使用C#或JavaScript等编程语言编写的.用户界面是使用XAML或HTML等标记语言编写的.每种类型的开发所需的技能和使用的工具都有根本的不同.将这些元素分开可以使团队开发更简单,更高效.(: Business logic is written using programming languages such as C# or JavaScript. User interfaces are written using markup languages such as XAML or HTML. The skills required and tools used for each type of development are fundamentally different. Separating these elements makes team development simpler and more efficient.)
- 多目标应用(Multi-Target Applications):将应用程序的业务逻辑封装到ViewModel类中,可以更轻松地开发针对多个设备的应用程序的多个版本.例如,可以为台式机,平板电脑和电话设备开发单个ViewModel和不同的视图.(: Encapsulating an application’s business logic into ViewModel classes makes it easier to develop several versions of an application, targeting multiple devices. For example, one could develop a single ViewModel and different views for desktop, tablet, and phone devices.) 下图说明了MVVM应用程序中元素之间的关系(该图摘自一个很好的MVVM入门,可以找到(The diagram below illustrates the relationship between elements in MVVM applications (the diagram was taken from a good MVVM introduction that can be found) 这里(here) ):(.):)
KnockoutJS非常简短的介绍(Very Brief Introduction to KnockoutJS)
淘汰赛(KnockoutJS)通过提供两个主要元素来实现MVVM开发:(enables MVVM development by providing two main elements:)
- JavaScript类,例如(JavaScript classes such as)可观察的(observable)和(and)observableArray(observableArray),用于实现ViewModel变量,该变量在值更改时发出通知(类似于(, which are used to implement ViewModel variables that issue notifications when their value changes (similar to)
INotifyPropertyChanged
.NET开发中).(in .NET development).) - 引用这些可观察对象的HTML标记扩展,并在其值更改时自动更新页面.标记扩展非常丰富.除了显示数字和字符串之类的值之外,它们还可以用于自定义样式,启用或禁用UI元素以及表示集合的元素(如列表,网格或图表).标记扩展名类似于(HTML markup extensions that refer to these observables and automatically update the page when their values change. The markup extensions are very rich. In addition to showing values such as numbers and strings, they can be used to customize styles, to enable or disable UI elements, and to elements that represent collections such as lists, grids, or charts. The markup extensions are similar to)捆绑(Binding)XAML开发中的对象.(objects in XAML development.) 要使用KnockoutJS开发应用程序,首先要创建包含应用程序逻辑的ViewModel类,并公开由可观察对象构建的对象模型.在继续进行下一步(创建视图)之前,可以测试这些类.(To develop applications using KnockoutJS, you start by creating ViewModel classes that contain the application logic and expose an object model built of observable objects. These classes can be tested before moving on to the next step, which is creating the View.)
该视图是使用HTML和CSS创建的.唯一的区别是增加了标记扩展,例如"数据绑定:文本"(以显示静态内容)或"数据绑定:值"(在文本框等元素中创建双向绑定).(The View is created using HTML and css. The only difference is the addition of markup extensions such as “data-bind: text” (to show static content) or “data-bind: value” (to create two-way bindings in elements such as text boxes).)
最后,通过一次调用KnockoutJS将ViewModel和View连接起来(Finally, the ViewModel and the View are connected with a single call to the KnockoutJS) applyBindings
方法,该方法将对象模型作为参数并实际构建绑定.(method, which takes the object model as a parameter and actually builds the bindings.)
简而言之,这就是KnockoutJS.我认为,这种概念上的简单性是图书馆的主要优势之一.但是还有很多. KnockoutJS官方网站上有许多示例,教程和出色的文档.您可以在以下位置获取所有详细信息(This is KnockoutJS in a nutshell. In my opinion, this conceptual simplicity is one of the library’s main strengths. But there’s a lot more to it. The official KnockoutJS site has many samples, tutorials, and great documentation. You can get all the details at) kickoutjs.com(knockoutjs.com) .(.)
Invexplorer示例应用程序(The Invexplorer Sample Application)
的(The)入侵者(Invexplorer)应用程序大致基于Google财经网站.它允许用户选择股票并将其添加到投资组合中.将股票添加到投资组合后,该应用程序将检索历史价格数据并在图表上显示价格的演变.用户可以选择要绘制图表的股票以及在什么时期内.用户还可以输入他为股票支付的价格和购买的金额.如果提供了此信息,则应用程序将计算每个项目的退货.(application was loosely based on the Google Finance site. It allows users to select stocks and add them to portfolios. Once a stock is added to the portfolio, the application retrieves historical price data and shows the evolution of the prices on a chart. The user may select which stocks should be charted and over what period. The user may also enter the prices he paid for the stocks and the amount purchased. If this information is provided, the application calculates the return on each item.)
项目组合信息会保留下来,因此当用户退出应用程序时,他们的编辑不会丢失,并且可以在应用程序再次运行时自动重新加载.(The portfolio information is persisted so when users quit the application so their edits are not lost and can be re-loaded automatically when the application runs again.)
的原始版本(The original version of the)入侵者(Invexplorer)应用程序是使用MVVM模式用Silverlight编写的(您可以看到Silverlight版本(application was written in Silverlight, using the MVVM pattern (you can see the Silverlight version) 这里(here) ).引入KnockoutJS时,我们认为它是移植HTML5/JavaScript的理想选择.(). When KnockoutJS was introduced, we thought it would be an ideal candidate for a port to HTML5/JavaScript.)
移植应用程序仅花了几天时间.大部分工作都是使用KnockoutJS库将属性实现为可观察对象,从而用JavaScript重写视图模型类.编写视图非常容易,因为我们很容易找到所需的控件:用于选择新股票的自动搜索框是jQueryUI库的一部分,而我们使用的图表是Wijmo库的一部分.这两个控件库都支持KnockoutJS,并且都稳定,强大且易于使用.(Porting the application took only a couple of days. Most of the work went into re-writing the view model classes in JavaScript, using the KnockoutJS library to implement properties as observable objects. Writing the view was very easy, because the controls we needed were easy to find: the auto-search box used to select new stocks is part of the jQueryUI library, and the chart we used is part of the Wijmo library. Both control libraries support KnockoutJS, and both are stable, powerful, and easy-to-use.)
查看模型实施(JavaScript)(View Model Implementation (JavaScript))
下面的类图显示了实现视图模型的类:(The class diagram below shows the classes that implement the view model:)
每个类的主要属性如下:(The main properties in each class are described below:)
ViewModel
类的主要属性:(class main properties:)
-
companies
:一组(: An array of)Company
包含股票代码,公司名称和历史价格数据的完整列表的对象.创建模型时,列表中会填充符号和名称,并根据需要检索价格数据.(objects that contains the complete list of ticker symbols, company names, and historical price data. The list is populated with symbols and names when the model is created, and the price data is retrieved on demand.) -
portfolio
:包含投资组合项目列表的投资组合对象.(: A Portfolio object that contains a list of portfolio items.) -
chartSeries
,(,)chartStyles
:两个(: Two)observableArray
包含准备在折线图控件上显示的数据的对象.的(objects that contain data prepared for display on a line chart control. The)chartSeries
数组包含价格变化而不是绝对值,因此可以轻松比较不同公司的序列.的(array contains price variations rather than absolute values, so series for different companies can be compared easily. The)chartStyles
数组包含CSS样式定义,用于指定每个数据系列使用的颜色.(array contains CSS style definitions used to specify the color to be used for each data series.) -
minDate
:一个可观察的日期对象,用于定义图表的开始日期.这使用户可以比较特定时期(例如今年,过去12个月等)内的不同库存.(: An observable date object that defines the starting date for the chart. This allows users to compare different stocks over specific periods such as this year, last 12 months, etc.) -
updating
,(,)chartVisible
:两个可观察的对象,它们公开视图模型的状态,并允许视图在加载数据时向用户提供反馈,并在图表为空时隐藏与图表相关的元素.(: Two observable objects that expose the state of the view model and enable the view to provide user feedback while data is being loaded and to hide chart-related elements when the chart is empty.)Company
类的主要属性:(class main properties:) -
symbol
:该公司的交易代码.(: The trading symbol for this company.) -
name
:完整的公司名称.(: The full company name.) -
prices
:一个包含日期/价格对象数组的可观察对象.请注意,这不是(: An observable containing an array of date/price objects. Note that this is not an)observableArray
目的;而是一个常规可观察对象,其值是常规数组.此阵列按需填充,每个公司一次.(object; rather it is a regular observable object whose value is a regular array. This array is filled on demand, one time per company.) -
chartSeries
:一个包含自定义对象的可观察对象,该对象用于为图表控件定义单个系列.此自定义对象指定系列x和y值以及系列标签,标记,以及是否应在图表图例上显示它.当价格数组被填充时,以及(: An observable containing a custom object used to define a single series for the chart control. This custom object specifies the series x and y values as well as the series label, markers, and whether it should be displayed on the chart legend. This array is created and refreshed when the prices array is filled and also when the)minDate
父级ViewModel的属性更改值.(property of the parent ViewModel changes value.)Portfolio
类的主要属性:(class main properties:) -
items
:一个(: An)observableArray
包含(containing)PortfolioItem
对象.每个投资组合项目均指一家公司,并且可以包括交易数据,例如购买的股票数量和购买价格.(objects. Each portfolio item refers to a company, and may include transaction data such as the number of shares purchased and the purchase price.) -
newSymbol
:包含要添加到投资组合的股票代码的可观察对象.该值绑定到"视图"中的自动搜索文本框,该文本框允许用户选择要添加到投资组合中的股票.(: An observable containing a stock symbol to be added to the portfolio. This value is bound to an auto-search textbox in the View which allows users to select stocks they want to add to the portfolio.) -
canAddNewSymbol
:包含布尔值的可观察对象,该布尔值确定(: An observable containing a Boolean value that determines whether the)newSymbol
属性当前包含当前投资组合中尚未包含的有效符号.此变量用于启用或禁用用于向项目集合添加新项目的UI元素.(property currently contains a valid symbol that is not already included in the current portfolio. This variable is used to enable or disable the UI element used to add new items to the items collection.)PortfolioItem
类的主要属性:(class main properties:) -
symbol
:此项目代表的公司的交易代码.此值用作ViewModel公司列表的键.(: Trading symbol of the company that this item represents. This value is used as a key into the ViewModel’s companies array.) -
company
:包含公司信息(包括名称和价格历史记录)的Company对象.(: The Company object that contains the company information including name and price history.) -
lastPrice
,(,)change
:这些是可观察对象,最初设置为null,并在公司的价格历史记录可用时更新.(: These are observable objects that are initially set to null, and are updated when the price history for the company becomes available.) -
shares
,(,)unitCost
:这些是可以由用户编辑的可观察对象.这些值代表用户交易,并用于计算每个投资组合项目的成本.这些值属于投资组合项目本身.(: These are observable objects that can be edited by the user. These values represent the user transactions, and are used to calculate the cost of each portfolio item. These values belong to the portfolio item itself.) -
chart
:一个可观察的布尔变量,用于确定是否应在图表中包括此项.(: An observable Boolean variable that determines whether this item should be included in the chart.) -
value
,(,)cost
,(,)gain
,等等:提供一些基于其他属性计算的值的多个计算对象.例如,值=份额* lastPrice.(*, etc: Several computed objects that provide values that are calculated based on other properties. For example, value = shares * lastPrice.*) 的构造函数(*The constructor of the*)ViewModel
该类的实现如下:(*class is implemented as follows:*)
/***********************************************************************************
* ViewModel class.
* @constructor
*/
function ViewModel() {
var self = this;
// object model
this.companies = [];
this.updating = ko.observable(0);
this.minDate = ko.observable(null);
this.chartSeries = ko.observable([]);
this.chartStyles = ko.observable([]);
this.chartHoverStyles = ko.observable([]);
this.chartVisible = ko.observable(false);
this.setMinDate(6); // chart 6 months of data by default
this.minDate.subscribe(function () { self.updateChartData() });
this.portfolio = new Portfolio(this);
// create color palette
this.palette = ["#FFBE00", "#C8C800", …];
构造函数的第一部分声明了上述属性.请注意(The first part of the constructor declares the properties that were described above. Notice that the) minDate
属性是可观察的,并且(property is an observable and the) ViewModel
类订阅它.当值(class subscribes to it. When the value of the) minDate
属性更改,(property changes, the) ViewModel
来电(calls) updateChartData
因此,将重新生成图表以反映用户请求的日期范围.(so the chart is re-generated to reflect the date range requested by the user.)
的(The)调色板(palette)属性包含一个颜色数组,用于创建每个项目的图表系列.颜色也显示在网格中,该网格充当图表的图例.将这种与UI纯粹相关的元素添加到ViewModel类中是很常见的.毕竟,存在ViewModel来驱动视图(这就是为什么它们称为" ViewModels",而不仅仅是" Models")的原因.(property contains an array with colors used to create the chart series for each item. The colors are also displayed in the grid, which acts as a legend for the chart. Adding this type of purely UI-related elements to ViewModel classes is fairly common. After all, ViewModels exist to drive views (that is why they called “ViewModels”, and not just “Models”).)
声明属性后,构造函数将填充(Once the properties have been declared, the constructor populates the) companies
数组如下:(array as follows:)
// populate companies array
$.get("StockInfo.ashx", function (result) {
var lines = result.split("\r");
for (var i = 0; i < lines.length; i++) {
var items = lines[i].split("\t");
if (items.length == 2) {
var c = new Company(self, $.trim(items[0]), $.trim(items[1]));
self.companies.push(c);
}
}
// load/initialize the portfolio after loading companies
self.portfolio.loadItems();
});
该代码使用jQuery(The code uses the jQuery) get
调用名为"(method to invoke a service called “)StockInfo.ashx(StockInfo.ashx)",它是Invexplorer应用程序的一部分.该服务异步执行并返回公司符号和名称的列表,该列表已解析并添加到(”, which is part of the Invexplorer application. The service executes asynchronously and returns list of company symbols and names which is parsed and added to the) companies
数组.(array.)
.NET开发人员:(.NET Developers:)注意使用" self"变量从局部函数内部访问ViewModel类.在此范围内,此变量引用内部函数本身,而不是ViewModel.这是一种常见的JavaScript技术.(Notice the use of the “self” variable to access the ViewModel class from within local functions. In this scope, the variable this refers to the inner function itself, not to the ViewModel. This is a common JavaScript technique.)
加载公司数据后,构造函数会调用投资组合的(After the company data has been loaded, the constructor calls the portfolio’s) loadItems
从本地存储加载最后保存的投资组合的方法.为了使它起作用,必须在用户关闭应用程序时保存投资组合.这是在构造函数的最后一个块中完成的:(method to load the last saved portfolio from local storage. In order for this to work, the portfolio must be saved when the user closes the application. This is done in the last block of the constructor:)
// save portfolio when window closes
$(window).unload(function () {
self.portfolio.saveItems();
});
}
此代码使用jQuery连接到窗口的(This code uses jQuery to connect to the window’s) unload
事件,当用户关闭应用程序时调用.此时,投资组合的(event, which is called when the user closes the application. At this point, the portfolio’s) saveItems
方法将被调用,当前的投资组合将保存到本地存储中.(method is called and the current portfolio is saved to local storage.)
的(The) saveItems
和(and) loadItems
方法是(methods are part of the) Portfolio
类.在展示它们的实现之前,这里是(class. Before we show their implementation, here is the) Portfolio
构造函数:(constructor:)
/***********************************************************************************
* Portfolio class.
* @constructor
*/
function Portfolio(viewModel) {
var self = this;
this.viewModel = viewModel;
this.items = ko.observableArray([]);
this.newSymbol = ko.observable("");
this.newSymbol.subscribe(function () { self.newSymbolChanged() });
this.canAddSymbol = ko.observable(false);
}
构造函数保留对父级的引用(The constructor keeps a reference to the parent)视图模型(ViewModel),创建将包含投资组合项目的项目可观察数组,并声明一个(, creates the items observable array that will contain the portfolio items, and declares a) newSymbol
可观察的.的(observable. The) newSymbol
属性包含要添加到投资组合中的公司的符号.(property contains the symbol of a company to be added to the portfolio.)
的(The) constructor
订阅(subscribes to changes in the) newSymbol
属性,因此只要其值发生变化,(property so whenever its value changes, the) newSymbolChanged
方法被调用.的(method is called. The) newSymbolChanged
方法依次设置(method in turn sets the value of the) canAddSymbol
如果新符号有效且与投资组合中已有的任何项目都不对应,则将属性设置为true.的(property to true if the new symbol is valid and does not correspond to any of the items already in the portfolio. The) newSymbol
和(and) canAddSymbol
视图使用属性来允许用户向项目组合添加项目.(properties are used by the view to allow users to add items to the portfolio.)
以下是从本地存储中保存和加载项目组合项的方法:(And here are the methods that save and load portfolio items from local storage:)
// saves the portfolio to local storage
Portfolio.prototype.saveItems = function () {
if (localStorage != null) {
// build array with items
var items = [];
for (var i = 0; i < this.items().length; i++) {
var item = this.items()[i];
var newItem = {
symbol: item.symbol,
chart: item.chart(),
shares: item.shares(),
unitCost: item.unitCost()
};
items.push(newItem);
}
// save array to local storage
localStorage["items"] = JSON.stringify(items);
}
}
.NET开发人员:(.NET Developers:)注意使用(Notice the use of the) Portfolio.prototype.saveItems
用于定义方法的语法. “(syntax used to define the method. The “) prototype
“关键字将方法附加到(” keyword attaches the method to every instance of the) Portfolio
类.这是在JavaScript中实现对象的常用方法.(class. This a common way to implement objects in JavaScript.)
该方法首先检查(The method starts by checking that the) localStorage
对象已定义.这是所有现代浏览器中都可用的HTML5功能.然后,它将构建一个数组,其中包含每个投资组合项目(符号,图表,股票和unitCost)应保留的信息.最后,使用(object is defined. This is an HTML5 feature available in all modern browsers. Then it builds an array containing the information that should be persisted for each portfolio item (symbol, chart, shares, and unitCost). Finally, the array is converted into a string using the) JSON.stringify
方法并保存到"项"键下的存储中.(method and saved to storage under the key “items”.)
的(The) loadItems
方法从本地存储中读取以下信息:(method reads this information back from local storage:)
// loads the portfolio from local storage (or initializes it with a few items)
Portfolio.prototype.loadItems = function () {
// try loading from local storage
var items = localStorage != null ? localStorage["items"] : null;
if (items != null) {
try {
items = JSON.parse(items);
for (var i = 0; i < items.length; i++) {
var item = items[i];
this.addItem(item.symbol, item.chart, item.shares, item.unitCost);
}
}
catch (err) { // ignore errors while loading...
}
}
// no items? add a few now
if (this.items().length == 0) {
this.addItem("AMZN", false, 100, 200);
this.addItem("YHOO", false, 100, 15);
}
}
该方法首先从中检索” items"字符串(The method starts by retrieving the “items” string from) localStorage
.它使用(. It uses the) JSON.parse
将字符串转换为JavaScript数组对象,然后循环遍历数组中的项目的方法,调用(method to convert the string into a JavaScript array object and then loops through the items in the array calling the) addItem
每个方法.(method on each one.)
.NET开发人员:(.NET Developers:)的(The) JSON.stringify
和(and) JSON.parse
方法与.NET序列化程序的JavaScript类似.提供了一种简单的方法来将对象与字符串进行相互转换.(methods are the JavaScript analogous of .NET serializers. The provide a simple way to convert objects into and from strings.)
的(The) addItem
方法实现如下:(method is implemented as follows:)
// add a new item to the porfolio
Portfolio.prototype.addItem = function (symbol, chart, shares, unitCost) {
var item = new PortfolioItem(this, symbol, chart, shares, unitCost);
this.items.push(item);
}
的(The) PortfolioItem
类代表投资组合中的项目.这是构造函数:(class represents items in the portfolio. Here is the constructor:)
/***********************************************************************************
* PortfolioItem class.
* @constructor
*/
function PortfolioItem(portfolio, symbol, chart, shares, unitCost) {
var self = this;
this.portfolio = portfolio;
this.symbol = symbol;
// observables
this.lastPrice = ko.observable(null);
this.change = ko.observable(null);
// editable values
this.shares = ko.observable(shares == null ? 0 : shares);
this.unitCost = ko.observable(unitCost == null ? 0 : unitCost);
this.chart = ko.observable(chart == null ? false : chart);
this.shares.subscribe(function () { self.parametersChanged() });
this.unitCost.subscribe(function () { self.parametersChanged() });
this.chart.subscribe(function () { self.updateChartData() });
// find company
this.company = portfolio.viewModel.findCompany(symbol);
if (this.company != null) {
this.company.prices.subscribe(function () { self.pricesChanged() });
this.pricesChanged();
this.company.updatePrices();
}
// computed observables
this.name = ko.computed(function() {…}, this);
this.value = ko.computed(function () {…}, this);
this.cost = ko.computed(function () {…}, this);
this.gain = ko.computed(function () {…}, this);
this.color = ko.computed(this.getColor, this);
// finish initialization
this.updateChartData();
this.parametersChanged();
}
构造函数首先存储对父产品组合和公司符号的引用.然后它声明(The constructor starts by storing a reference to the parent portfolio and to the company symbol. Then it declares the) lastPrice
和(and) change
属性,两个可观察值,稍后将通过Web服务获得.(properties, two observables which will be obtained later from the web service.)
接下来,构造函数声明用户可以更改的属性:(Next, the constructor declares the properties that can be changed by the user:) chart
,(,) unitCost
和(, and) shares
.请注意,构造函数还如何订阅这些可观察对象,因此,当用户编辑这些值中的任何一个时,项目组合项目将调用更新项目参数或图表数据所需的方法.(. Notice how the constructor also subscribed to these observables so when the user edits any of these values, the portfolio item invokes the methods needed to update the item parameters or the chart data.)
的(The) pricesChanged
当该物料的公司的价格历史记录可用时调用的方法实现如下:(method, invoked when the price history for the item’s company becomes available, is implemented as follows:)
PortfolioItem.prototype.pricesChanged = function () {
var prices = this.company.prices();
if (prices.length > 1) {
this.lastPrice(prices[0].price);
this.change(prices[0].price - prices[1].price);
if (this.chart()) {
this.updateChartData();
}
}
}
该方法从(The method retrieves the price history from the) company.prices
属性.如果价格历史记录已经存在,则代码将更新(property. If the price history is already available, then the code updates the value of the) lastPrice
和(and) change
属性.如果该项目当前配置为显示在图表上,则该方法将调用(properties. If the item is currently configured to appear on the chart, the method calls) updateChartData
,这称为(, which calls the) updateChartData
上的方法(method on the) ViewModel
类:(class:)
PortfolioItem.prototype.updateChartData = function () {
var vm = this.portfolio.viewModel;
vm.updateChartData();
}
这是实施(Here is the implementation of the) updateChartData
方法:(method:)
// update chart data when min date changes
ViewModel.prototype.updateChartData = function () {
// start with empty lists
var seriesList = [], stylesList = [], hoverStylesList = [];
// add series and styles to lists
var items = this.portfolio.items();
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.chart()) {
var series = item.company.updateChartData();
seriesList.push(series);
var style = { stroke: item.getColor(), 'stroke-width': 2 };
stylesList.push(style);
var hoverStyle = { stroke: item.getColor(), 'stroke-width': 4 };
hoverStylesList.push(hoverStyle);
}
}
// update chartVisible property
this.chartVisible(seriesList.length > 0);
// update chartSeries and styles
this.chartStyles(stylesList);
this.chartHoverStyles(hoverStylesList);
this.chartSeries(seriesList);
}
的(The) updateChartData
数据方法遍历投资组合项目.对于chart属性设置为true的每个项目,代码将创建一个新的图表系列,一个新的图表样式以及一个新的图表"悬停样式”.所有这些对象都添加到数组中.(data method loops through the portfolio items. For each item with the chart property set to true, the code creates a new chart series, a new chart style, and a new chart ‘hover style’. All these objects are added to arrays.)
循环完成后,将使用数组更新(When the loop is complete, the arrays are used to update the) chartStyles
,(,) chartHoverStyles
和(, and) chartSeries
属性.这些都是绑定到视图中的图表控件的可观察属性.(properties. These are all observable properties bound to the chart control in the view.)
的(The) series
变量包含要在图表上绘制的实际数据.它是由(variable contains the actual data to be plotted on the chart. It is calculated by the) updateChartData
的方法(method of the) Company
类:(class:)
// update data to chart for this company
Company.prototype.updateChartData = function () {
var xData = [], yData = [];
// loop through prices array
var prices = this.prices();
for (var i = 0; i < prices.length; i++) {
// honor min date
if (prices[i].day < this.viewModel.minDate()) {
break;
}
// add this point
xData.push(prices[i].day);
yData.push(prices[i].price);
}
// convert to percentage change from first value
var baseValue = yData[yData.length - 1];
for (var i = 0; i < yData.length; i++) {
yData[i] = yData[i] / baseValue - 1;
}
// return series object with x and y values
var series = {
data: { x: xData, y: yData },
label: this.symbol,
legendEntry: false,
markers: { visible: false }
};
return series;
}
该代码将历史价格转换为百分比变化,从而使比较同一图表上显示的多家公司的绩效变得更加容易.计算出的值被包装到"系列"对象中,该对象包含Wijmo图表控件期望的属性,这些属性将用于显示数据.(The code converts the historical prices into percentage changes, making it easier to compare the performance of multiple companies shown on the same chart. The values calculated are wrapped into a “series” object that contains properties expected by the Wijmo chart control that will be used to show the data.)
我们最后一个有趣的方法(The last interesting method in our) ViewModel
类别是加载历史价格数据的类别.该方法如下:(class is the one that loads the historical price data. This method is given below:)
// get historical prices for this company
Company.prototype.updatePrices = function () {
var self = this;
// don't have prices yet? go get them now
if (self.prices().length == 0) {
// go get prices
var vm = self.viewModel;
vm.updating(vm.updating() + 1);
$.get("StockInfo.ashx?symbol=" + self.symbol, function (result) {
// got them
vm.updating(vm.updating() - 1);
// parse result
var newPrices = [];
var lines = result.split("\r");
for (var i = 0; i < lines.length; i++) {
var items = lines[i].split("\t");
if (items.length == 2) {
var day = new Date($.trim(items[0]));
var price = $.trim(items[1]) * 1;
var item = { day: day, price: price };
newPrices.push(item);
}
}
// update properties
self.prices(newPrices);
// update chart series data
self.updateChartData();
});
} else {
// same data, different min date
self.updateChartData();
}
}
该方法首先检查是否已为该公司加载价格.如果还没有,则该方法会递增(The method starts by checking whether the prices have already been loaded for this company. If they haven’t, the method increments the) updating
属性(因此UI可以确保正在进行下载活动).(property (so the UI can that there is download activity going on).)
然后,该方法会调用"(The method then calls the “)StockInfo.ashx(StockInfo.ashx)",则再次传递股票代码作为参数.服务返回时,(” service again, this time passing the stock symbol as a parameter. When the service returns, the) updating
属性递减,并将返回的值解析为(property is decremented and the value returned is parsed into a) newPrices
数组.最后,该方法更新prices属性的值并更新图表数据.(array. Finally, the method updates the value of the prices property and updates the chart data.)
查看实施(HTML/CSS)(View Implementation (HTML/CSS))
该视图在(The View is implemented in the)**default.html(default.html)**文件.该文件以包含语句块开头,该语句将加载应用程序使用的库.这类似于在.NET项目中添加引用.的(file. The file starts with a block of include statements which load the libraries used by the application. This is similar to adding references in .NET projects. The)入侵者(Invexplorer)应用程序使用以下库:(application uses the following libraries:)
- 淘汰赛(KnockoutJS):JavaScript应用程序的数据绑定.(: data binding for JavaScript applications.)
- jQuery的(jQuery):用于处理DOM和调用Web服务的实用程序.(: utilities for manipulating the DOM and calling web services.)
- jQueryUI(jQueryUI):UI控件,包括自动搜索框.(: UI controls including the auto-search box.)
- 维莫(Wijmo):UI控件,包括折线图控件.(: UI controls including the line chart control.)
- 淘汰赛jQuery(Knockout-jQuery)和(and)淘汰赛(Knockout-Wijmo):将KnockoutJS支持添加到jQuery和Wijmo控件库的库.(: libraries that add KnockoutJS support to the jQuery and Wijmo control libraries.)
- 视图模型(ViewModel):(: the)入侵者(Invexplorer)查看上述模型类.(view model classes described above.) 此项目中使用的Knockout-jQuery库由Mike Edmunds编写,可从以下网站获得(The Knockout-jQuery library used in this project was written by Mike Edmunds and is available from) 的github(github) . Knockout-Wijmo库包含在Wijmo中,可以直接从(. The Knockout-Wijmo library is included with Wijmo and can be included directly from the) Wijmo CDN(Wijmo CDN) .(.)
除了这些内容之外,该视图还包含一个小的脚本块,该脚本块执行以下两项操作:(In addition to these includes, the view includes a small script block that does two things:)
- 实例化(Instantiates the)
ViewModel
并应用绑定(使用KnockoutJS(and applies the bindings (using the KnockoutJS)applyBindings
方法)(method)) - 配置jQueryUI(Configures the jQueryUI)
autoComplete
控件在下拉列表中显示HTML而不是纯文本.这是可选步骤,与(control to show HTML in the dropdown list instead of plain text. This is an optional step, not related to the)入侵者(Invexplorer)应用.它使我们能够在自动完成列表中突出显示部分匹配项.(application. It allows us to highlight partial matches in the auto-complete list.)
<script type="text/javascript">
// initialize application on page load
$(function () {
// create ViewModel and apply bindings
var vm = new ViewModel();
ko.applyBindings(vm);
// configure auto-complete control to render html instead of plain text
// http://stackoverflow.com/questions/3488016/using-html-in-jquery-ui-autocomplete
$("#autoComplete").autocomplete().data("autocomplete")._renderItem =
function (ul, item) {
return $("<li></li>")
.data("item.autocomplete", item)
.append("<a>" + item.label + "</a>")
.appendTo(ul);
};
});
</script>
的身体(The body of the)**default.html(default.html)**页面以显示标题和有关应用程序的一些信息的标题开头.标头下方是投资组合表,它是标准的HTML表格元素,定义如下:(page starts with a header that displays a title and some information about the application. Below the header comes the portfolio table, which is a standard HTML table element defined as follows:)
<!-- portfolio table -->
<table>
<!-- table header -->
<thead>
<tr>
<th class="left">Name</th>
<th class="left">Symbol</th>
<th class="left">Chart</th>
<th class="right">Last Price</th>
<th class="right">Change</th>
<th class="right">Change %</th>
<th class="right">Shares</th>
<th class="right">Unit Cost</th>
<th class="right">Value</th>
<th class="right">Gain</th>
<th class="right">Gain %</th>
<th class="center">Delete</th>
</tr>
</thead>
表标题只是为表中的每一列指定标题.有趣的部分是表主体,它使用KnockoutJS绑定,如下所示:(The table header simply specifies the header for each column on the table. The interesting part is the table body, which uses KnockoutJS bindings as follows:)
<!-- table body-->
<tbody data-bind="foreach: portfolio.items">
<tr>
<td>
<span data-bind="style: { backgroundColor: color }">
</span>
<span data-bind="text: name"></span></td>
<td data-bind="text: symbol"></td>
<td class="center">
<input data-bind="checked: chart" type="checkbox" /></td>
<td class="right" data-bind="text: Globalize.format(lastPrice(), 'n2')"></td>
<td class="right" data-bind="text: Globalize.format(change(), 'n2'),
style: { color: $root.getAmountColor(change()) }"></td>
<td class="right" data-bind="text: Globalize.format(changePercent(), 'p2'),
style: { color: $root.getAmountColor(changePercent()) }"></td>
<td><input class="numeric" data-bind="value: shares" /></td>
<td><input class="numeric" data-bind="value: unitCost" /></td>
<td class="right" data-bind="text: Globalize.format(value(), 'n2')"></td>
<td class="right" data-bind="text: Globalize.format(gain(), 'n2'),
style: { color: $root.getAmountColor(gain()) }"></td>
<td class="right" data-bind="text: Globalize.format(gainPercent(), 'p2'),
style: { color: $root.getAmountColor(gainPercent()) }"></td>
<td class="center">
<a class="hlink" data-bind="click: $root.portfolio.removeItem">x</a></td>
</tr>
</tbody>
</table>
的(The) tbody
元素指定表的数据源.在这种情况下,源是投资组合项目属性.以下(element specifies the data source for the table. In this case, the source is the portfolio items property. Below the) tbody
元素,我们指定一行((element, we specify a single row () tr
元素)与几个单元格((element) with several cells () td
元素).每个单元格包含一个(elements). Each cell contains a) data-bind
该属性指定将显示的数据,在某些情况下,还用于指定用于显示单元格的格式和颜色.(attribute that specifies the data it will display and in some cases the formatting and the color to be used for showing the cells.)
最常见的绑定是"数据绑定:文本",它指定单元格的内容.内容可以是任何JavaScript表达式,包括对(The most common binding is “data-bind: text”, which specifies the content of the cell. The content can be any JavaScript expression, including calls to the)全球化(Globalize)图书馆.同样,"(library. Similarly, the “) data-bind: value
“绑定用于在文本框中显示可编辑的值.(” binding is used to display editable values in text boxes.)
另一个常见的绑定是"数据绑定:样式”,它允许您指定呈现单元格时要使用的CSS元素.上表使用样式绑定以绿色显示正数,以红色显示负数.可以通过调用(Another common binding is “data-bind: style”, which allows you to specify CSS elements to be used when rendering the cell. The table above uses style bindings to show positive amounts in green and negative amounts in red. This is done with a call to the) getAmountColor
方法,在XAML中扮演绑定转换器的角色.(method, which plays the role of a binding converter in XAML.)
最后,“数据绑定:单击"用于创建带有按钮的列,这些按钮可用于从项目组合中删除项目.点击事件绑定到(Finally, the “data-bind: click” is used to create a column with buttons that can be used to remove items from the portfolio. The click event is bound to the) portfolio.removeItem
方法,该方法将被调用并自动接收一个参数,该参数指定被单击的项目.(method, which is invoked and automatically receives a parameter that specifies the item that was clicked.)
使用KnockoutJS构建HTML表与在XAML中构建数据网格非常相似.(Building HTML tables with KnockoutJS is very similar to building data grids in XAML.)
投资组合表下方是允许用户向投资组合中添加项目的部分.这是使用jQueryUI自动完成控件和常规HTML按钮实现的:(Below the portfolio table comes the section that allows users to add items to the portfolio. This is implemented using a jQueryUI auto-complete control and a regular HTML button:)
<!-- add symbol -->
<div class="addSymbol">
Add Symbol:
<!-- jQueryUI autocomplete -->
<input id="autoComplete" type="text" data-bind="
value: portfolio.newSymbol,
jqueryui: {
widget: 'autocomplete',
options: {
/* require two characters to start matching */
minLength: 2,
/* use ViewModel's getSymbolMatches to populate drop-down */
source: function(request, response) {
response(getSymbolMatches(request))
},
/* update current portfolio's newSymbol property when drop-down closes */
close: function() {
portfolio.newSymbol(this.value)
}
}
}" />
<!-- add the selected symbol to the portfolio -->
<button data-bind="
click: function() { portfolio.addNewSymbol()},
enable: portfolio.canAddNewSymbol">
Add to Portfolio
</button>
<!-- progress indicator (visible when ViewModel.updating != 0) -->
<span class="floatRight" data-bind="visible: updating">
<i> getting data...</i>
</span>
</div>
input元素从jQueryUI库获取自动完成行为.我们使用数据绑定来指定有效选择的列表以及用户进行选择时要采取的操作.(The input element gets the auto-complete behavior from the jQueryUI library. We use data binding to specify the list of valid choices and the action to be taken when the user makes a selection.)
的(The) source
选项指定有效选择的列表将由(option specifies that the list of valid choices will be provided by the) getSymbolMatches
的方法(method of the) ViewModel
类.此方法采用用户提供的输入(例如” gen mot"),并返回名称或符号中包含这些术语的公司的列表(在这种情况下,匹配项为" General Motors").返回的值是HTML,因此匹配项在"自动完成"下拉列表中突出显示.(class. This method takes the input provided by the user (for example “gen mot”) and returns a list of companies that have those terms in their name or symbol (in this case, a match would be “General Motors”). The values returned are HTML, so matches are highlighted in the auto-complete drop-down.)
的(The) close
option指定当用户从列表中选择一个项目时调用的方法.在这种情况下,该方法设置投资组合的价值(option specifies a method that is invoked when the user picks an item from the list. In this case, the method sets the value of the portfolio’s) newSymbol
属性.回想一下,设置此值将自动更新(property. Recall that setting this value will automatically update the value of the) canAddNewSymbol
属性,在下一个绑定中使用.(property, which is used in the next binding.)
绑定控件涉及在HTML中设置选项和属性值.这类似于在XAML中设置属性值.(Binding controls involves setting options and property values in HTML. This is similar to setting property values in XAML.)
在input元素旁边有一个带有两个绑定的按钮:click绑定调用(Next to the input element there is a button with two bindings: the click binding invokes the) addNewSymbol
投资组合类别中的方法;启用绑定可确保仅当(method in the portfolio class; the enable binding ensures the button is enabled only when the) canAddNewSymbol
属性设置为true(当选择一个符号并且该符号尚未包含在投资组合中时会发生).这些绑定起到了(property is set to true (which happens when a symbol is selected and if this symbol is not yet included in the portfolio). These bindings play the role of the) ICommand
XAML中的接口.(interface in XAML.)
本部分的最后一个元素是带有可见绑定的"获取数据"消息,以确保仅当(The last element in this section is a “getting data” message with a visible binding that ensures the message is visible only while the) ViewModel
正在下载一些数据.(is downloading some data.)
下一部分包含用于选择图表上显示的时间跨度的命令:(The next section contains commands used to select the time span shown on the chart:)
<!-- links to select time span to be charted -->
<div data-bind="visible: chartVisible">
<a class="hlink" data-bind="click: function() { setMinDate(6) }">6m</a>
<a class="hlink" data-bind="click: function() { setMinDate(0) }">YTD</a>
<a class="hlink" data-bind="click: function() { setMinDate(12) }">1y</a>
<a class="hlink" data-bind="click: function() { setMinDate(24) }">2y</a>
<a class="hlink" data-bind="click: function() { setMinDate(36) }">3y</a>
<a class="hlink" data-bind="click: function() { setMinDate(1000) }">All</a>
</div>
整个部分都有一个可见的绑定,可确保仅当图表当前可见时才显示它.在此部分中,包含带有点击绑定的链接,这些链接会调用(The whole section has a visible binding that ensures it is displayed only if the chart is currently visible. Within the section, there are links with click bindings that invoke the) setMinDate
中的方法(method in the) ViewModel
并传递所需的时间跨度作为参数.(and pass the desired time span as a parameter.)
视图的最后一部分是图表控件,实现如下:(The final part of the view is the chart control, implemented as follows:)
<!-- portfolio chart -->
<div id="chart" data-bind="
wijlinechart: {
/* bind series, styles */
seriesList: chartSeries,
seriesStyles: chartStyles,
seriesHoverStyles: chartHoverStyles,
/* axis label formats */
axis: {
y: { annoFormatString : 'p0' },
x: { annoFormatString : 'dd-MMM-yy' }
},
/* series tooltip */
hint: {
content: function() {
return this.label + ' on ' +
Globalize.format(this.x, 'dd-MMM-yy') + ':\n' +
Globalize.format(this.y, 'p2');
}
},
/* other properties */
animation: { enabled: false },
seriesTransition: { enabled : false },
showChartLabels: false,
width: 800, height: 250,
}">
data-bind属性用于指定我们需要的图表属性.回想一下(The data-bind attribute is used to specify the chart properties we need. Recall that the) seriesList
,(,) seriesStyles
和(, and) seriesHoverStyles
是由ViewModel实现并由KnockoutJS跟踪的属性.只要这些属性中的任何一个改变,图表就会自动刷新.(are properties implemented by the ViewModel and tracked by KnockoutJS. Whenever any of these properties change, the chart is refreshed automatically.)
data-bind属性还初始化未绑定的图表属性,与在XAML中设置控件属性的方式相同.在这种情况下,代码将设置轴注释的格式,添加一个工具提示,该提示将在用户将鼠标移到图表上时显示当前符号,日期和值,并禁用动画等.(The data-bind attribute also initializes chart properties that are not bound, the same way you would set control properties in XAML. In this case, the code sets the format for the axis annotations, adds a tooltip that shows the current symbol, date, and value as the user moves the mouse over the chart, disables animations, and so on.)
Web服务实现(C#)(WebService Implementation (C#))
回想一下,我们的视图模型类使用"(Recall that our view model classes use a “)StockInfo.ashx(StockInfo.ashx)“以检索公司名称和历史价格数据的服务.此服务是(” service to retrieve company names and historical price data. This service is part of the)入侵者(Invexplorer)应用.它是一个"通用处理程序”,实现如下:(application. It is a “Generic Handler” implemented as follows:)
// StockInfo returns two types of information:
//
// Stock Prices:
// If the request contains a 'symbol' parameter, StockInfo returns a string
// with a list where each line contains a date and the closing value for the
// stock on that day. Dates are between 1/1/2008 and today.
// Values are obtained from the Yahoo finance service.
//
// Company Names and Symbols:
// If the request does not contain a 'symbol' parameter, StockInfo returns
// a string with a list where each line contains company symbols and names.
// Values are loaded from resource file 'resources/symbolnames.txt'.
public class StockInfo : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string symbol = context.Request["symbol"];
string content = string.IsNullOrEmpty(symbol)
? GetSymbols()
: GetPrices(symbol);
context.Response.ContentType = "text/plain";
context.Response.Write(content);
}
// implementations of GetSymbols and GetPrices methods follow…
该服务检查请求是否具有" symbol"参数.如果是,则通过调用(The service checks to see if the request has a “symbol” parameter. If it does, the content is obtained by calling the) GetPrices
方法.否则,通过调用获取内容(method. Otherwise, the content is obtained by calling) GetSymbols
.两种方法都返回包含请求的信息的字符串,其中项目之间的换行符以及值之间的制表符.(. Both methods return strings containing the information requested, with line breaks between items and tabs between values.)
的(The) GetSymbols
方法仅读取包含公司名称和符号的资源文件.的(method simply reads a resource file that contains the company names and symbols. The) GetPrices
方法调用Silverlight应用程序所做的Yahoo金融服务.我们不会在这里显示这些方法的实现,但是如果您要检出它们,它们将包含在源代码中.请记住,雅虎财务数据不是免费的.如果要在商业应用中使用它,则需要联系Yahoo以获得许可证.(method calls Yahoo finance services as the Silverlight application did. We will not show the implementation of those methods here, but they are included in the source code in case you want to check them out. Remember that the Yahoo financial data is not free; if you want to use it in commercial application, you will need to contact Yahoo to obtain a license.)
服务返回制表符分隔的项目,而不是JSON,以减小下载大小.这些调用中的每一个都返回数千个项目,在这种情况下解析它们非常容易.(The services return tab-separated items instead of JSON to reduce the size of the download. Each of these calls returns thousands of items, and parsing them in this case is very easy.)
结论(Conclusion)
在过去的几年中,HTML5和JavaScript已经走了很长一段路.首先,jQuery带来了与浏览器无关的特性以及易于进行的DOM操作.同时,新的浏览器开始支持HTML5功能,例如地理位置,隔离存储和灵活的canvas元素.然后,KnockoutJS和其他类似的库使将HTML(View)与JavaScript(ViewModel)分离变得容易.这种分离使创建和维护JavaScript应用程序变得更加容易.(HTML5 and JavaScript have come a long way over the last couple of years. First, jQuery brought browser-independence and easy DOM manipulation. At the same time, new browsers started to support HTML5 features such as geo-location, isolated storage, and the flexible canvas element. Then KnockoutJS and other similar libraries made it easy to separate the HTML (View) from the JavaScript (ViewModel). This separation makes creating and maintaining JavaScript applications much easier.)
最后,一些流行的控件库增加了对KnockoutJS的支持,使得在HTML5和JavaScript中的开发与在Silverlight中一样容易.(Finally, some popular control libraries have added support for KnockoutJS, making development in HTML5 and JavaScript about as easy as it is in Silverlight.)
我认为,HTML5/JavaScript堆栈上仍然缺少的主要部分是:(In my opinion, the main pieces still missing on the HTML5/JavaScript stack are:)
- 业务就绪数据层. Silverlight长期以来一直以RIA服务的形式提供此服务. JavaScript仍然没有,但希望不久的将来会改变.(A business-ready data layer. Silverlight has had this for a long time in the form of RIA services. JavaScript still does not have it, but hopefully that will change in the near future.)
- 更好地支持扩展和创建可重用的自定义控件(包括布局元素,例如XAML Grid元素).(Better support for extending and creating re-usable custom controls (including layout elements such as the XAML Grid element).)
- 具有内置错误检查,重构支持和IntelliSense的更高级的开发工具.(More advanced development tools, with built-in error-checking, refactoring support, and IntelliSense.) 这是什么意思呢? HTML/JavaScript平台准备好替代Silverlight吗?在我看来,答案是它取决于应用程序的复杂性以及该应用程序是否必须能够在平板电脑和手机上运行.(What does all this mean? Is the HTML/JavaScript platform ready to replace Silverlight? In my opinion, the answer is it depends on the complexity of the application and on whether the application must be able to run on tablets and phones.)
Invexplorer应用程序相对简单.它不需要数据库更新,验证或复杂的数据类型.页面布局也很简单.这使其成为HTML5/JavaScript实现的理想选择.(The Invexplorer application is relatively simple. It does not require database updates, validation, or complex data types. The page layout is also simple. This makes it an ideal candidate for an HTML5/JavaScript implementation.)
TypeScript版本(TypeScript Version)
Invexlorer应用程序的第二版包含(The second revision of the Invexlorer application incorporates)打字稿((TypeScript ()http://www.typescriptlang.org/).(http://www.typescriptlang.org/).)
TypeScript是一个由Anders Hejlsberg领导的开源项目. TypeScript将可选的类型,类和模块添加到JavaScript.它编译为基于标准的可读JavaScript.除了这些出色的扩展之外,TypeScript编译器还与Visual Studio集成在一起并提供自动编译,静态错误检查和IntelliSense.要解决上一节中第3项中列出的限制,还有很长的路要走.(TypeScript is an open source project headed by Anders Hejlsberg. TypeScript adds optional types, classes, and modules to JavaScript. It compiles to readable, standards-based JavaScript. In addition to these great extensions, the TypeScript compiler integrates with Visual Studio and provides automatic compilation, static error-checking, and IntelliSense. It goes a long way towards fixing the limitations listed in item 3 in the previous section.)
TypeScript仍然很新,但是已经很流行.实际上,已经至少有两个CodeProject文章对其进行了很好的描述:(TypeScript is still quite new, but it is already popular. In fact, there are already at least two CodeProject articles that describe it very nicely:) TypeScript入门(Getting Started With TypeScript) 和(and) TypeScript简介(An Introduction to TypeScript) .(.)
您可以从http://typescript.codeplex.com/从CodePlex下载并安装TypeScript.(You can download and install TypeScript from CodePlex at http://typescript.codeplex.com/.)
请注意,安装后,您可能必须手动运行vsix文件才能完成安装(我花了几个小时来解决这个问题). vsix通常可以在这里找到:(Note that after installing it, you may have to run the vsix file manually in order to complete the installation (I spent a few hours figuring this out). The vsix can usually be found here:)
c:\ Program Files(x86)\ Microsoft SDKs \ TypeScript \ 0.8.0.0 \ TypeScriptLanguageService.vsix(c:\Program Files (x86)\Microsoft SDKs\TypeScript\0.8.0.0\TypeScriptLanguageService.vsix)
安装后,您可以使用以下命令在Visual Studio中创建新的TypeScript项目(Once you’ve installed it, you can create new TypeScript projects in Visual Studio using the)档案|新项目(File | New Project)菜单,然后选择Visual C#节点并选择"带有TypeScript的HTML应用程序"选项(不是很直观).(menu, then selecting the Visual C# node and picking the “HTML Application with TypeScript” option (not very intuitive).)
将原始ViewModel从纯JavaScript转换为TypeScript非常容易.该过程包括以下步骤:(Converting the original ViewModel from plain JavaScript to TypeScript was very easy. The process consisted of the following steps:)
1)将原始的" js"文件分解为多个" ts"文件(每个类一个)(1) Breaking up the original “js” file into several “ts” files (one per class))
2)在每个" ts"文件的顶部添加外部变量声明.这些声明指示编译器忽略外部文件中定义的名称. Invexplorer项目需要以下声明:(2) Adding external variable declarations to the top of each “ts” file. These declarations instruct the compiler to ignore names defined in external files. The Invexplorer project requires these declarations:)
// declare externally defined objects (to keep TypeScript compiler happy)
declare var $; // jQuery
declare var ko; // KnockoutJS
declare var Globalize; // Globalize plug-in
3)添加"引用路径"语句,以使TypeScript编译器可以找到同一项目中其他文件中定义的对象.例如,我们的ViewModel类引用了Portfolio和Company类,因此它需要这些引用:(3) Adding “reference path” statements that allow the TypeScript compiler to find objects defined in other files within the same project. For example, our ViewModel class references the Portfolio and Company classes, so it needs these references:)
///<reference path='portfolio.ts'/>
///<reference path='company.ts'/>
4)在类及其元素中添加类,成员,构造函数和方法声明.例如(只是为了给您语法的味道):(4) Adding class, member, constructor, and method declarations to the classes and their elements. For example (just to give you a flavor of the syntax):)
class PortfolioItem {
// fields (typed)
portfolio: Portfolio;
symbol: string;
company: Company;
// ...
constructor(portfolio: Portfolio, symbol: string, chart = false, shares = 0, unitCost = 0)
{
this.portfolio = portfolio;
this.symbol = symbol;
// ...
5)将类型信息添加到成员和方法签名.(5) Adding type information to members and method signatures.)
这似乎是很多工作,但实际上非常容易.完成后,您将在TypeScript文件中获得静态错误检查和IntelliSense.实际上,一旦完成转换,编译器很可能会在您的项目中发现一些错误.作为C#开发人员,我在编写JavaScript代码时确实错过了这种支持.(This may seem like a lot of work, but it is actually very easy. And once you are done, you will get static error checking and IntelliSense right in your TypeScript files. In fact, it is likely that the compiler will find a few bugs in your project as soon as you finish the conversion. As a C# developer, I really missed this support when I wrote JavaScript code.)
下图显示了TypeScript编译器如何与Visual Studio集成以提供静态错误检查和IntelliSense.(The images below show how the TypeScript compiler integrates with Visual Studio to provide static error-checking and IntelliSense.)
TypeScript提供的静态错误检查(Static error-checking provided by TypeScript)
TypeScript提供的IntelliSense(IntelliSense provided by TypeScript)
运行项目会使TypeScript编译器生成包含纯JavaScript的" js"文件.最终项目将不包含TypeScript的痕迹,它只是以前的旧版HTML和JavaScript.(Running the project causes the TypeScript compiler to generate the “js” files which contain plain JavaScript. The final project will contain no traces of TypeScript, it’s just good old HTML and JavaScript as before.)
我已经是TypeScript的粉丝,并计划在以后的HTML/JS项目中使用它.如果您使用JavaScript开发并且还没有尝试过TypeScript,那么您将可以接受.(I am a fan of TypeScript already, and plan to use it in my future HTML/JS projects. If you develop in JavaScript and haven’t tried TypeScript yet, you are in for a treat.)
参考资料和资源(References and Resources)
- http://demo.componentone.com/wijmo/InvExplorer/(http://demo.componentone.com/wijmo/InvExplorer/) :本文介绍的Invexplorer应用程序的实时版本.(: Live version of the Invexplorer application described in this article.)
- http://www.codeproject.com/Articles/365120/KnockoutJS-vs-Silverlight(http://www.codeproject.com/Articles/365120/KnockoutJS-vs-Silverlight) :Colin Eberhart的CodeProject文章比较了使用KnckoutJS和Silverlight开发的MVVM. Silverlight开发人员入门HTML5和JavaScript的绝佳资源.(: CodeProject article by Colin Eberhart, compares MVVM development using KnckoutJS and Silverlight. Excellent resource for Silverlight developers getting started with HTML5 and JavaScript.)
- [Silverlight和HTML5 IX.pdf中的http://publicfiles.componentone.com/Bernardo/MVVM(http://publicfiles.componentone.com/Bernardo/MVVM in Silverlight and in HTML5 IX.pdf)](http://publicfiles.componentone.com/Bernardo/MVVM in Silverlight and in HTML5 IX.pdf) :比较Invexplorer应用程序的Silverlight和JavaScript实现的文章.(: Article that compares Silverlight and JavaScript implementations of the Invexplorer application.)
- http://knockoutjs.com/(http://knockoutjs.com/) :KnockoutJS主页.这是KnockoutJS上的最终资源.它包含概念性信息,文档,样本和教程.(: KnockoutJS home page. This is the ultimate resource on KnockoutJS. It contains conceptual information, documentation, samples, and tutorials.)
- http://jqueryui.com/(http://jqueryui.com/) :jQueryUI主页. jQueryUI的官方资源,包括jQueryUI附带的控件(窗口小部件),效果和实用程序的文档和示例.(: jQueryUI home page. The official resource for jQueryUI, including documentation and samples for the controls (widgets), effects, and utilities included with jQueryUI.)
- http://wijmo.com/(http://wijmo.com/) :Wijmo主页. Wijmo的官方资源,Wijmo是一个控件库,其中包括支持KnockoutJS的网格和图表控件.(: Wijmo home page. The official resource for Wijmo, a control library that includes grid and chart controls that support KnockoutJS.)
- http://typescript.codeplex.com/(http://typescript.codeplex.com/) :TypeScript主页. TypeScript编译器将类型信息,静态错误检查和IntelliSense添加到JavaScript.(: TypeScript home page. The TypeScript compiler adds type information, static error-checking, and IntelliSense to JavaScript.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
HTML5 Typescript C# Dev MVVM 新闻 翻译