В этой публикации хоче поделиться одним из способов работы с данными. Началось все с того, что мне дали задачу сделать календарь, в котором каждая ячейка представляет собой некоторую форму, причем в зависимости от выбора пользователя. То есть, генерация шаблона ячейки должна была происходить на стороне клиента, в зависимости от выбора разных полей. Сложность заключалась в том, что поля каждой формы можно редактировать, а значит нужно где-то хранить все изменения.
От создания моделей на каждую форму я сразу отказался, поскольку при изменении одного поля, должно было каким-то образом меняться другое. Логика взаимодействия полей распространялась на 3 таблицы базы данных, из которых 2 хотя бы на половину заполнены данными и одна, в которую мне нужно передавать изменения. Поэтому я решил сделать собственное хранилище (навроде Store в Ext js). Суть заключалась в том, чтобы представить каждый узел объекта формата JSON в самостоятельную единицу. То есть принцип следующий: я получаю все 3 таблицы и создаю 3 дерева указателей, где каждый родитель является контекстом дочернего узла. Возможно, звучит, немного запутанно. Вот пример.
var Customers = [ { "FirstName": "Customer first name 1", "LastName": "Customer second name 1", "Id": "1", "JobSpecificsList": [ { "Id": "1", "Name": "Orders group 1" }, { "Id": "2", "Name": "Orders group 2" } ], "WorkTypes": [ "2", "0", "1" ], "PaymentTypes": [ "1", "2", "3" ] }, { "FirstName": "Customer first name 2", "LastName": "Customer second name 2", "Id": "2", "JobSpecificsList": [ { "Id": "1", "Name": "Orders group 1" }, { "Id": "61", "Name": "Orders group 3" }, { "Id": "58", "Name": "Orders group 4" } ], "WorkTypes": [ "2", "0" ], "PaymentTypes": [ "1" ] } ]
var orders = [ { "Id":"1", "CustomerId": "1", "OrderName": "Order 1", "Start": "2015-04-12T11:22:00.0000000", "End": "2015-04-12T22:11:00.0000000" }, { "Id":"2", "CustomerId": "2", "OrderName": "Order 2", "Start": "2015-04-12T11:22:00.0000000", "End": "2015-04-12T22:11:00.0000000" }, { "Id":"3", "CustomerId": "1", "OrderName": "Order 3", "Start": "2015-04-12T11:22:00.0000000", "End": "2015-04-05T22:11:00.0000000" } ]
var schedule = [ { "CustomerId": "1", "StartWeek": "2015-04-13T00:00:00.000Z", "Schedule": [ { "Id": "-1", "Start": "2015-04-15T17:00:00+06:00", "End": "2015-04-15T17:30:00+06:00", "WorkType": "0" "JobSpecificId": "-1", "PaymentType": "-1", }, { "Id": "-1", "Start": "2015-04-16T19:00:00+06:00", "End": "2015-04-16T20:30:00+06:00", "WorkType": "2", "JobSpecificId": "-1", "PaymentType": "-1" }, { "Id": "-1", "Start": "2015-04-19T00:00:00+06:00", "End": "2015-04-19T00:10:00+06:00", "WorkType": "-1", "JobSpecificId": "-1", "PaymentType": "-1" } ] }, { "CustomerId": "2", "StartWeek": "2015-04-13T00:00:00.000Z", "Schedule": [ { "Id": "-1", "Start": "2015-04-13T00:00:00+06:00", "End": "2015-04-13T00:00:00+06:00", "WorkType": "-1", "JobSpecificId": "-1", "PaymentType": "-1" } ] }, { "CustomerId": "25", "StartWeek": "2015-04-13T00:00:00.000Z", "Schedule": [ { "Id": "-1", "Start": "2015-04-19T00:00:00+06:00", "End": "2015-04-19T00:00:00+06:00", "WorkType": "-1", "JobSpecificId": "-1", "PaymentType": "-1" } ] } ]
Есть список заказчиков, список заказов и расписание отправки. В календаре происходит редактирование расписания. То есть курьер может просматривать назначенные на него заказы. В расписание так же можно добавить/удалить курьера. Добавление или удаление происходит понедельно.
Реализация
Объект Store отвечает за хранение, получение и отправку данных на сервер. Реализация примерно такая:
var Store = function(){ var wrapSchedule= undefined; … this.loadScheduleList = function(){ ... wrapSchedule = inMemory(serverResponse); }; … this.getScheduleList = function(){ return wrapSchedule }; … this.saveScheduleList = function(){ var clientResponse= dumpAcc(wrapSchedule); ... }; }
Функция InMemory как раз занимается тем, что приводит объект или массив, полученный с сервера в дерево указателей:
function inMemory(from) { return (function getNode() { var that = this; function facade(innerValue) { function facadeArray(_innerValue) { return (function(context) { return { get: function() { return context; }, set: function(value) { context = clone(value); }, indexOf: function(index) { return context[index]; }, push: function (v) { context.push(getNode.call(clone(v))); }, splice: function(index, count) { context.splice(index, count); }, orderBy: function (comparator) { context.sort(comparator); }, filter: function (predicate) { return facadeArray(context.filter(predicate)); } } })(_innerValue); } function facadeObject(_innerValue) { return (function(context) { return { get: function() { return context; }, set: function(value) { context = clone(value); }, update: function(obj) { var keys = Object.keys(obj); for (var prop_i = 0; prop_i < keys.length; prop_i++) { if (context[keys[prop_i]]) { context[keys[prop_i]] = clone(obj[keys[prop_i]]); } } }, append: function(obj) { var keys = Object.keys(obj); for (var prop_i = 0; prop_i < keys.length; prop_i++) { context[keys[prop_i]] = clone(obj[keys[prop_i]]); } } } })(_innerValue); } function facadePrimitive(_innerValue) { return (function(context) { return { get: function() { return context; }, set: function(value) { context = value; } } })(_innerValue); } if (innerValue instanceof Array) { return facadeArray(innerValue); } if (isPrimitive(innerValue)) { return facadePrimitive(innerValue); } return facadeObject(innerValue); } if (that instanceof Array) { for (var i = 0; i < that.length; i++) { if (!isPrimitive(that[i])) that[i] = getNode.call(that[i]); else { that[i] = facade(that[i]); } } } if (!(that instanceof Array)) { for (var prop_i = 0; prop_i < Object.keys(that).length; prop_i++) { var field = that[Object.keys(that)[prop_i]]; if (!isPrimitive(field)) { that[Object.keys(that)[prop_i]] = getNode.call(field); } else { that[Object.keys(that)[prop_i]] = facade(field); } } } that = facade(that); return that; }).call(from); };
Доступ к каждому уровню осуществляется через get, set или любую другую функцию узла. Например в facadeArray можно реализовать метод first, тогда получение расписания определенного курьера за определенную неделю выглядело бы так:
var firstOnWeekPredicate = function(x){ return x.get().CustomerId.get() == "1" && x.get().StartWeek.get() == '2015-04-13T00:00:00.000Z'; } var scheduleByСourierIdAndWeek = store.getScheduleList().get().first(firstOnWeekPredicate).get().Schedule
Для тех, кто еще не знает про такие удобные функции как where, first, last, union, map и т.д., советую посетить underscorejs.org
Теперь во внутреннее значение (массив) scheduleByСourierIdAndWeek можно добавить новый день недели или изменить существующий.
Соответственно:
scheduleByСourierIdAndWeek.push({ "Id": "-1", "Start": "2015-04-15T00:00:00+06:00", "End": "2015-04-15T00:00:00+06:00", "WorkType": "0" "JobSpecificId": "-1", "PaymentType": "-1" }) scheduleByСourierIdAndWeek.get()[0].update({WorkType: 2})
Весь «фасад» в функции getNode может быть переработан под собственные нужды.
Ну и что же мы получили в итоге? Мы разделили большой объект, полученный с сервера на связанные узлы. Раздали узлы в участки кода, где с ними происходит работа. Преобразовали данные так, как захотел пользователь. Теперь нужно все это собрать в объект и отправить обратно на сервер. Для этого есть функция dumpAcc.
function dumpAcc(storeAcc) { return clone((function getNodeValue(acc) { if (typeof (acc.get ? acc.get() : acc) === "boolean" || typeof (acc.get ? acc.get() : acc) === "number" || typeof (acc.get ? acc.get() : acc) === "string") { return acc.get ? acc.get() : acc; } else if (acc.get() instanceof Array) { var _a = []; for (var _a_i = 0; _a_i < acc.get().length; _a_i++) { _a.push(getNodeValue(acc.get()[_a_i])); } return _a; } else { var _o = {}; for (var prop in acc.get()) { if (acc.get().hasOwnProperty(prop)) _o[prop] = getNodeValue(acc.get()[prop]); } return _o; } })(storeAcc)); }
То есть для того, чтобы получить clientResponse, нужно выполнить JSON.stringify(dumpAcc(store.getScheduleList())).
И напоследок функции clone, isPrimitive, которые я использовал.
function clone(a) { return JSON.parse(JSON.stringify(a)); } function isPrimitive(value) { return typeof (value) === "string" || typeof (value) === "number" || typeof (value) === "boolean"; }
ссылка на оригинал статьи http://habrahabr.ru/post/255881/
Добавить комментарий