Angular.js示例应用程序
表的内容 演示应用程序概述。js介绍 应用服务模块依赖注入路由视图控制器范围指令 出版商Angular.js网站 js使用主应用程序设置根页面的路由 liveupdatesservice localstorageservice imageservice利用了service draggable directive resizable directive根控制器根视图 最喜欢的页面 Favs控制器Favs视图colorbox指令 关于页面 就是这样 介绍 这是我很长一段时间以来写的第一篇文章,这是有原因的,但我不想让你们感到厌烦。不管怎样,我在休息了一段时间后写了这篇文章。那么它是做什么的,这篇文章是关于什么的? 我决定花点时间多学习一些关于一个流行的web MVC框架的知识,这个框架是我们在谷歌的朋友设计的,叫做Angular。js,这是一个JavaScript MVC框架,这对我来说有点不同于我通常受XAML影响的世界。无论如何,试着去了解如何在不同的语言/环境中做事情是件好事(我的老导师Fredrik Bornander(也就是那个瑞典人)告诉我),所以我决定用Angular.js做一个旋转。 本文将讨论Angular背后的一些基本思想。然后将重点介绍我为配合本文创建的演示应用程序的细节。 在我们进入实际的文章中,我将简要(不要太技术,尽管我知道你很多想要看到,它会来的,别担心)谈论什么演示应用程序在普通条款,所以你知道如何驾驶自己的东西,当你下载它。 演示应用程序概述 附呈的demo app有2个部分: 出版商 这是一个标准的WPF项目,因此产生一个EXE文件,可以运行。在本文中,我不会花太多时间讨论出版商,因为它不是本文的重要部分,它只是在Angular.js网站中演示内容的一个工具。无论如何,发布者所做的是允许用户点击一个图像,当用户点击一个图像,一个消息被发送到Angular.js网站使用web sockets(更多关于这个稍后)。简而言之,这就是出版商所做的一切。 网站 js网站是有趣的事情发生的地方(至少在我看来是这样)。js网站基本上完成了这些任务。 当在根页面上时,Angular.js将侦听通过web套接字通过WPF发布者发送的消息,然后使用响应式JavaScript扩展在内部广播给任何感兴趣的人,在本文中,这些扩展基本上就是根页面。 根页面将为接收到的每个允许的消息显示一个图像平铺。由于jQuery用户界面的喜爱,图像平铺可以调整大小并拖动。用户可以选择将图像块保存到他们的收藏夹中,这将导致有关图像块的所有信息被保存到HTML 5本地存储中。这些信息包括大小、位置等,所以当用户返回到根页面时,他们的收藏夹(他们保存的)应该和以前一样显示出来。用户还可以决定从根页面删除他们收藏的图片块。 用户还可以选择导航到收藏夹页面,该页面将显示HTML 5本地存储持久化收藏夹的一些缩略图。点击这些缩略图可以显示标准的jQuery插件ColorBox (Lightbox等)。 用户也可以选择查看静态的about页面,我只是添加了足够的路由,让在Angular.js中演示路由时更有价值。 所以简单地说,这就是它的全部,这幅图像可能有助于巩固我刚才所说的文字,图片说1000nd文字和所有的: 这是这篇文章的演示代码的2部分应该看起来像当他们正确运行: 单击图像查看大图 重要提示 您应该确保按照这些步骤成功运行演示代码。 如果你发现一些出版商。Wpf项目的引用找不到,我已经把它们包括在一个“Lib”文件夹中,在那里你可以简单地重新引用它们,你应该确保出版商。Wpf项目首先运行,并显示所有的图像。这可以通过构建发布者来实现。Wpf项目一个EXE和简单地找到EXE文件系统并双击运行(或使用Visual Studio调试运行了一个实例)你应该然后运行角的网站,确保你在Visual Studio index . html页面设置为开始,然后使用Visual Studio角网站运行 Angular.Js介绍 在这一节中,我将讨论使用Angular.js的一些基础知识。这一部分将是我自己的话的混合和直接从Angular.js网站下载的文本。我不会涵盖所有的角度。js做,因为那将更像一本书真的,我只是没有那么多的时间。不过,我将介绍一些基本的Angular.js构建模块,所以,如果你读了这篇文章并认为“嗯……Angular的东西引起了我的兴趣,我在哪里可以学到更多呢?”当然是通过超链接。 应用程序 每个Angular.js应用程序都需要在HTML中使用一个Angular.js ng-app绑定,或者通过一些代码完成与声明性HTML绑定相同的工作。这段代码从本质上引导了Angular.js,并让它知道应用程序运行在什么上下文中。例如,您可能有以下代码: 隐藏,复制Code
<divid="outer"> <divid="inner"ng-app="myApp"> <p>{{name}}</p> </div> <p>This is not using the angular app, as it is not within the Angular apps scope</p> </div>
可以看到,我们可以让HTML的特定部分充当Angular.js应用程序。在这个例子中,这意味着div的id =“内在”会有一个部分的HTML(虽然没有什么能阻止你进行实际的身体标记角应用),被认为是Angular.js应用程序,这样会有完全访问Angular.js应用程序功能(我们将在下面讨论)。 而id="outer"的div将不会被认为是Angular.js应用程序的一部分,因此不会访问Angular.js应用程序的任何特性(我们将在下面讨论)。 服务 js中的服务与WinForms/WPF或Silverlight中的服务基本相同。它们是小的助手类,可以提供可跨应用程序使用的功能。在像c#这样的强类型语言中,我们通常会让这些服务实现一个特定的接口,并将它们(通过构造函数或属性注入)注入到我们的应用程序代码中。然后我们就可以在测试中提供这些服务的伪造/模拟,或者在底层系统发生变化时提供替代版本(例如从Mongo DB到Raven DB存储) js不支持接口(尽管你可以使用TypeScript),但它支持在代码中注入real/fakes服务。事实上,我想说的是,Angular.js的主要观点之一是它支持开箱即用的IOC。 模块 大多数应用程序都有一个主方法,用于实例化、连接和引导应用程序。Angular应用没有主方法。相反,模块声明式地指定应用程序应该如何引导。这种方法有几个优点: 这个过程更具有声明性,在单元测试中更容易理解,不需要加载所有模块,这可能有助于编写单元测试。可以在场景测试中加载其他模块,它可以覆盖一些配置,并帮助对应用程序进行端到端测试。第三方代码可以打包为可重用模块。模块可以以任意/并行的顺序加载(由于模块执行的延迟特性)。 http://docs.angularjs.org/guide/module 推荐的方法是实际拆分你的模块,这样你可能有一个这样的结构: 一个服务模块一个指令模块一个过滤器模块应用模块 这就是定义Angular.js模块的方法,它的好处是使用了Angular.js提供的函数“module”。 隐藏,复制Code
angular.module('xmpl.service', []). value('greeter', { salutation: 'Hello', localize: function(localization) { this.salutation = localization.salutation; }, greet: function(name) { return this.salutation + ' ' + name + '!'; } }). value('user', { load: function(name) { this.name = name; } });
依赖注入 js在构建时考虑到了依赖注入(IOC),因此,许多基础设施可能会被替换为模拟版本,或者控制器可以使用模拟服务进行测试。演示如何做这些,或者如何测试Angular.js应用程序,但不在本文的范围内。如果你想知道这一点,可以访问Angular.js文档,或者得到一本书。对不起! 在上一个模块示例的基础上,这是一个具有一些依赖关系的模块的样子,在本例中是我们在上面定义的模块,其中我们使用两个值“greeter”和“user”,这两个值在“xmpl”中都是可用的函数。服务”模块。这个模块可以与'xmpl的模拟版本一起提供。服务”模块。 隐藏,复制Code
angular.module('xmpl', ['xmpl.service']). run(function(greeter, user) { // This is effectively part of the main method initialization code greeter.localize({ salutation: 'Bonjour' }); user.load('World'); })
路由 js主要是一个单页面应用程序框架,因此具有视图模板的概念,可以应用它来响应请求的特定路由。 js中的路由实际上和ASP MVC甚至node。js中的路由没有什么不同。 路由是通过使用一个名为$routeProvider的预构建服务来完成的,它是作为Angular.js的一部分免费提供的。它允许用户配置他们的路由使用一个非常简单的API,这归结为以下两个功能: 当(道路、路线) 其中route对象具有以下属性: 控制器模板模板模板解析重定向到reloadonsearch 否则(params) 这里有一个小例子: 隐藏,复制Code
$routeProvider .when('/products', { templateUrl: 'views/products.html', controller: 'ProductsCtrl' }) .when('/about', { templateUrl: 'views/about.html' }) .otherwise({ redirectTo: '/products' });;
的观点 关于vi,没有太多要说的电子战。我们以前可能都遇到过HTML。这就是视图所包含的内容。唯一的区别是,Angular.js视图将包含额外的(非标准HTML)绑定,允许视图模板显示来自Angular.js范围对象的数据。scope对象通常来自一个控制器(尽管它不限于来自一个控制器,它可以被继承,或者通过指令创建)。 下面是一个视图的小示例,请注意其中使用的绑定,比如ng-model和ng-repeat的使用,以及一些Angular.js预构建的fiters的使用,即filter和orderBy(请注意,在本文中我不会介绍过滤器)。 隐藏,复制Code
Search: <inputng-model="query"> Sort by: <selectng-model="orderProp"> <optionvalue="name">Alphabetical</option> <optionvalue="age">Newest</option> </select> <ulclass="phones"> <ling-repeat="phone in phones | filter:query | orderBy:orderProp"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul>
控制器 控制器用于定义视图的范围。Scope可以被认为是视图可能使用的变量和函数,比如通过使用ng-click绑定。下面是我们刚才看到的视图模板的控制器代码。 隐藏,复制Code
function PhoneListCtrl($scope) { $scope.phones = [ {"name": "Nexus S", "snippet": "Fast just got faster with Nexus S.", "age": 0}, {"name": "Motorola XOOM™ with Wi-Fi", "snippet": "The Next, Next Generation tablet.", "age": 1}, {"name": "MOTOROLA XOOM™", "snippet": "The Next, Next Generation tablet.", "age": 2} ]; $scope.orderProp = 'age';
可以看出,控制器定义了以下2个范围属性: 这是一个JSON数组orderprop:这是一个单字符串值 范围 作用域是一种粘合剂,它允许定义作用域对象属性/函数的视图和控制器绑定在一起。如果您曾经使用过基于XAML的技术,比如WPF/Silverlight/WinRT,那么您可以将scope看作一个DataContext。事实上,有很多UI框架都有类似范围的概念。XAML技术有DataContext,它通常是一个视图模型,而另一个流行的MVVM JavaScript库Knockout.js也有作用域的概念,以及heirarchical作用域,它可以通过绑定html&th中使用各种预先构建的关键字来访问。 js还支持嵌套的/旋转的范围,这有时会让人有点困惑。我个人发现,使用Angular.js和它的作用域的最好方法之一是安装Batarang Chrome插件,它有一个很好的方法,可以让你使用作用域检查器(有点像WPF的Snoop或Silverlight的SilverlightSpy)进入作用域。 此图有助于巩固视图-控制器-范围的概念。 单击图像查看大图 指令 js使用了一个非常新颖的概念,称为指令。指令是聪明的家伙,它实际上允许您创建额外的属性,甚至是新的DOM片段。这都是通过对指令应用某些约束来控制的,例如您可能希望声明某个指令只能作为属性使用,或者它只能作为元素使用。你可以把指令看作是自定义控件。 指令也遵循正常的Angular.js规则,因为它们支持依赖注入,而且它们也有作用域意识。 最好的信息我写这篇文章时遇到这个Bernardo Castilho: http://www.codeproject.com/Articles/607873/Extending-HTML-with-AngularJS-Directives,我强烈建议你去读,这是一个优秀的文章,和结束时,你将完全得到指令。 总之,这就是Angular.js的基础知识,现在是实际演示应用程序代码演练的开始。 出版商 如前所述,发布者是一个WPF应用程序(因此它是一个可运行的EXE),这并不是本文的主旨。关于出版商的主要观点是: 这就是使用了很棒的Fleck WebSocket库来与Angular.js网站对话,这个网站有一个Win8类型的全景图,所以你可以使用鼠标来滚动 以下是WPF发布程序运行时的样子: 现在来看重要的部分,web套接字代码。 隐藏,收缩,复制Code
public class WebSocketInvoker : IWebSocketInvoker { List<IWebSocketConnection> allSockets = new List<IWebSocketConnection>(); WebSocketServer server = new WebSocketServer("ws://localhost:8181"); public WebSocketInvoker() { FleckLog.Level = LogLevel.Debug; server.Start(socket => { socket.OnOpen = () => { Console.WriteLine("Open!"); allSockets.Add(socket); }; socket.OnClose = () => { Console.WriteLine("Close!"); allSockets.Remove(socket); }; socket.OnMessage = Console.WriteLine; }); } public void SendNewMessage(string jsonMessage) { foreach (var socket in allSockets) { socket.Send(jsonMessage); } } }
这是所有有那部分实际上,相当酷哈(所有感谢斑点WebSocket库)。现在我意识到可能会有一些的你,就像你为什么不使用SignalR,我可以有,但这将是一个非常不同的文章,对于这个我想要纯粹的关注事物的web客户端,所以选择使用原始网络套接字,斑点WebSocket图书馆完全符合这一法案。 在此代码中,当用户单击图像时将调用SendNewMessage,单击的图像的名称将通过web套接字发送到Angular.js web站点。Angular.js网站,复制所有可能的图片,因为我不想进入复杂的POST操作文件,显然一个web服务器不能显示本地文件(这将是一个安全风险(和其他)的意见),所以我选择了共享文件,出版商和Angular.js网站都知道这个演示应用程序/文章的目的。 Angular.js网站 本节将讨论附加的演示代码Angular.js网站的细节。希望,如果你已经理解了这一点,当你看到s时,我上面提到的一些东西会开始变得有意义渗出性中耳炎的代码。 Require.js用法 在我开始研究角度。js之前,我正在研究使用要求。这是一个JavaScript的模块加载框架,允许您指定依赖项和首选的模块加载顺序。我在另一篇文章中提到了这一点,您可以在这里阅读:使用Require.Js的模块化Javascript 在这篇文章中,我在某种程度上构建了一些内容(首先从Angular.js O'Reilly图书附带的源代码开始:https://github.com/shyamseshadri/angularjs-book)。 所以这是你可以做更多的阅读围绕这个主题,如果你想,但让我们破解,看看附加的Angular.js网站的需求。js元素是什么样子的。 它开始于以下代码在主Angular.js页面(Index.html): 看到index . html 隐藏,复制Code
<html> ..... <scriptdata-main="scripts/main"src="scripts/vendor/require.js"></script> </html>
这是标准的Require.js代码,它告诉Require.js应该运行哪个主引导代码文件。可以看出,这是脚本/主要,所以让我们看看现在,我们可以。 看到脚本\ main.js 隐藏,收缩,复制Code
// the app/scripts/main.js file, which defines our RequireJS config require.config({ paths: { angular: 'vendor/angular.min', jqueryUI: 'vendor/jquery-ui', jqueryColorbox: 'vendor/jquery-colorbox', jquery: 'vendor/jquery', domReady: 'vendor/domReady', reactive: 'vendor/rx' }, shim: { angular: { deps: ['jquery', 'jqueryUI', 'jqueryColorbox'], exports: 'angular' }, jqueryUI: { deps: ['jquery'] }, jqueryColorbox: { deps: ['jquery'] } } }); require([ 'angular', 'app', 'domReady', 'reactive', 'services/liveUpdatesService', 'services/imageService', 'services/localStorageService', 'controllers/rootController', 'controllers/favsController', 'directives/ngbkFocus', 'directives/draggable', 'directives/resizable', 'directives/tooltip', 'directives/colorbox' // Any individual controller, service, directive or filter file // that you add will need to be pulled in here. ], function (angular, app, domReady) { ……. ……. ……. ……. } );
这里发生了很多事情。然而,它可以归结为三个部分: 我们用JavaScript文件的路径来配置Require.js。我们使用Require.js垫片为Require.js配置了一个首选的加载顺序。shim实际上设置了要加载的库的依赖关系。然后我们使用Require.js [Require]来告诉Angular.js应用程序我们需要满足哪些依赖项。 我还使用了requir .js来满足演示应用程序控制器的要求。其中一个例子如下: 隐藏,收缩,复制Code
define(['controllers/controllers', 'services/imageService', 'services/utilitiesService', 'services/localStorageService'], function (controllers) { controllers.controller('FavsCtrl', ['$window', '$scope', 'ImageService', 'UtilitiesService', 'LocalStorageService', function ( $window, $scope, ImageService, UtilitiesService, LocalStorageService) { ...... ...... ...... ...... ...... }]); });
主应用程序设置 看到脚本\ main.js 现在您已经看到了主引导代码(主要由requir .js配置占用),让我们快速看一下实际的Angular.js引导代码。 这是我们在本文开始时讨论的部分,你知道的,这部分使附加的代码变成了“Angular”。js”应用程序。 该部分内容如下: 隐藏,复制Code
function (angular, app, domReady) { 'use strict'; app.config(['$routeProvider', function ($routeProvider) { .... .... .... }]); domReady(function () { angular.bootstrap(document, ['MyApp']); // The following is required if you want AngularJS Scenario tests to work $('html').addClass('ng-app: MyApp'); }); }
这个bootstrapping做两件事: 它设置了可用的有效路由,我们将在下面看到这些路由。它依赖于一个特殊的Angular.js插件,称为“DomReady”,它的工作方式与jQuery及其ready()事件非常相似。dom准备好之后,“HTML”元素被赋予属性,使其充当Angular.js应用程序。 还有一个问题是“MyApp”模块来自哪里。在它被引导到这里之前是谁创建的? 答案是,它存在于自己的文件"app.js"看起来像这样: 看到脚本\ app.js 隐藏,复制Code
// The app/scripts/app.js file, which defines our AngularJS app define(['angular', 'controllers/controllers', 'services/services', 'filters/filters', 'directives/directives'], function (angular) { return angular.module('MyApp', ['controllers', 'services', 'filters', 'directives']); });
路由 看到脚本\ main.js 对于演示应用程序,有3条有效的路由Root/Favs/About。每一个都使用标准的Angular.js $routeProvider服务配置,其中所有设置代码都在引导文件main.js中完成。 隐藏,复制Code
unction ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/root.html', controller: 'RootCtrl' }) .when('/favs', { templateUrl: 'views/favs.html', controller: 'FavsCtrl' }) .when('/about', { templateUrl: 'views/about.html' }).otherwise({ redirectTo: '/' });; }
我认为从上面提到的三种路由以及它们的配置来看,这是非常明显的。所以我就不再多说了。 根页面 这是我在演示网站中放置的最复杂的页面,因为它将许多不同的东西放在一起。 那么这个页面到底是做什么的呢? 其思想是,有一个名为“LiveUpdatesService”的服务,它侦听WPF发布者正在将数据推出的Web套接字的客户端。LiveUpdatesService使用JavaScript的响应扩展来提供可能订阅的数据流。 根页面将订阅这个已发布的流,每当它看到一个新条目时,它将添加一个新的jQuery UI可拖拽/可调整大小的UI元素,只要它还没有获得与所显示的图像名称相同的元素。 它还允许用户将图像保存到HTML 5本地存储中,并从本地存储中删除它们。如果在本地存储中已经有项目,则使用它们的详细信息作为根页面的初始启动状态。我不得不说这看起来很酷,因为所有的信息都是持久的,所以它记住了大小,位置,ZIndex,所以它返回到你保存它的方式。 一般来说,这就是根页面的作用。 这是根页面的样子: 单击图像查看大图 这就是它的样子,想看看代码吗? LiveUpdatesService 该服务侦听来自发布者web套接字的传入数据,并通过针对JavaScript Subject对象的响应扩展将新接收到的web套接字数据推出。以下是服务代码: 隐藏,收缩,复制Code
define(['services/services'], function (services) { services.factory('LiveUpdatesService', ['$window', function (win) { var subject = new Rx.Subject(); if ("WebSocket" in window) { // create a new websocket and connect var ws = new WebSocket('ws://localhost:8181/publisher', 'my-protocol'); // when data is coming from the server, this metod is called ws.onmessage = function (evt) { subject.onNext(evt.data); }; // when the connection is established, this method is called ws.onopen = function () { win.alert('Websocket connection opened'); }; //// when the connection is closed, this method is called ws.onclose = function () { subject.onError('Websocket connection closed, perhaps you need to restart the Publisher, and refresh web site'); }; } return { publishEvent: function (value) { subject.onNext(value); }, eventsStream: function () { return subject.asObservable(); } }; }]); });
下面是使用反应扩展主题流的根控制器代码,在这里我们首先检查之前是否看到了具有相同名称的项,以及是否仅向用户显示了一条消息(没有我们不直接使用window,而是使用angular的$window服务(可能更容易模仿))。 如果以前没有见过图像名称,那么将使用随机定位的ImageService创建一个新项目。 隐藏,复制Code
LiveUpdatesService.eventsStream().subscribe( function (data) { if ($location.path() == '/') { var idx = $scope.imageitems.propertyBasedIndexOf('name', data); if (idx >= 0) { $window.alert('An item with that name has already been added'); } else { var randomLeft = UtilitiesService.getRandomInt(10, 600); var randomTop = UtilitiesService.getRandomInt(10, 400); var randomWidth = UtilitiesService.getRandomInt(100, 300); var randomHeight = UtilitiesService.getRandomInt(100, 300); $scope.imageitems.push(ImageService.createImageItem( data, randomLeft, randomTop, randomWidth, randomHeight, false)); $scope.$apply(); } } }, function (error) { $window.alert(error); });
LocalStorageService 此服务负责从HTML 5本地存储中持久化/获取数据项。我认为这段代码是相当自我解释,所以我将留在那里: 隐藏,收缩,复制Code
define(['services/services'], function (services) { services.factory('LocalStorageService', [ function () { return { isSupported: function () { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } }, save: function (key, value) { localStorage[key] = JSON.stringify(value); }, fetch: function (key) { return localStorage[key]; }, parse: function(value) { return JSON.parse(value); }, clear: function (key) { localStorage.removeItem(key); } }; }]); });
ImageService 该服务只是帮助创建根页面的ImageItem对象和Favs页面的FavItem对象。 隐藏,收缩,复制Code
function ImageItem(name, left, top, width, height, isFavourite) { var self = this; self.name = name; self.left = left; self.top = top; self.width = width; self.height = height; self.isFavourite = isFavourite; self.styleProps = function () { return { left: self.left + 'px', top: self.top + 'px', width: self.width + 'px', height: self.height + 'px', position: 'absolute' }; }; return self; }; function FavImageItem(name) { var self = this; self.name = name; return self; }; define(['services/services'], function (services) { services.factory('ImageService', [ function () { return { createImageItem: function (name, left, top, width, height, isFavourite) { return new ImageItem(name, left, top, width, height, isFavourite); }, createFavImageItem: function (name) { return new FavImageItem(name); } }; }]); });
需要注意的一件重要事情是动态CSS是如何在Angular.js中完成的。要实现动态CSS,当对象改变时更新,你需要提供一个函数来调用,这就是你在这里可以看到的styleProps()函数: 隐藏,复制Code
self.styleProps = function () { return { left: self.left + 'px', top: self.top + 'px', width: self.width + 'px', height: self.height + 'px', position: 'absolute' }; };
使用这种方法的标记如下所示,这意味着每当对JSON对象进行更新时,CSS也会更新,而HTML会反映这一点。这并不是那么容易找到的,所以你一定要把这段读几遍,巩固你的知识,把它牢牢地塞进去。 隐藏,复制Code
ng-style="imageitems[$index].styleProps()"
UtilitiesService 这项服务提供以下功能: 添加一个propertyBasedIndexOf()数组,数组可以搜索一个特定的项目属性,索引将返回getrandomint():用于获得一个随机的x / y指向新形象物品在第一次显示delayedalert():显示一些延迟时间之后一个警告 代码如下: 隐藏,收缩,复制Code
define(['services/services'], function (services) { services.factory('UtilitiesService', [ function () { var initialised = false; return { addArrayHelperMethods: function () { if (!initialised) { initialised = true; Array.prototype.propertyBasedIndexOf = function arrayObjectIndexOf(property, value) { for (var i = 0, len = this.length; i < len; i++) { if (this[i][property] === value) return i; } return -1; }; } }, getRandomInt: function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }, delayedAlert: function(message) { setTimeout(function () { $window.alert(message); }, 1000); } }; }]); });
可拖动的指令 为了实现拖拽,我已经知道我必须使用jQuery UI库,但是在使用Angular的时候。js中有一种明确的Angular.js方法,用DOM修改jQuery代码来乱丢控制器代码肯定不是Angular.js方法。那我们还有什么选择呢?这正是Angular.js指令所适合的,它们被设计用来替换和增强DOM,这是指令最擅长做的事情。 因此,在需要直接更改DOM时(而不是通过范围更改),应该考虑使用Angular.js指令。 所以说,创建一个小的jQuery UI Angular.js指令是一件简单的事情,可以在<表中看到…拖拽resizable>……& lt; / table>标记中的表标记。 这是可拖动的指令代码,你可以看到这是局限于属性的用法,并简单地代表实际jQuery UI的工作(这是引用Require.js配置中的要求,所以我们知道它加载好,否则Angular.js不会开始,一样有jQuery UI依赖Require.js内配置)。 这里值得一提的是,拖动完成后,我希望将新的位置值通知控制器,以便它们能够反映在样式中。现在,由于定位是在Angular.js控制器作用域之外完成的(正如它是通过内置的jQuery UI代码完成的),我们需要获得draggable指令来更新控制器作用域,这样它就知道外部的某些东西改变了它的一个变量。幸运的是,jQuery UI draggable小部件提供了一个很好的回调函数,我们可以使用这个回调函数,然后告诉Angular.js控制器的作用域发生了变化,这是使用用于此目的的Angular.js $scope.apply()完成的。 隐藏,复制Code
define(['directives/directives'], function (directives) { directives.directive('draggable', ['$rootScope', function ($rootScope) { return { restrict: 'A', //may need the model to be passed in here so we can apply changes //to its left/top positions link: function (scope, element, attrs) { element.draggable( { stop: function (event, ui) { scope.$apply(function() { scope.updatePosition( scope.imageitem.name, { left: ui.position.left, top: ui.position.top } ); }); } }); } }; }]); });
这是控制器代码,当指令调用到控制器的作用域时被调用: 隐藏,复制Code
// NOTE: $scope.$apply is called by the draggable directive $scope.updatePosition = function (name, pos) { var idx = $scope.imageitems.propertyBasedIndexOf('name', name); var foundItem = $scope.imageitems[idx]; foundItem.left = pos.left; foundItem.top = pos.top; };
可调整大小的指令 resizable指令的工作原理与draggable指令大致相同,它是另一个基于jQuery UI的Angular.js指令。下面是它的代码: 隐藏,收缩,复制Code
define(['directives/directives'], function (directives) { directives.directive('resizable', ['$rootScope', function ($rootScope) { return { restrict: 'A', //may need the model to be passed in here so we can apply changes //to its left/top positions link: function (scope, element, attrs) { element.resizable( { maxHeight: 200, minHeight: 100, //aspectRatio: 16 / 9, stop: function (event, ui) { scope.$apply(function () { scope.updateScale( scope.imageitem.name, { top: ui.position.top, left: ui.position.left }, { width: ui.size.width, height: ui.size.height } ); }); } }); } }; }]); });
和以前一样,由于我们改变的东西(UI元素的比例)超出了Angular.js控制器的知识范围,我们需要得到指令来更新控制器范围,以下是相关代码: 隐藏,复制Code
// NOTE: $scope.$apply is called by the resizable directive $scope.updateScale = function (name, pos, size) { var idx = $scope.imageitems.propertyBasedIndexOf('name', name); var foundItem = $scope.imageitems[idx]; foundItem.left = pos.left; foundItem.top = pos.top; foundItem.width = size.width; foundItem.height = size.height; };
根控制器 一些内部的根控制器已经被覆盖,所以我将删除内部代码从我们已经覆盖的函数,这真的只留下以下控制器代码: 隐藏,收缩,复制Code
define(['controllers/controllers', 'services/liveUpdatesService', 'services/utilitiesService', 'services/imageService', 'services/localStorageService'], function (controllers) { controllers.controller('RootCtrl', ['$window', '$scope', '$location', 'LiveUpdatesService', 'UtilitiesService', 'ImageService', 'LocalStorageService', function ( $window, $scope, $location, LiveUpdatesService, UtilitiesService, ImageService, LocalStorageService) { $scope.imageitems = []; $scope.imageItemsStorageKey = 'imageItemsKey'; //load existing items from local storage which looks cool, //as they show up in their persisted //positions again...Cool if (LocalStorageService.isSupported()) { var currentFavs = LocalStorageService.fetch($scope.imageItemsStorageKey); if (currentFavs != undefined) { currentFavs = JSON.parse(currentFavs); for (var i = 0; i < currentFavs.length; i++) { var favItem = currentFavs[i]; $scope.imageitems.push(ImageService.createImageItem( favItem.name, favItem.left, favItem.top, favItem.width, favItem.height, true)); } } } UtilitiesService.addArrayHelperMethods(); LiveUpdatesService.eventsStream().subscribe( ..... ..... ..... ); $scope.addToFavourites = function (index) { if (!LocalStorageService.isSupported()) { $window.alert('Local storage is not supported by your browser, so saving favourites isn\'t possible'); } else { var currentStoredFavsForAdd = LocalStorageService.fetch ($scope.imageItemsStorageKey); if (currentStoredFavsForAdd == undefined) { currentStoredFavsForAdd = []; } else { currentStoredFavsForAdd = JSON.parse(currentStoredFavsForAdd); } var scopeImageItem = $scope.imageitems[index]; var favsIdx = currentStoredFavsForAdd.propertyBasedIndexOf ('name', scopeImageItem.name); if (favsIdx >= 0) { $window.alert('An item with that name is already in your favourites.'); return; } $scope.imageitems[index].isFavourite = true; currentStoredFavsForAdd.push(scopeImageItem); LocalStorageService.save($scope.imageItemsStorageKey, currentStoredFavsForAdd); $window.alert('Saved to favourites'); } }; $scope.removeFromFavourites = function (index) { if (!LocalStorageService.isSupported()) { $window.alert('Local storage is not supported by your browser, so removing from favourites isn\'t possible'); } else { var currentStoredFavsForRemoval = LocalStorageService.fetch ($scope.imageItemsStorageKey); if (currentStoredFavsForRemoval == undefined) { return; } else { currentStoredFavsForRemoval = JSON.parse(currentStoredFavsForRemoval); } var scopeImageItem = $scope.imageitems[index]; var favsIdx = currentStoredFavsForRemoval.propertyBasedIndexOf ('name', scopeImageItem.name); $scope.imageitems.splice(index, 1); if (favsIdx >= 0) { currentStoredFavsForRemoval.splice(favsIdx, 1); LocalStorageService.save ($scope.imageItemsStorageKey, currentStoredFavsForRemoval); } $window.alert('Item removed from favourites'); } }; // NOTE: $scope.$apply is called by the draggable directive $scope.updatePosition = function (name, pos) { ..... ..... ..... }; // NOTE: $scope.$apply is called by the resizable directive $scope.updateScale = function (name, pos, size) { ..... ..... ..... }; }]); });
可以看到,根控制器的大部分代码已经介绍过了,那么还有什么要讨论的呢? 本质上,剩下的代码做了以下工作: 允许用户调用addToFavourites函数(将它添加到HTML 5本地存储)通过使用一个按钮图像瓷砖UI允许用户调用removeFromFavourites函数(将它从UI也从HTML 5本地存储)通过使用一个按钮图像瓷砖UI将读取所有项目和他们的坚持页面第一次呈现时,HTML 5本地存储中的状态,这将导致所有持久保存的喜爱项显示与用户将它们保存到本地存储时完全相同 根视图 视图是简单的部分,因为大多数实际工作是由各种服务和控制器完成的。下面是根视图标记: 隐藏,收缩,复制Code
<divng-repeat="imageitem in imageitems"> <tabledraggableresizableclass="imageHolder, imageDropShadow"ng-style="imageitems[$index].styleProps()"> <tr> <tdclass="imageHeader"> {{imageitem.name}}</td> </tr> <tr> <tdalign="center"> <imgng-src="http://www.codeproject.com/app/images/{{imageitem.name}}"class="imageCell"/> </td> </tr> <tr> <tdalign="center"> <imgsrc="http://www.codeproject.com/app/images/favIcon.png"width="25%"class="favIcon"tooltiphref=""title="Save To Favourites"ng-click="addToFavourites($index)"/> <imgsrc="http://www.codeproject.com/app/images/favDelete.png"width="25%"class="favIcon"title="Remove From Favourites"tooltipng-click="removeFromFavourites($index)"ng-show="imageitems[$index].isFavourite"/> </td> </tr> </table> </div>
最喜欢的页面 虽然没有根页面和它的控制器复杂,但收藏夹是第二复杂的页面,所以在我们深入到它的代码之前,可能需要做一些解释。 那么这个页面到底是做什么的呢? 其思想是,在HTML 5本地存储中将有一组(可能是空的)图像数据,这些数据针对某个键存储。当favorites视图被请求时,这个HTML 5本地存储的数据将被检查,并且对于找到的所有项目,将呈现一个小的缩略图。用户也可以点击任意一个缩略图来启动一个ColorBox jQuery插件。 这是收藏夹页面看起来像一些项目保存在本地存储: 单击图像查看大图 当你点击其中一个按键的时候它会是这样的: 那么这个页面是如何工作的呢?其中一个困难的部分是一些你们可能都认为很琐碎的事情。在本地存储中,我们有一个1维JSON stringified数组,我想把它变成一个2维的表格布局,我可以使用angular。js ng-repeat绑定。 比如控制器 让我们先看看控制器。 隐藏,收缩,复制Code
define(['controllers/controllers', 'services/imageService', 'services/utilitiesService', 'services/localStorageService'], function (controllers) { controllers.controller('FavsCtrl', ['$window', '$scope', 'ImageService', 'UtilitiesService', 'LocalStorageService', function ( $window, $scope, ImageService, UtilitiesService, LocalStorageService) { $scope.imageItemsStorageKey = 'imageItemsKey'; $scope.favImageItems = []; $scope.columnCount = 5; $scope.favText = ''; $scope.shouldAlert = false; $scope.tableItems = []; while ($scope.tableItems.push([]) < $scope.columnCount); if (!LocalStorageService.isSupported()) { $scope.favText = 'Local storage is not supported by your browser, so viewing favourites isn\'t possible'; $scope.shouldAlert = true; } else { var currentStoredFavs = LocalStorageService.fetch($scope.imageItemsStorageKey); var currentFavs = []; if (currentStoredFavs != undefined) { currentFavs = JSON.parse(currentStoredFavs); } if (currentFavs.length == 0) { $scope.favText = 'There are no favourites stored at the moment'; $scope.shouldAlert = true; } else { var maxRows = Math.ceil(currentFavs.length / $scope.columnCount); $scope.favText = 'These are your currently stored favourites. You can click on the images to see them a bit larger'; if (currentFavs.length < $scope.columnCount) { $scope.tableItems[0] = []; for (var i = 0; i < currentFavs.length; i++) { $scope.tableItems[0].push(ImageService.createFavImageItem (currentFavs[i].name)); } } else { var originalIndexCounter = 0; for (var r = 0; r < maxRows; r++) { for (var c = 0; c < $scope.columnCount; c++) { if (originalIndexCounter < currentFavs.length) { $scope.tableItems[r][c] = ImageService.createFavImageItem (currentFavs[originalIndexCounter].name); originalIndexCounter++; } } } } } if ($scope.shouldAlert) { UtilitiesService.delayedAlert($scope.favText); } } }]); });
可以看出,这里的大部分工作是HTML 5的数据本地存储,并翻译成JSON字符串表示1维数组,然后到一个二维的结构,可以用来绑定标记内。 这里还有一些其他的注意事项: 我们使用美元Angular.js窗口而不是“窗口”,允许美元的窗口服务,取而代之的是一个模拟我们利用LocalStorageService之前看到我们利用UtilitiesService之前看到我们利用ImageService之前看到 比如视图 所有繁琐的工作都是在控制器中完成的,视图标记非常小: 隐藏,复制Code
<divclass="infoPageContainer"> <h2>Favourites</h2> <p>{{favText}}</p> <tableid="favsTable"> <trng-repeat="row in tableItems"> <tdng-repeat="cell in row"> <acolorboxtitle="{{cell.name}}"ng-href="http://www.codeproject.com/app/images/{{cell.name}}"> <imgng-src="http://www.codeproject.com/app/images/ {{cell.name}}"class="favSmallIcon"/> </a> </td> </tr> </table> </div>
请注意整洁的嵌套ng-repeat,一旦在范围中有了要遍历的正确结构,那么在Angular.js中执行表布局是多么容易 ColorBox指令 谜题的最后一部分是如何将这些项制作成jQuery ColorBox。了解了我们现在所知道的,我们应该能够意识到,这个问题的答案在于使用另一个指令。 是的,你猜它是一个colorbox指令,可以在一个…colorbox> /a>标记中的锚标记。 这是colorbox指令代码,你可以看到这是局限于属性的用法,并简单地代表实际工作jQuery colorbox(这是引用Require.js配置中的要求,所以我们知道它加载好,否则Angular.js不会开始)。 隐藏,复制Code
define(['directives/directives'], function (directives) { directives.directive('colorbox', ['$rootScope', function ($rootScope) { return { restrict: 'A', //may need the model to be passed in here //so we can apply changes to its left/top positions link: function (scope, element, attrs) { $(element).colorbox({ rel: 'group3', transition: "elastic", width: "50%", height: "50%" }); } }; }]); });
关于页面 about页面只是静态文本,所以这里没有真正的groovy。我添加这个页面只是为了在演示应用程序中有足够的路径,使其功能更加完整,我想。为了完整起见,下面是about页面的屏幕截图: ” 单击图像查看大图 就是这样 无论如何,这就是我现在想说的,我希望你喜欢这篇文章并从中得到一些东西。我知道我个人是这么做的。这是一个棘手的编写方法,因为它有一些新的概念,为我,但我很高兴的最终结果。如果你喜欢你读到的/看到的,并倾向于留下一个投票/评论,那将是很酷的。 不管怎样,下次再见了,希望下次不会像这次一样耗时太久。 本文转载于:http://www.diyabc.com/frontweb/news17319.html