В этом разделе будет описано пошаговое создание простого прототипа фронтенда SPA интернет магазина. Серверная часть которого написана на node.js с использованием express, шаблонизатора - twig , базы данных - NeDB , для обработки данных форм - модуль formidable , клиентская часть написана на 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
Итак покликав по различным пунктам меню мы видим работающий сервер, без фронтенд части т.к. мы только что ее удалили. Теперь заново поэтапно ее создадим, но для начала разберемся как работает сервер.
Перейдя по адресу "/"
нам отдается верхнее меню с тремя пунктами, основная страница с шестью карточками товара, и список категорий с левой стороны.
Чтобы отдать нам это все сервер посылает два запроса к базе данных, в одном он находит все категории, во втором первые шесть карточек товара из таблицы, затем присоединяет к каждой карточке товара два поля из совпадающей по 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' %}
Аналогично для адреса http://localhost:3000/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.
var State = {
categories: { //компонент категории
container: "category", //контейнер компонента категории
props: [ ],
methods: {
}
},
carts: { //компонент карточки товара
container: "cart", //контейнер компонента карточки товара
props: [ ],
methods: {
}
}
}
Итак мы создали два компонента давайте добавим несколько свойств для компонента categories:
"data"
- данные которые содержат idCategory ( {{ category.idCategory }} ), "click"
- обработчик кликов по категории,'class'
- доступ к классу категории для изменения цвета текущей категории,"title"
- доступ к тексту внутри категории ( {{ category.titleCategory }} ), В 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);
},
}
},
В коде выше мы добавили четыре свойства для каждого контейнера категории, и для свойства "click" метод который пока что ничего не делает, только отменяет переход по ссылке и перезагрузку страницы. Давайте разберемся что нам нужно сделать при клике по категории:
Для первого пункта создадим метод load_carts
в общих для всего приложения методах stateMethods
и поместим загруженные карточки в переменную carts
объекта stateProperties
- общих для всего приложения переменных;
Для второго пункта создадим пользовательское событие "emiter-click-on-category" и будем слушать его во всех контейнерах category чтобы добавить или удалить класс при его наступлении, вызывать событие будем в методе click контейнера category.
Изменим описание приложения:
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);
},
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){
//метод вызывает fetchCategoryCarts передавая в него функцию обратного вызова
// в которой подставляем данные с сервера в переменную carts объекта stateProperties
//в дальнейшем также будем вызывать событие "emiter-load-carts" которое мы еще не создали
//this в методах из объекта stateMethods указывает на rootLink
var context = this;
this.stateMethods.fetchCategoryCarts(url, function(data){
context.stateProperties.carts = data;
console.log(data)
//context.eventProps["emiter-load-carts"].setEventProp(data);
});
},
},
eventEmiters: {//создали объект со всеми пользовательскими событиями
["emiter-click-on-category"] : { //добавили событие "emiter-click-on-category"
prop: [],
},
}
}
window.onload = function(){
///создаем экземпляр HTMLix
var HM = new HTMLixState(State);
console.log(HM);
}
Теперь при клике по категории у нас меняется класс текущей категории с помощью события "emiter-click-on-category", а также загружаются данные с сервера с помощью метода load_carts
объекта stateMethods, которые мы сохраняем в свойстве carts объекта stateProperties и выводим пока что в консоль.
При клике по другому пункту меню, либо по карточке товара, появляется ошибка, что не может найти какое-либо свойство, пока что не обращаем на них внимание, это связано стем что мы не создали роутер и htmlix не знает где искать для них шаблоны.
Далее давайте добавим свойства контейнера 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
:
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;
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 ){
var context = this;
this.stateMethods.fetchCategoryCarts(url, function(data){
context.stateProperties.carts = data;
context.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"]: { //знак `:` говорит что это параметр, и с ним сравнение не требуется, проверяется только его наличие.
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"
- событие клика по категории в карточке товара,"listner_click_on_cart"
слушатель пользовательского события - "emiter-click-on-cart", 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", "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: 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
//устанавливает компонент carts в div теге `data-router_carts="router"`
/* ............ конец метода ............*/
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/ 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
компонента - контейнера cart_single
чтобы при клике по категории изнутри карточки товара у нас также менялась активная категория и изменялся отображаемый в роутере компонент, однако чтобы не дублировать код, так как он будет такой-же как и в компоненте category
мы унаследуем из него данный метод, а старый удалим.
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", ""], "data", //"click",
["click", "extend", "category", "props"]///наследуем свойство из компонента "category"
],
/* закомментировали метод
click: function(){
event.preventDefault();
console.log(this.parent.props.data)
} */
Итак мы создали первую часть прототипа интернет магазина для трех адресов url, полный скрипт приложения до этой точки приведен ниже, на этом пока что все.
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);
//меняем компонент в div теге `data-router_carts="router"` - на carts
this.rootLink.router.setRout(historyUrl);
},
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: {
//добавили свойство 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;
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(){ //метод для обработки кликов по карточке товара,
event.preventDefault();
var url = this.parent.props.data.getProp(); //в свойстве data контейнера cart у нас url крточки товара.
console.log(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);
}
}
},
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", ""], "data", //"click",
["click", "extend", "category", "props"]///наследуем свойство из компонента "category"
],
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: function(){
event.preventDefault();
console.log(this.parent.props.data)
} */
},
},
cart_variant_option: {
container: "cart_variant_option", // views/twig_templates/ 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());
}
}
},
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){
//метод вызывает fetchCategoryCarts передавая в него функцию обратного вызова
// в которой подставляем данные с сервера в переменную carts объекта stateProperties
//this в методах из объекта stateMethods указывает на rootLink
var context = this;
this.stateMethods.fetchCategoryCarts(url, function(data){
context.stateProperties.carts = data;
context.eventProps["emiter-load-carts"].setEventProp(data);
});
},
},
eventEmiters: {//создали объект со всеми пользовательскими событиями
["emiter-click-on-category"] : { //добавили событие "emiter-click-on-category"
prop: [],
},
["emiter-load-carts"]: {
prop: "",
},
["emiter-click-on-cart"]: {
prop: "",
},
}
}
var routes = {
["/"]: {
first: ["categories", 'carts'], /// компоненты которые есть в html файле указываются в этом массиве, остальные будут загружены с шаблона, в fetch запросе асинхронно
routComponent: {
router_carts: "carts", //компонент соответствующий данному роуту
},
templatePath: "/static/templates/index.html" // папка для загрузки шаблонов
},
["/category/:idCategory"]: { //знак `:` говорит что это параметр, и с ним сравненние не требуется, проверяется только его наличие
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);
}