bezumkin/miniShop2 — pull request #59 by antixrist (2013-07-31 19:12:41+00:00)
Title: Работоспособность miniShop2 с выключенным у пользователя javascript и несколько новых плюшек.

Наконец-то дошли руки описать что, зачем, куда и почему. Заодно в процессе сделал несколько фиксов своей писанины.

> я поржу посмотрю. MS2 работает js-only, все на это рассчитано.

Да тут особо не над чем ржать, коммит готов, страшного ничего нет. Теперь ms2 не только js-only. Вся работоспособность сохранилась полностью, плюс ещё пара плюшек. Изменения коснулись нескольких чанков, default.js, один сниппет, немного в 2х классах. Ну и php-обработчик action'ов.

В общем, что и зачем было сделано/изменено и обоснования выбранных решений. Сначала описание мелких фиксов, потом главное:

1. [Везде](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L16L131), [где](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L15L300) [встретил](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L15L321), заменил регулярки проверки email'а. В сравнении: [твой вариант](http://www.regexpr.ru/?pattern=%2F.%2B%40.%2B..%2B%2Fi&subject=mail%40example.com%0D%0Amail.mail%40example.com%0D%0Amail.mail%40example.example.com%0D%0Amail%40example%40example.com%0D%0Amail%40example..com%0D%0Amail%40%40example.com%0D%0A%40.com%0D%0A%40.%0D%0A%FB%E2%E0%FB%E0%40%FB%E2%E0%E2%E0%EF.%FB%E2%E0) и [мой](http://regexpr.ru/?pattern=%2F%5E%5B%5E%40%E0-%FF%C0-%DF%5D%2B%40%5B%5E%40%E0-%FF%C0-%DF%5D%2B%28%3F%3C%21%5C.%29%5C.%5B%5E%5C.%E0-%FF%C0-%DF%5D%7B2%2C%7D%24%2Fm&subject=mail%40example.com%0D%0Amail.mail%40example.com%0D%0Amail.mail%40example.example.com%0D%0Amail%40example%40example.com%0D%0Amail%40example..com%0D%0Amail%40%40example.com%0D%0A%40.com%0D%0A%40.%0D%0A%FB%E2%E0%FB%E0%40%FB%E2%E0%E2%E0%EF.%FB%E2%E0).

2. Изменил [валидацию](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L16L132) [ФИО](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L16R403). В текущем виде люди с двойными фамилиями и именами (да, даже такие есть) оставались не у дел. Сейчас вот такое: ```николай - костер--Валдау мл.``` корректно превращается в ```Николай Костер-Валдау Мл```. Мы же за [юзабилити](http://bezumkin.ru/sections/help/1516/#comment-13007), верно?

3. Метод [```msOrderHandler::add($key, $value)```](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L16L121) теперь не всегда возвращает ```msOrderHandler::success```, а только если валидация прошла успешно. Если валидация провалилась, то вернётся ```msOrderHandler::error```.
Теперь, если пользователь неправильно введёт email, система не сотрёт его из поля ввода, а [подсветит ошибку](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L384-L388) (полное описание изменений в default.js ниже). Ибо негоже полностью стирать то, что пользователь уже ввёл. Снова юзабилити.

4. [Проверку](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L15L72) подключения кастомных скриптов магазина переместил ниже, после описания js-настроек, потому что иначе js-настройки магазина не будут доступны в своём js-файле, что немного не логично.

5. Видимость блоков пустой мини-корзины (и заполненной) теперь [регулируется в css](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L3R12), а в скриптах к ```#msMiniCart``` просто [добавляется соответствующий класс](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L231-L235). Это позволило [избавиться от одного фильтра вывода](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L7L1).

Ну а теперь то, ради чего мы здесь собрались.

1. Прежде всего, чтобы магазин работал без js, все функциональные элементы, конечно же, [были](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_products_row.tpl#L4-L14) [обёрнуты](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_product_content.tpl#L7-L53) [в](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_cart_outer.tpl#L29-L31) [формы](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_order_outer.tpl#L1), а все дополнительные параметры передаются в [скрытых](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_products_row.tpl#L11-L13) (или, при необходимости, [не очень](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_cart_row.tpl#L9)) [инпутах](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_product_content.tpl#L8-L9).

2. Теперь надо, чтобы эти формы куда-то слали данные. Конечно, можно было бы сделать вот так:
```html

```
Но тогда в [этом](https://github.com/bezumkin/miniShop2/blob/master/assets/components/minishop2/action.php) файле, после выполнения нужного действия, пришлось бы отслеживать HTTP\_REFERER, чтобы редиректить на ту страницу, с которой была отправлена форма. Но мне это показалось не очень красивым.
Поэтому, в html-форме атрибут ```action``` не указываем:
```html

```
, а код action-файла был перенесён в плагин на событие [OnHandleRequest](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L12R8), а в нём, уже после выполнения нужного действия, отслеживается - если запрос аяксом, то делаем
```php
if ($isAjax) die($response);
```
Соответственно, чтобы не было никаких возможных несостыковок с другими дополнениями, параметр ```$_REQUEST['action']``` переименован в ```$_REQUEST['ms2_action']```. И в формах, нужный ms2_action передаём через кнопки типа submit:
```html
Отправить!
```
Всё это и позволило работать магазину с отключенным js.

3. Теперь ```default.js```. Здесь очень существенные переработки.

3.1. У всех "контроллеров" miniShop2 появился метод ```setup()``` ([```Order```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L282-L294), [```Cart```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L168-L178), [```Gallery```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L261-L265), в т.ч. у самого объекта [```miniShop2```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L10-L23)).
В ```setup()``` задаются переменные с селекторами html-элементов, которые нужны для работы этого контроллера (все jQuery-вызовы селекторов и отработки event'ов - полностью делегированные). Смысл - просто попытка отделить логику от представления. Так немного структурированней.
А в методах ```initialize()``` каждого из контроллеров ([```Order```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L295-L315), [```Cart```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L179-L187), [```Gallery```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L266-L278), сам объект [```miniShop2```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L24-L60)) вызывается, собственно, ```setup()``` этого контроллера и запускаются необходимые события и прочие вещи, нужные для того, чтобы этот контроллер стартанул и начал свою работу.

3.2. Основной принцип работы:
а) [отслеживаются ```submit```'ы](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L36) форм с классом ```ms2_form```;
б) [определяется](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L40) сработавший ```ms2_action```;
в) в переменную ```miniShop2.sendData``` [записываются](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L47-L51): отправляемые данные, сработавший экшн и jquery-объект сабмитнувшейся формы, для доступа к этим данным из любого места;
г) [запускается](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L53), тоже своеобразный контроллер, в котором, исходя из значения переменной ```miniShop2.sendData.action``` [вызывается нужный метод](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L68-L92) нужного контроллера (```miniShop2.Cart.add()```, ```miniShop2.Order.submit()```, etc).

3.3. И вот теперь самое интересное. Теперь отправкой всех ajax-запросов на сервер рулит метод ```miniShop2.send(data, controller, method)```. Делает он это не автоматически, его надо вызывать вручную, чтобы что-то отправить. Но делать это практически обязательно, потому что благодаря нему появилось несколько клёвых плюшек. Он принимает 3 параметра:
1) ```data``` - это параметры для отправки. Может быть в виде:
а) сериализированной строки

```
key=value&key2=value
```

б) в виде хэша:

```javacsript
var data = {
key	: value
,key2	: value2
};
```

в) или [сериализированного массива](http://jquery.page2page.ru/index.php5/%D0%9F%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85_%D1%84%D0%BE%D1%80%D0%BC%D1%8B_%D0%B2_%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2):

```javascript
var data = [{
name	: 'key'
,value	: 'value'
},
{
name	: 'key2'
,value	: 'value2'
}];
```
В общем, может быть всем тем, что принимает метод ```$.post()```/```$.get()```.

2) ```controller``` - имя контроллера (```Cart```/```Order```);
3) ```method``` - имя метода обозначенного контроллера (```add```/```getcosts```/etc). Детальное описание - ниже.

Дальше этот метод делает отправку запроса. И вот тут у нас появляется новая сущность - **коллбэки**.
Коллбэки бывают 2х видов - пользовательские и коллбэки miniShop'а. Суть та же, что и плагины в modx - реагируют на определённые события.
Перед отправкой запроса выполняются [коллбэки ```before```](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L137-L141) и если хотя бы один из них возвратит ```false```, то отправка данных осуществлена не будет.
После того, как запрос отправился и пришёл ответ, функция [анализирует тип ответа](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L144) ([```success```](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/model/minishop2/mscarthandler.class.php#L250) или [```before```](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/model/minishop2/mscarthandler.class.php#L231)) и, [если в ответе от сервера есть сообщение](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L145-L147) (```response.message```), выведет его. Тип выводимого сообщения будет определён автоматически - ```miniShop2.Message.success``` или ```miniShop2.Message.error```. После чего выполняет [соответствующие коллбэки](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L148-L149).
Так же есть коллбэки на выполнение ajax-запроса:
1) при [успешном выполнении](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L151-L154) запроса;
2) при [ошибке выполнения](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L155-L158) запроса;
3) и по [завершению выполнения](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L159-L163) запроса, т.е. независимо от того - удачен был запрос или нет.
Это стандартные события для [deffered-объектов jquery](http://jquery.page2page.ru/index.php5/Ajax_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_POST) (описание в конце страницы).

Пользовательские коллбэки должны лежать в ```miniShop2.Callbacks```, а коллбэки miniShop'а в в каждом из контроллеров в ключе ```callbacks```, т.е. ```miniShop2.Cart.callbacks```/```miniShop2.Order.callbacks```.
Формат коллбэков:

```javascript
miniShop2.Cart.callbacks.add = {
before	: function () {}
,response	: {
success	: function (response) {}
,error	: function (response) {}
}
,ajax	: {
done	: function () {}
,fail	: function () {}
,always	: function () {}
}
};
```

Из примера выше видно, что, чтобы набор коллбэков срабатывал, к примеру, при добавлении товара в корзину, его (этот набор) надо поместить в соответствующий объект с ключём события. Сложно объяснил, но, если нужно создать коллбэки на добавление в корзину, то записываем как в примере выше. Для пользовательских коллбэков, соответственно:

```javascript
miniShop2.Callbacks.Cart.add = {
before	: function () {}
,response	: {
success	: function (response) {}
,error	: function (response) {}
}
,ajax	: {
done	: function () {}
,fail	: function () {}
,always	: function () {}
}
};
```

Если какой-то коллбек не определён (например ```ajax.done``` или ```before```) или вообще ни один не определён, то ошибок не будет, всё пройдёт без эксцессов.

В любой функции-коллбэке ```this``` указывает на объект ```miniShop2```. А ```response.success(response)``` и ```response.error(response)``` принимают единственный параметр ```response```. Таким образом с сервера можно слать любые данные, а в коллбэках их обрабатывать.

**Зачем всё это?** Звучит очень сложно, выглядит изощрённо и совершенно не нужно. НО.
Вот код добавления [товара в корзину](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L188-L197). А вот код [получения стоимости заказа](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L399-L413). Всё намного структурированней, визуально чище и сразу понятно - что и зачем тут происходит.

А пользовательские коллбэки! Это же просто клад! Каждый, каждый раз стоят задачи сделать что-то, к примеру, показывать всплывашку какую-то, после добавления товара в корзину или сделать дополнительную проверку чего-нибудь, или посчитать что-то дополнительно к общей сумме заказа - варианты каждый раз новые и замудрённые. А коллбеки позволят, не влезая в исходный код ```default.js```, делать такие вещи на раз, просто в любом месте своего кода написав, например, [такое](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_cart_outer.tpl#L1-L9).
К тому же это реально избавляет от проблем обновлений (если кто-то использует свои скрипты и если при последующих доработках ```default.js``` придерживаться этой нотации).

3.3. Используя механизм коллбэков, я сперва написал для себя, а потом решил внести и в основной функционал (ибо классно получилось), - при смене способа доставки - динамическую подсветку обязательных полей.
Для этого вынес код, получения обязательных полей в [отдельный метод](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L16R200), который теперь возвращает ```msOrderHandler::success()```, и в скриптах добавил [метод запроса этих полей](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/js/web/default.js#L331-L354). В чанках добавил звёздочки к каждому полю ввода, а сам показ звёздочки регулируется [через css](https://github.com/antixrist/miniShop2/blob/master/assets/components/minishop2/css/web/default.css#L70-L75). Получилось норм.

Ну и по мелочи в ```default.js```:
3.4. Подключение доп. плагинов вынесено за пределы объявления объекта miniShop2.

3.5. Подгрузка плагина ajaxForm закомментирована, потому что (пока?) не используется.

4. Чтобы работала подсветка ошибок в полях ввода при выключенном js, в сниппет ```msOrder``` добавил [сбор и вывод](https://github.com/antixrist/miniShop2/compare/bezumkin:master...master#L14R131) этих ошибок. А в чанке получилось [так](https://github.com/antixrist/miniShop2/blob/master/core/components/minishop2/elements/chunks/chunk.ms_order_outer.tpl#L5) (как было раньше в Tickets (а сейчас почему-то этого нет..)).

Такие дела. Надеюсь коммит получит положительную оценку.

p.s. miniShop2 оочень круто сделан. Спасибо за него, в очередной раз)