В этом сообщении я хотел бы рассказать о двух вещах:
- Как я расширил AngularJS для поддержки журналов транзакций (подробных различий) для массивов. Я разработал проект на GitHub: https://github.com/attodorov/angular.js
- Как я создал собственную угловую директиву для сетки Ignite UI.
В результате вы увидите, как IgniteUI Grid очень хорошо интегрируется с Angular, а также поддерживает полное двустороннее связывание данных.
В прикрепленном проекте есть все, что вам нужно, чтобы увидеть его в действии, просто распакуйте архив и запустите angular.html .
Давайте начнем с некоторой справочной информации о том, как AngularJS выполняет обновления между моделями и представлениями. Я бы не хотел подробно останавливаться на этом, потому что это уже подробно описано в следующем посте SO:
http://stackoverflow.com/questions/9682092/databinding-in-angularjs
Это очень элегантный подход, гораздо более элегантный, чем смена слушателей. К сожалению, если вы привязываетесь к двумерному массиву объектов и изменяете свойство некоторого объекта, у вас нет возможности узнать, что именно было изменено. Angular дает вам старый массив и новый массив, и у вас есть выбор, чтобы снова их различать, но это не оптимально. Вот почему я изменил функции equals и $ digest в Angular для поддержки передачи подробной информации о том, что на самом деле изменилось. Таким образом, если какое-либо свойство изменяется где-то в моем массиве, и я ограничил его сеткой пользовательского интерфейса Ignite, используя Angular, я могу перерисовать только ячейку, которая привязана к этому винту, без повторного применения равных ко всему массиву , Вот фрагмент того, как это работает, обратите внимание на аргумент «diff»:
scope.$watch(attrs.source, function (value, last, currentValue, diff) { if (Array.isArray(diff)) { for (var i = 0; i < diff.length; i++) { // update cell values if (!diff[i].txlog) { continue; } for (var j = 0; j < diff[i].txlog.length; j++) { // get the td var colIndex = $("#" + element.attr("id") + "_" + diff[i].txlog[j]["key"]).index(); var key = scope[attrs.source][diff[i].index][attrs.primarykey]; var td = element.find("tr[data-id='" + key + "']").children().get(colIndex); $(td).html(diff[i].txlog[j]["newVal"]); } } } }, true);
Аргумент «diff» — это массив объектов, имеющих следующую структуру:
{index: , txlog: []}
Где каждая передача имеет следующий формат:
{key: , oldVal: , newVal: }
Давайте пройдемся по нашей странице шаг за шагом. Сначала нам нужно обратиться к модифицированной библиотеке AngularJS (вы можете получить ее из моего разветвленного проекта; я включил папку «build», которая содержит объединенные и минимизированные ресурсы). Вам также необходимо сослаться на скрипт контроллеров , а также на скрипт, содержащий пользовательские директивы Ignite UI.
<head> <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" type="text/css"></link> <link rel="stylesheet" href="css/themes/infragistics/infragistics.theme.css" type="text/css"></link> <link rel="stylesheet" href="css/structure/infragistics.css" type="text/css"></link> <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/infragistics.core.js"></script> <script type="text/javascript" src="js/infragistics.lob.js"></script> <script src="js/controllers.js"></script> <script src="js/igniteui-directives.js"></script> <title>Angular.JS and Ignite UI</title> </head>
Сценарий controllers.js имеет действительно простой формат: он просто определяет NorthwindCtrl, который мы позже укажем в HTML, следуя соглашениям Angular, и возвращает источник данных в формате JSON — сам источник данных жестко закодирован в реализации контроллера, для простоты.
function NorthwindCtrl($scope) { $scope.northwind = [ ]; }
Следующее, что мы сделаем, это установим директиву ng-controller для тела, которое в нашем случае будет NorthwindCtrl:
<body ng-controller="NorthwindCtrl">
Обратите внимание, что нам также необходимо установить директиву ng-app для тега html, чтобы Angular мог правильно анализировать пользовательские и зарезервированные директивы и шаблоны:
Затем мы объявляем нашу пользовательскую директиву ignitegrid следующим образом:
id="grid1" data-source="northwind" data-height="400px" data-updating="true" data-primarykey="ProductID"> </ignitegrid>
Я также добавляю простую таблицу на страницу, чтобы мы могли видеть, как двусторонние обновления работают в режиме реального времени между двумя пользовательскими интерфейсами.
<table id="simpletable"> <tbody> <tr ng-repeat="product in northwind"> <td>{{product.ProductID}}</td> <td><input type="text" ng-model="product.ProductName"></input></td> <td>{{product.QuantityPerUnit}}</td> <td>{{product.UnitPrice}}</td> </tr> </tbody> </table>
Вы можете увидеть, что способ определения сетки зажигания — использование полностью настраиваемого тега HTML, а также некоторых атрибутов data- *, которые отражают параметры виджета. Давайте посмотрим на нашу реализацию пользовательских директив, а также на то, как мы распространяем обновления от обновления сетки до модели Angular:
angular.module('igApp', []).directive('ignitegrid', function () { return { restrict: "E", template: "<table></table>", replace: true,
Объявление restrict: «E» означает, что наша пользовательская директива является пользовательским элементом, а не атрибутом. Мы также хотим, чтобы тег по умолчанию и его дочернее содержимое были заменены пустым тегом, где будет инициализирован фактический виджет сетки.
Далее, наиболее важной частью нашей пользовательской директивы является функция «ссылка», которая генерирует параметры инициализации и затем создает виджет igGrid для элемента. Он также обрабатывает iggridupdatingeditrowended клиентское событие, где мы устанавливаем новые значения в модели Angular, а затем вызываем $ apply, чтобы сработали все связанные слушатели и обновился DOM, который связывается с этими объектами. «северный ветер», на который мы ссылаемся из области видимости, в основном определяется в нашем контроллере Angular.
angular.module('igApp', []).directive('ignitegrid', function () { return { restrict: "E", template: "<table></table>", replace: true, link: function (scope, element, attrs) { //initialize an ignite UI grid on element, using attrs.igniteuiModel as the data source if (!scope.hasOwnProperty(attrs.source)) { throw new Error("The data source (dataSource) does not exist in the current context"); } var ds = scope[attrs.source], opts = {}; if (typeof (attrs.autogeneratecolumns) !== "undefined") { opts.autoGenerateColumns = attrs.autogeneratecolumns === "true" ? true : false; } opts.dataSource = ds; //opts.columns = scope.columns; if (attrs.height) { opts.height = attrs.height; } if (attrs.updating && attrs.updating === "true") { if (!Array.isArray(opts.features)) { opts.features = []; } opts.autoCommit = true; opts.features.push({name: "Updating"}); // we need to listen for updates in order to support two-way databinding in the grid // ensure that we don't handle it twice or create any recursion // the same can/should be done for cell editing, adding and deleting rows element.on("iggridupdatingeditrowended", function (e, args) { // we need the data source from the scope, but without triggering $digest for the grid itself // note that there may be other subscribers var ds = angular.element(element).scope()[attrs.source]; for (var i = 0; i < ds.length; i++) { if (ds[i][attrs.primarykey] === args.rowID) { ds[i] = args.values; break; } } // force $apply angular.element(element).scope().$apply(); }); } if (attrs.primarykey) { opts.primaryKey = attrs.primarykey; } element.igGrid(opts); // watch for changes from the data source to the view scope.$watch(attrs.source, function (value, last, currentValue, diff) { if (Array.isArray(diff)) { for (var i = 0; i < diff.length; i++) { // update cell values if (!diff[i].txlog) { continue; } for (var j = 0; j < diff[i].txlog.length; j++) { // get the td var colIndex = $("#" + element.attr("id") + "_" + diff[i].txlog[j]["key"]).index(); var key = scope[attrs.source][diff[i].index][attrs.primarykey]; var td = element.find("tr[data-id='" + key + "']").children().get(colIndex); $(td).html(diff[i].txlog[j]["newVal"]); } } } }, true); } } });
Подводя итог — используя подход, описанный в этом сообщении в блоге, и разветвленную версию AngularJS, вы получаете плавную поддержку двустороннего обновления и удобную интеграцию с сеткой интерфейса Ignite с использованием пользовательских директив. Вы можете применить тот же подход к другим виджетам Ignite UI.