В этом сообщении я хотел бы рассказать о двух вещах:
- Как я расширил 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.