В этой статье будет описано пошаговое создание фронтенда для прототипа SPA интернет магазина. Серверная часть которого написана на node.js с использованием express, шаблонизатора — twig, базы данных — NeDB, для обработки данных форм — модуль formidable, клиентская часть написана на htmlix.
Для тех кто не знаком с Htmlix, почитать основы построения приложений можно здесь.
Исходные данные для данного урока можно скачать здесь.
Это уже готовый пример, мы просто удалим из файла /static/js/front.js все что там есть и вставим вместо этого:
var State = { };
window.onload = function(){
///создаем экземпляр HTMLix
var HM = new HTMLixState(State);
console.log(HM);
}
Итак мы полностью удалили фронтенд из данного примера, и у нас остался сервер приложения, давайте разберемся как он работает:
- сперва нужно установить все модули которых сейчас нет, для этого введем в консоли: npm install
- далее ввести в командной строке node app
- затем перейти по адресу localhost:3000
Итак покликав по различным пунктам меню мы видим работающий сервер, без фронтенд части т.к. мы только что ее удалили.
Теперь заново поэтапно ее создадим, но для начала разберемся как работает сервер.
Перейдя по адресу «localhost:3000/» нам отдается верхнее меню с тремя пунктами, основная страница с шестью карточками товара, и список категорий с левой стороны.
Чтобы отдать нам это все сервер посылает два запроса к базе данных, в одном он находит все категории, во втором первые шесть карточек товара из таблицы, затем присоединяет к каждой карточке товара два поля из совпадающей по id категории товара, затем отдает нам представление /views/index.twig передав в него массив с категориями — categories и массив с карточками товара carts.
В самом представлении /views/index.twig мы загружаем из папки /twig_templates шапку сайта и верхнее меню:
{% include '/twig_templates/header.twig' %}
Далее создаем разметку для главной страницы и страницы категорий, в ней создаем список категорий из переданного в шаблонизатор массива categories:
<section class="categories" >
<ul class="nav flex-column" data-categories="array">
{% for category in categories %}
<li class="nav-item" data-category="container">
<a {% if activeCategory == category.idCategory %}
class="nav-link active"
{% else %}
class="nav-link"
{% endif %}
data-category-click="click" data-category-title="text" data-category-class="class" data-category-data="{{ category.idCategory }}" class="nav-link" href="/category/{{ category.idCategory }}">{{ category.titleCategory }}</a>
</li>
{% endfor %}
</ul>
</section>
Пока что не обращаем внимания на записи в теге начинающиеся с data- все это относится к фронтенду и нам пока что не интересно.
Далее в цикле создаем все карточки товара из массива carts:
{% for cart in carts %}
<div data-cart="container" class="col-12 col-sm-6 col-lg-4">
<div class="card" style="margin-bottom: 10px;">
<img data-cart-src_img="src" src="/static/upload/{{cart.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 data-cart-title="text" class="card-title">{{cart.title}}</h5>
<h6 data-cart-title_category="text" class="card-subtitle mb-2 text-muted">
{{cart.titleCategory}}
</h6>
<div class="row justify-content-between">
<div class="col-6">
<h5 data-cart-cost="text" class="card-title">{{cart.cost}}</h5>
</div>
<div class="col-6" style="padding-right: 0px;">
<a data-cart-click="click" data-cart-data="/cart/{{cart._id}}" href="/cart/{{cart._id}}" class="btn btn-primary btn-sm">Смотреть</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
Затем в самом конце мы загружаем footer, в котором подключаем htmlix.js и frontend.js — наш файл с фронтендом:
{% include '/twig_templates/footer.twig' %}
Аналогично для адреса /category/:idCategory мы также загружаем все категории, затем ищем карточки товара для данной категории а потом также передаем все это в представление /views/index.twig.
Также давайте сразу рассмотрим два адреса: /json и /category/:idCategory/json перейдя по этим адресам мы получим вместо представления — массив с карточками товара в формате json, для первого адреса это первые шесть карточек, а для второго — карточки соответствующей категории :idCategory.
Теперь разберем маршрут /cart/:idCart
Здесь мы загружаем все категории, затем одну карточку соответствующую :idCart затем также присоединяем ей два поля из таблицы категорий, далее отдаем представление /views/cart.twig передав в него карточку — cart и массив с категориями categories.
Далее в представлении /twig_templates/header.twig. Подключаем header, затем создаем список категорий из массива categories.
Создаем разметку для карточки товара, в неё мы вставляем данные из объекта cart, а также подключаем один из двух вариантов шаблона из папки /twig_templates/: cart_variant_option.twig или cart_variant_radio.twig который мы выбираем при создании категории товара (доставка или количество).
<div class="col-md-5" data-cart_single-variant_tmpl="render-variant">
{% set templ = cart.variant_tmpl %}
<!-- создали переменную set templ на основе свойства объекта cart.variant_tmpl -->
{% include '/twig_templates/' ~ templ ~ '.twig' %}
<!-- динамически вставили шаблон из папки '/twig_templates/' на основании переменной с именем шаблона templ -->
</div>
В конце разметки подключаем footer.
Маршрут /cart/:idCart/json отдает пустой массив [], данный роут используется для заглушки.
Итак, мы достаточно знаем о сервере чтобы начать строить приложение пока что для трех роутов.
Давайте сначала разберемся с роутами "/" и "/category/:idCategory":
Верхнее меню пока что использовать не будем, будем строить приложения для всего что находится под ним.
Здесь страницу можно разделить на два компонента это категории и карточки товара, давайте создадим два компонента в описании приложения это: categories и carts:
В html коде файла /views/index.twig они уже обозначены как data-carts=«array» — компонент — массив carts и data-cart=«container» — контейнер cart компонента carts. data-categories=«array» — компонент — массив categories, data-category=«container» — контейнер category компонента categories.
Создадим их описание в javascript:
var State = {
categories: { //компонент категории
container: "category", //контейнер компонента категории
props: [ ],
methods: {
}
},
carts: { //компонент карточки товара
container: "cart", //контейнер компонента карточки товара
props: [ ],
methods: {
}
}
}
Итак мы создали два компонента categories и carts давайте добавим несколько свойств для компонента categories:
* «data» — данные которые содержат idCategory,
* «click» — обработчик кликов по категории,
* 'class' — доступ к классу категории для изменения цвета текущей категории,
* «title» — доступ к тексту внутри категории,
В html коде файла /views/index.twig они уже обозначены как data-category-data="{{ category.idCategory }}", data-category-click=«click», data-category-class=«class», data-category-title=«text».
Добавим их в описание приложения:
categories: {
container: "category",
props: [ "data", "click", 'class', "title"],
methods: {
click: function(){
event.preventDefault();
console.log(this);
},
}
},
Итак мы создали четыре свойства для каждого контейнера категории ( «category» ), и для свойства «click» метод который пока что ничего не делает, только отменяет переход по ссылке и перезагрузку страницы. Давайте разберемся что нам нужно сделать при клике по категории:
1 — загрузить массив с карточками товара для дальнейшего использования, чтобы построить компонент — массив carts на основании полученных данных.
2 — удалить класс .active у предыдущей и установить у новой категории.
Для первого пункта создадим метод load_carts в общих для всего приложения методах stateMethods и поместим загруженные карточки в переменную carts объекта stateProperties — общих для всего приложения переменных;
Для второго пункта создадим пользовательское событие «emiter-click-on-category» и будем слушать его во всех контейнерах category чтобы добавить или удалить класс при его наступлении, вызывать событие будем в методе click контейнера category.
Итак напомню что свойства в массиве props контейнера, либо arrayProps массива можно обозначать двумя способами это, по имени свойства например «click» тогда нужно указывать его в html разметке как data-имя_контейнера-click=«click».
И второй способ это массивом [ 'имя_свойства', «тип_свойства», «селектор для поиска свойства»] тогда нам не требуется указывать данное свойство в html разметке, т. к. мы его ищем с помощью селектора, если оставить селектор пустым "" то свойство будет присвоено тегу самого контейнера, либо тегу самого массива — для свойств arrayProps.
Изменим описание приложения:
var State = {
categories: {
container: "category",
//добавили слушателя события "emiter-click-on-category" определив свойство вторым способом
props: [ "data", "click", 'class', "title", ['listner_click_on_category', "emiter-click-on-category", ""]],
methods: {
click: function(){
event.preventDefault();
var categoryId = this.parent.props.data.getProp();
//вызываем событие "emiter-click-on-category"
// передав в него данные со свойства data контейнера по которому был клик
this.rootLink.eventProps["emiter-click-on-category"].setEventProp(categoryId);
//создаем url на основе данных со свойства data контейнера,
//один для истории - historyUrl понадобится нам в дальнейшем,
// второй чтобы сделать запрос для получения карточек товара на адрес /category/:idCategory/json
var historyUrl = "/category/"+categoryId;
var url = historyUrl+"/json";
this.rootLink.stateMethods.load_carts(url, this); //вызываем метод load_carts и передаем в него url и this чтобы не потерять контекст
},
listner_click_on_category: function(){
//в слушателе события клика по категории который мы вызываем в методе выше - удаляем класс "active" со всех контейнеров
// затем устанавливаем его на контейнере данные свойства data которого совпадают
// с данными переданными в событие "emiter-click-on-category"
this.parent.props.class.removeProp("active");
if(this.parent.props.data.getProp() == this.emiter.prop){
this.parent.props.class.setProp("active");
}
},
}
},
carts: {
container: "cart",
props: [ ],
methods: {
}
},
stateProperties:{ //объект для хранения общих переменных приложения
carts: [],
},
stateMethods: {
fetchCategoryCarts: function(url, callb){
// общий метод для загрузки данных с какого либо адреса get запросм,
//принимает в параметрах адрес - url и функцию обратного вызова callb,
// в которую он передаст полученные данные
fetch(url).then((response) => {
if(response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
}).then((json) => {
callb(json);
}).catch((error) => {
console.log(error);
});
},
load_carts: function(url, context ){
//метод вызывает fetchCategoryCarts передавая в него функцию обратного вызова
// в которой подставляем данные с сервера в переменную .carts
//в дальнейшем также будем вызывать здесь событие "emiter-load-carts", которое мы еще не создали, поэтому пока что закоментируем его
context.rootLink.stateMethods.fetchCategoryCarts(url, function(data){
context.rootLink.stateProperties.carts = data;
console.log(data)
//context.rootLink.eventProps["emiter-load-carts"].setEventProp(data);
});
},
},
eventEmiters: {//создали объект со всеми пользовательскими событиями
["emiter-click-on-category"] : { //добавили событие "emiter-click-on-category"
prop: [],
},
}
}
Итак теперь при клике по категории у нас меняется класс текущей категории с помощью события «emiter-click-on-category», а также загружаются данные с сервера с помощью метода load_carts объекта stateMethods, которые мы сохраняем в свойстве carts объекта stateProperties и выводим пока что в консоль.
Далее давайте добавим свойства контейнера cart компонента carts:
* «data» — данные которые содержат '/cart/:idCart' ( "/cart/{{cart._id}}" ),
* «title_category» — название категории {{cart.titleCategory}},
* «cost» — стоимость товара {{cart.cost}},
* 'title' — доступ к тексту внутри карточки ( {{cart.title}} ),
* «src_img» — адрес картинки карточки "/static/upload/{{cart.image}}",
* «click» — обработчик события клика по карточке товара,
а также создадим дополнительное свойство компонента — массива carts — 'listener_load_carts' которое будет слушать событие «emiter-load-carts» — загрузки карточек товара и обновлять массив с карточками при его наступлении, мы его будем вызывать в методе load_carts объекта stateMethods.
Теперь добавим свойство к нашему компоненту carts и все свойства к контейнеру cart компонента carts:
carts: {
//добавили свойство listener_load_carts для массива carts
arrayProps: [ ['listener_load_carts', "emiter-load-carts", ""] ],
arrayMethods: {
listener_load_carts: function(){ //слушаем событие "emiter-load-carts"
this.parent.removeAll(); //переходим из свойства массива в сам массив и очищаем его
var carts = this.emiter.prop; //получаем массив из события "emiter-load-carts" которое мы вызовем в методе load_carts при клике по категории товара
for(var i=0; i<carts.length; i++){
//в цикле перебираем полученный массив с карточками товара
var cart = carts[i];
var props = { //создаем объект со всеми изменяемыми свойствами для контейнера cart
title: cart.title,
title_category: cart.titleCategory,
cost: cart.cost,
src_img: '/static/upload/'+cart.image,
data: "/cart/"+cart._id
}
this.parent.add(props);
//добавляем новый контейнер в массив, передав в него начальные данные для свойств
}
}
},
container: "cart",
//добавили все свойства для контейнера cart
props: ['title', "title_category", "cost", "click", "src_img", "data" ],
methods: {
click: function(){ //метод для обработки кликов по карточке товара,
//пока что просто выводит url карточки в консоль
event.preventDefault();
var url = this.parent.props.data.getProp();
console.log(url);
}
}
},
Также не забудем раскоментировать вызов события «emiter-load-carts» в методе load_carts и добавить новое событие в объект eventEmiters:
load_carts: function(url, context ){
context.rootLink.stateMethods.fetchCategoryCarts(url, function(data){
context.rootLink.stateProperties.carts = data;
console.log(data)
context.rootLink.eventProps["emiter-load-carts"].setEventProp(data);
//раскомментировали вызов события "emiter-load-carts"
//теперь мы передаем в него массив carts полученный с сервера,
// в обработчике события их можно будет получить вызвав this.emiter.prop
});
},
///*********************************************
eventEmiters: {
// ------------------------------
["emiter-load-carts"]: { //добавили новое событие
prop: "",
}
}
Ну вот теперь при клике по категории у нас загружаются данные с сервера и отображаются в компоненте carts, однако при клике по карточке товара пока что ничего не происходит, кроме вывода в консоль адреса просматриваемой карточки '/cart/:idCart'
Далее создадим компонент карточку товара cart_single, но прежде чем ее создать разберемся как работает htmlix:
Итак при загрузке страници с адреса "/" либо "/category/:idCategory", сервер присылает нам html код двух компонентов, точнее трех, еще «menu», но его мы пока что не используем, мы используем html код для компонентов carts и categories, на основе этого кода htmlix создает шаблоны для компонентов, но шаблон для компонента cart_single нам не передается по данным адресам, т.к. его там нет. Как же нам его «догрузить»? Для этого, мы создаем компонент cart_single в описании приложения и помещаяем его в специальный объект «fetchComponents» а в настройках `stateSettings.templatePath` указываем адрес по которому загружать шаблоны для данного компонента, да и всех остальных, которые еще появятся. Таким образом приложение сначала создаст компоненты которые прислал сервер вместе с html, а затем после отправки второго запроса по адресу stateSettings.templatePath создаст остальные компоненты, находящиеся в объекте fetchComponents.
Так бы мы сделали если бы вход в приложения у нас всегда бы был с адресов, "/" либо "/category/:idCategory", а что если мы потом первую загрузку приложения произведем с адреса "/cart/:idCart"? Приложение выдаст ошибку, что не может найти шаблон для массива carts, так как теперь его не будет по этому адресу и нам нужно помещать теперь carts в объект «fetchComponents» a single_cart наоборот оттуда достать, т.к. он должен инициализироваться первым вместе с компонентом categories, что же делать?
Для решения этой задачи мы будем использовать HTMLixRouter(), он перед тем как создать экземпляр приложения, после загрузки страницы проверяет какой сейчас адрес url и сравнивает его с теми которые мы ему укажем, и затем сам помещает те для которых нет шаблона в первой загрузке, в объект «fetchComponents», после чего инициализирует приложение, таким образом мы избежим создания, нескольких вариантов описания приложения для разных url и соответственно дублирования кода.
Итак у нас пока что есть три адреса "/" ,"/category/:idCategory" и "/cart/:idCart", три основных компонента carts, categories и cart_single. На первых двух адресах у нас первыми должны инициализироваться компоненты carts и categories, а на адресе "/cart/:idCart" — categories и cart_single.
Создадим для них объект routes и поместим его отдельно от писания приложения State в переменную:
var routes = {
["/"]: {
first: ["categories", 'carts'], /// компоненты которые есть в html файле указываются в этом массиве, остальные будут загружены с шаблона, в fetch запросе асинхронно
routComponent: {
router_carts: "carts", //компонент соответствующий данному роуту
},
templatePath: "/static/templates/index.html" // папка для загрузки шаблонов
},
["/category/:idCategory"]: { //знак `:` говорит что это параметр, и с ним сравненние не требуется, проверяется только его наличие в данной позиции url, еще есть знак `*` в конце слова например если у нас category1, category2, и т.д то ставим звездочку в конце category*.
first: ["categories", 'carts', "menu", "home_page"],
routComponent: {
router_carts: "carts",
},
templatePath: "/static/templates/index.html"
},
["/cart/:idCart"]: {
first: ["categories", 'cart_single'],
routComponent: {
router_carts: "cart_single",
},
templatePath: "/static/templates/index.html"
},
}
Итак мы создали три маршрута для роутера, здесь router_carts это div элемент в котором будут отображаться «carts» на первых двух адресах, а на третьем «cart_single», в html (в файлах '/views/index.twig' и '/views/cart.twig') он указан как data-router_carts=«router».
Далее добавим в описание приложения cart_single вместе со следующими свойствами:
* «variant_tmpl» — свойство с типом «render-variant» для отображения дополнительного варианта шаблона (cart_variant_option.twig или cart_variant_radio.twig в зависимости от категории) в html разметке — data-cart_single-variant_tmpl-«render-variant»,
* 'title' — Название карточки товара 'data-cart_single-title=«text»',
* «title_category» название категории,
* «manufacture» — призводитель,
* «cost» — стоимость,
* «description» — описание,
* «cost_btn» стоимость в кнопке,
* «src_img» картинка data-cart_single-src_img=«srс»,
* «data» — данные содержащие id категории для формирования url — data-cart_single-data="{{ cart.category }}",
* «click_category» — событие клика по категории в карточке товара,
* «listner_click_on_cart» слушатель пользовательского события — «emiter-click-on-cart»,
cart_single: {
container: "cart_single",
//пока что закомментируем "variant_tmpl" т.к. мы еще не создали компоненты с вариантами шаблонов для карточки
props: [/*"variant_tmpl",*/'title', "title_category", "manufacture", "cost", "description", "cost_btn", "src_img", ["listner_click_on_cart", "emiter-click-on-cart", ""], "click_category", "data"],
methods: {
listner_click_on_cart: function(){
var index = this.emiter.getEventProp();
//получаем индекс контейнера карточки по которой кликнули в компоненте carts
var cart = this.rootLink.stateProperties.carts[index];
//выбираем из загруженных раннее в массиве карточек нужную нам по индексу контейнера
var props = {
title: cart.title,
title_category: cart.titleCategory,
cost: cart.cost,
src_img: '/static/upload/'+cart.image,
data: cart.category,
description: cart.description,
cost_btn: cart.cost,
manufacture: cart.manufacture,
// variant_tmpl: cart.variant_tmpl, пока что закоментируем установку вариантов шаблона, т.к. мы еще не создали для них компоненты.
}
this.parent.setAllProps(props);
//устанавливаем новые значения сразу для всех свойств, с помощью метода setAllProps(props);
},
click_category: function(){
event.preventDefault();
//данный метод создадим чуть позже, пока что просто выводим id категории в консоль
console.log(this.parent.props.data)
}
},
},
Далее добавим в метод click контейнера 'cart' компонента carts дополнительный код
click: function(){
event.preventDefault();
var url = this.parent.props.data.getProp(); //в свойстве data контейнера cart у нас url крточки товара.
//добавили вызов события "emiter-click-on-cart" которое мы слушаем в компоненте single_cart и обновляем данные всех свойств
//передаем в него индекс контейнера по которому кликнули
this.rootLink.eventProps["emiter-click-on-cart"].setEventProp(this.parent.index);
//вызываем метод setRout передав в него новый url,
// чтобы роутер поменял компонент carts на cart_single в div элементе data-router_carts="router" и обновил историю в броузере
this.rootLink.router.setRout(url);
}
Далее добавляем новое событие «emiter-click-on-cart» в объект eventEmiters
eventEmiters: {
["emiter-click-on-cart"]: {
prop: "",
},
А также в конец метода click контейнера catgory компонента categories:
click: function() {
//метод setRout принимает url и сравнивает его с картой которую мы создали в объекте routes
//и на соответствующем адресе меняет компонент в div теге `data-router_carts="router"` отображая carts или single_cart
/* ............ конец метода ............*/
this.rootLink.router.setRout(historyUrl);
}
И не забываем заменить способ загрузки приложения, теперь мы передаем наши роуты routes и описание приложения State в функцию HTMLixRouter().
window.onload = function(){
///создаем экземпляр HTMLix
var HM = HTMLixRouter(State, routes);
var url = window.location.pathname;
if(window.location.pathname == "/"){
url = url+"json";
}else{
url = url + "/json";
}
//отправляем запрос чтобы загрузить массив со всеми карточками товара при первой загрузке приложения
HM.stateMethods.fetchCategoryCarts(url, function(arr){ HM.stateProperties.carts = arr; });
console.log(HM);
}
Ну вот теперь наше приложение работает для трех адресов url, за исключением смены варианта шаблона для single_cart и клика по категории из single_cart. Давайте «покликаем» посмотрим что все в порядке.
Теперь создадим два новых компонента в описании приложения — для смены варианта шаблона в компоненте single cart:
cart_variant_option: {
container: "cart_variant_option", // views/twig_templates/cart_variant_option.twig => data-cart_variant_option="container"
props: ["click", "select"], //data-cart_variant_option-select="select" , data-cart_variant_option-click="click"
methods: {
click: function(){
console.log(this.parent.props.select.getProp());
}
}
},
cart_variant_radio: {
selector: "div:last-of-type",
container: "cart_variant_radio_cont",
props: ["click", "radio"],
methods: {
click: function(){
console.log(this.parent.index+" --- "+this.parent.props.radio.getProp());
}
}
},
После чего раскомментируем свойство «variant_tmpl» компонента — контейнера cart_single
cart_single: {
container: "cart_single",
props: ["variant_tmpl" /*...........................*/],
И в методе listner_click_on_cart:
var props = {
title: cart.title,
title_category: cart.titleCategory,
cost: cart.cost,
src_img: '/static/upload/'+cart.image,
data: cart.category,
description: cart.description,
cost_btn: cart.cost,
manufacture: cart.manufacture,
variant_tmpl: cart.variant_tmpl, //раскомментировали
}
Теперь при клике на карточку товара, в разных категориях товара у нас будет разный шаблон, а в свойстве variant_tmpl один из двух компонентов cart_variant_radio — ноутбуки и телевизоры, cart_variant_option — фотоаппараты.
Далее давайте добавим код в метод click_category компонента — контейнера cart_single чтобы при клике по категории из нутри карточки товара у нас также менялась активная категория и изменялся отображаемый в роутере компонент, просто скопируем код из метода click контейнера category — компонента categories.
click_category: function(){
event.preventDefault();
this.rootLink.eventProps["emiter-click-on-category"].setEventProp(this.parent.props.data.getProp());
var historyUrl = "/category/"+this.parent.props.data.getProp();
var url = historyUrl+"/json";
this.rootLink.stateMethods.load_carts(url, this);
this.rootLink.router.setRout(historyUrl);
},
Итак метод работает но плохо то что мы продублировали код, давайте перенесем весь код в метод click_on_category в объект с общими методами для всего приложения stateMethods, и предадим в него context параметром, а вторым url который хотим сменить:
click_on_category: function(context, event){
event.preventDefault();
var historyUrl = "/category/"+context.parent.props.data.getProp();
var url = historyUrl+"/json";
context.rootLink.eventProps["emiter-click-on-category"].setEventProp(context.parent.props.data.getProp());
context.rootLink.router.setRout(historyUrl);
console.log(url);
context.rootLink.stateMethods.load_carts(url, context);
},
А в методе click_category компонента — контейнера cart_single и методе click контейнера category компонента categories вызовем общий метод:
/* */ : function(){
this.rootLink.stateMethods.click_on_category(this, event);
}
Итак мы создали первую часть прототипа интернет магазина для трех адресов url, полный скрипт приложения до этой точки приведен ниже, на этом пока что все.
var State = {
categories: {
container: "category",
props: [ "data", "click", 'class', "title", ['listner_click_on_category', "emiter-click-on-category", ""]],
methods: {
click: function(){
this.rootLink.stateMethods.click_on_category(this, event);
},
listner_click_on_category: function(){
this.parent.props.class.removeProp("active");
if(this.parent.props.data.getProp() == this.emiter.prop){
this.parent.props.class.setProp("active");
}
},
}
},
carts: {
arrayProps: [ ['listener_load_carts', "emiter-load-carts", ""] ],
arrayMethods: {
listener_load_carts: function(){
this.parent.removeAll();
for(var i=0; i<this.emiter.prop.length; i++){
//в цикле перебераем полученый массив с карточками товара
var prop = this.emiter.prop[i];
var props = {
title: prop.title,
title_category: prop.titleCategory,
cost: prop.cost,
src_img: '/static/upload/'+prop.image,
data: "/cart/"+prop._id
}
this.parent.add(props); //добавляем новый контейнер передав в него начальные данные для свойств
}
}
},
container: "cart",
//добавили все свойства для контейнера cart
props: ['title', "title_category", "cost", "click", "src_img", "data" ],
methods: {
click: function(){ //метод для обработки кликов по карточке товара,
event.preventDefault();
var url = this.parent.props.data.getProp();
this.rootLink.router.setRout(url);
this.rootLink.eventProps["emiter-click-on-cart"].setEventProp(this.parent.index);
}
}
},
cart_single: {
container: "cart_single",
props: ["variant_tmpl",'title', "title_category", "manufacture", "cost", "description", "cost_btn", "src_img", ["listner_click_on_cart", "emiter-click-on-cart", ""], "click_category", "data"],
methods: {
listner_click_on_cart: function(){
var index = this.emiter.getEventProp();
//получаем индекс контейнера карточки по которой кликнули в компоненте carts
var cart = this.rootLink.stateProperties.carts[index];
//выбираем из загруженных раннее в массиве карточек нужную нам по индексу контейнера
var props = {
title: cart.title,
title_category: cart.titleCategory,
cost: cart.cost,
src_img: '/static/upload/'+cart.image,
data: cart.category,
description: cart.description,
cost_btn: cart.cost,
manufacture: cart.manufacture,
variant_tmpl: cart.variant_tmpl,
}
this.parent.setAllProps(props);
//устанавливаем новые значения сразу для всех свойств, с помощью метода setAllProps(props);
},
click_category: function(){
this.rootLink.stateMethods.click_on_category(this, event);
},
},
},
cart_variant_option: {
container: "cart_variant_option",
props: ["click", "select"],
methods: {
click: function(){
console.log(this.parent.props.select.getProp());
}
}
},
cart_variant_radio: {
selector: "div:last-of-type",
container: "cart_variant_radio_cont",
props: ["click", "radio"],
methods: {
click: function(){
console.log(this.parent.index+" --- "+this.parent.props.radio.getProp());
}
}
},
stateProperties:{ //объект для хранения общих переменных приложения
carts: [],
},
stateMethods: {
click_on_category: function(context, event){ //общий метод для кликов по категории для компонентов categories и cart_single
event.preventDefault();
var historyUrl = "/category/"+context.parent.props.data.getProp();
var url = historyUrl+"/json";
context.rootLink.eventProps["emiter-click-on-category"].setEventProp(context.parent.props.data.getProp());
context.rootLink.router.setRout(historyUrl);
console.log(url);
context.rootLink.stateMethods.load_carts(url, context);
},
fetchCategoryCarts: function(url, callb){
// общий метод для загрузки данных с какого либо адреса get запросм,
//принимает в параметрах адрес - url и функцию обратного вызова callb в которую он передаст полученые данные
fetch(url).then((response) => {
if(response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
}).then((json) => {
callb(json);
}).catch((error) => {
console.log(error);
});
},
load_carts: function(url, context ){
//метод вызывает fetchCategoryCarts передавая в него функцию обратного вызова
// в которой подставляем данные с сервера в переменную .carts
//а также вызывает событие "emiter-load-carts" и передает в него массив с карточками
context.rootLink.stateMethods.fetchCategoryCarts(url, function(data){
context.rootLink.stateProperties.carts = data;
context.rootLink.eventProps["emiter-load-carts"].setEventProp(data);
});
},
},
eventEmiters: {//создали объект со всеми пользовательскими событиями
["emiter-click-on-cart"]: {
prop: "",
},
["emiter-click-on-category"] : {
prop: [],
},
["emiter-load-carts"]: {
prop: "",
}
}
}
var routes = {
["/"]: {
first: ["categories", 'carts'], /// компоненты которые есть в html файле указываются в этом массиве, остальные будут загружены с шаблона, в fetch запросе асинхронно
routComponent: {
router_carts: "carts", //компонент соответствующий данному роуту
},
templatePath: "/static/templates/index.html" // папка для загрузки шаблонов
},
["/category/:idCategory"]: { //знак `:` говорит что это параметр, и с ним сравненние не требуется, проверяется только его наличие, еще есть знак `*` в конце слова например если у нас category1, category2, и т.д то ставим звездочку в конце category*.
first: ["categories", 'carts', "menu", "home_page"],
routComponent: {
router_carts: "carts",
},
templatePath: "/static/templates/index.html"
},
["/cart/:idCart"]: {
first: ["categories", 'cart_single'],
routComponent: {
router_carts: "cart_single",
},
templatePath: "/static/templates/index.html"
},
}
window.onload = function(){
///создаем экземпляр HTMLix
var HM = HTMLixRouter(State, routes);
var url = window.location.pathname;
if(window.location.pathname == "/"){
url = url+"json";
}else{
url = url + "/json";
}
HM.stateMethods.fetchCategoryCarts(url, function(arr){ HM.stateProperties.carts = arr; }); //загружаем массив с карточками после инициализации приложения, для дальнейщего использования.
console.log(HM);
}
Автор: sergey_ovechkin