В этой публикации хоче поделиться одним из способов работы с данными. Началось все с того, что мне дали задачу сделать календарь, в котором каждая ячейка представляет собой некоторую форму, причем в зависимости от выбора пользователя. То есть, генерация шаблона ячейки должна была происходить на стороне клиента, в зависимости от выбора разных полей. Сложность заключалась в том, что поля каждой формы можно редактировать, а значит нужно где-то хранить все изменения.
От создания моделей на каждую форму я сразу отказался, поскольку при изменении одного поля, должно было каким-то образом меняться другое. Логика взаимодействия полей распространялась на 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";
}
Автор: Karkat