В данной статье будут рассмотрены базовые принципы работы с пользовательскими событиями в Htmlix и создано небольшое приложение из четырех компонентов: формы ввода имени пользователя, приветствия, кнопки выхода из приложения, и массива с пользователями приложения.
Уже готовый пример можно покликать здесь.
Почитать api htmlix можно здесь
После ввода имени пользователя в форму, приложение отобразит его имя в приветствии, кнопке выхода и создаст новый контейнер в массиве с пользователями, а также сохранит данные в localStorage. После перезагрузки страницы, возьмет имя из localStorage. При клике по кнопке «выйти из профиля» удалит данные со всех компонентов и переменную из localStorage.
Давайте создадим четыре компонента: форму входа — form, приветствие — greeting, кнопку выхода — logout и массив со всеми посетителями — users_array, и посмотрим как бы мы создавали между ними коммуникацию если бы не пользовались пользовательскими событиями.
Создадим html разметку всех компонентов:
Теперь перенесем их в javascript:
В примере выше мы записали данные свойства «input» из компонента формы в переменную «text». Теперь нам нужно отобразить их в свойствах компонентов greeting и logout, а также создать новый контейнер в массиве users_array. Для понимания того зачем нужны пользовательские события давайте сначала попробуем сделать это различными способами без их использования.
1. способ — в самом методе click формы напрямую перейти к каждому компоненту и установить свойство text:
Недостаток данного подхода очевиден, что если потом мы где-нибудь еще будем получать данные для этого свойства? Например на основе localstorage они будут загружаться автоматически.
2. способ это создать метод в объекте stateMethods и вызывать его при изменении данных:
В данном случае нам удалось избежать дублирования кода, однако проблема в том что методы для работы со свойствами компонента находятся не внутри него а во внешних функциях. Что если перед тем как установить свойство его нужно как-то отформатировать, по разному для каждого компонента, или нам нужно будет установить не одно свойство, а несколько, поэтому лучше когда компоненты сами работают со своими свойствами.
3. способ — перенести всю логику работы со свойствами в компоненты, и создать в них методы, с помощью которых вызывать определенные действия.
Теперь компоненты инкапсулированы, однако мы производим доступ к компоненту по его имени. Что если мы потом захотим изменить имя компонента или метода, или вообще удалить компонент, или у нас будет много таких компонентов которые слушают одно и тоже свойство. Или у нас будет много таких свойств, соответственно прийдется создавать много таких методов.
Чтобы облегчить данную задачу можно воспользоваться пользовательскими событиями. Компонент подписывается на событие, и при его наступлении делает что-то со своими свойствами.
Давайте отредактируем код, теперь с использованием пользовательских событий:
Таким образом мы избавились от промежуточного метода «entryUser» и просто вызываем событие передав в него новые данные в форме и после загрузки страницы. Теперь если нам нужно изменить что-либо в компоненте, или вообще удалить его нам не потребуется поправлять код в каждой функции, которых может быть сколько угодно. А чтобы отписаться от какого либо события можно просто удалить слушателя в компоненте, или временно отключить его с помощью метода disableEvent() в свойстве — подписчике.
Полный код данного примера вместе со вторым событием «emiter-exit-user» можно посмотреть здесь.
Уже готовый пример можно покликать здесь.
Почитать api htmlix можно здесь
После ввода имени пользователя в форму, приложение отобразит его имя в приветствии, кнопке выхода и создаст новый контейнер в массиве с пользователями, а также сохранит данные в localStorage. После перезагрузки страницы, возьмет имя из localStorage. При клике по кнопке «выйти из профиля» удалит данные со всех компонентов и переменную из localStorage.
Давайте создадим четыре компонента: форму входа — form, приветствие — greeting, кнопку выхода — logout и массив со всеми посетителями — users_array, и посмотрим как бы мы создавали между ними коммуникацию если бы не пользовались пользовательскими событиями.
Создадим html разметку всех компонентов:
<!-- компонент - контейнер форма с двумя свойствами input c типом свойства "inputvalue" и click с типом "click" -->
<form data-form="container" class="card col-12">
<div class="form-group">
<label for="">Введите имя</label>
<textarea data-form-input="inputvalue" class="form-control" rows="1"></textarea>
</div>
<button data-form-click="click" type="submit">Submit</button>
</form>
<!-- компонент - контейнер приветствие и свойство user_name с типом - "text" -->
<div data-greeting="container" class="col-6 card">
<p>Привет: <span data-greeting-user_name="text">guest</span></p>
</div>
<!-- компонент - контейнер кнопка выхода и свойство user_name с типом - "text" -->
<div data-logout="container" class="col-6 card">
<a href="#"> Выйти из профиля: ( <span data-logout-user_name="text"></span> )
</a>
</div>
<!-- компонент - массив пользователей, изначально с двумя контейнерами, в каждом контейнере свойство user_name - "text" -->
<div class="container-fluid" style="border: 1px solid red; margin-top: 20px;">
<p> все пользователи:</p>
<div data-users_array="array" class="row">
<div data-user="container" class="col-4 card">
<p>пользователь -
<span data-user-user_name="text">user_name_1</span>
</p>
</div>
<div data-user="container" class="col-4 card">
<p>пользователь -
<span data-user-user_name="text">user_name_1</span>
</p>
</div>
</div>
</div>
Теперь перенесем их в javascript:
var StateMap = {
form: {//форма входа
container: "form",
props: ["input", "click"],
methods: {
click: function(){
event.preventDefault(); //отменяем перезагрузку страницы
//получаем данные свойства input
var text = this.parent.props.input.getProp();
console.log(text);
}
},
},
greeting: {//приветствие
container: "greeting",
props: [ "user_name", ],
methods: {
}
},
logout: { //кнопка выхода
container: "logout",
props: [ "user_name", ],
methods: {
},
},
users_array: { //массив с пользователями
container: "user",
props: [ "user_name", ],
methods: {
},
},
}
window.onload = function(){//создаем экземпляр приложения htmlix
var HM = new HTMLixState(StateMap);
console.log(HM);
}
В примере выше мы записали данные свойства «input» из компонента формы в переменную «text». Теперь нам нужно отобразить их в свойствах компонентов greeting и logout, а также создать новый контейнер в массиве users_array. Для понимания того зачем нужны пользовательские события давайте сначала попробуем сделать это различными способами без их использования.
1. способ — в самом методе click формы напрямую перейти к каждому компоненту и установить свойство text:
event.preventDefault();
var text = this.parent.props.input.getProp();
//установили значения свойствам
this.rootLink.state["greeting"].props.user_name.setProp(text);
this.rootLink.state["logout"].props.user_name.setProp(text);
///создали новый контейнер
this.rootLink.state["users_array"].add({user_name: text});
Недостаток данного подхода очевиден, что если потом мы где-нибудь еще будем получать данные для этого свойства? Например на основе localstorage они будут загружаться автоматически.
2. способ это создать метод в объекте stateMethods и вызывать его при изменении данных:
ststeMethods: {
entryUser: function(text){
this.state["greeting"].props.user_name.setProp(text);
this.state["logout"].props.user_name.setProp(text);
this.state["users_array"].add({user_name: text});
}
}
// после загрузки страници
window.onload = function(){
var name = window.localStorage.getItem('user_name');
if(name)HM.stateMethods.entryUser.call.(HM, name); ///вызываем метод передав ему контекст= HM
}
//в форме
click: function(){
event.preventDefault();
var text = this.parent.props.input.getProp();
this.rootLink.stateMethods.entryUser.call.(this.rootLink, text);
}
В данном случае нам удалось избежать дублирования кода, однако проблема в том что методы для работы со свойствами компонента находятся не внутри него а во внешних функциях. Что если перед тем как установить свойство его нужно как-то отформатировать, по разному для каждого компонента, или нам нужно будет установить не одно свойство, а несколько, поэтому лучше когда компоненты сами работают со своими свойствами.
3. способ — перенести всю логику работы со свойствами в компоненты, и создать в них методы, с помощью которых вызывать определенные действия.
greeting: {
container: "greeting",
//добавили вспомогательный метод для приветствия пользователя
props: [ "user_name", ["greet_user", "aux"] ],
methods: {
greet_user: function(name){
this.props.user_name.setProp(name+" !!!");
}
}
},
logout: {
container: "logout",
props: [ "user_name", ["set_name", "aux"]], //добавили вспомогательный метод для работы со свойством компонента
methods: {
set_name: function(name){
this.props.user_name.setProp(name);
}
},
},
users_array: {
arrayProps: [["entry_user", "aux"]], ///добавляем контейнер с новым пользователем из массива
arrayMethods: {
entry_user: function(name){
this.add({user_name: name})
}
}
container: "user",
props: [ "user_name", ],
methods: {
},
},
stateMethods: { //изменили общий метод
entryUser: function(text){
this.state["greeting"].methods.greet_user(text);
this.state["logout"].methods.set_name(text);
this.state["users_array"].methods.entry_user(text);
}
}
//далее также вызываем данный метод entryUser из формы и при загрузке страницы
Теперь компоненты инкапсулированы, однако мы производим доступ к компоненту по его имени. Что если мы потом захотим изменить имя компонента или метода, или вообще удалить компонент, или у нас будет много таких компонентов которые слушают одно и тоже свойство. Или у нас будет много таких свойств, соответственно прийдется создавать много таких методов.
Чтобы облегчить данную задачу можно воспользоваться пользовательскими событиями. Компонент подписывается на событие, и при его наступлении делает что-то со своими свойствами.
Давайте отредактируем код, теперь с использованием пользовательских событий:
var StateMap = {
eventEmiters: {
//создали эмитер события - входа пользователя
["emiter-entry-user"]: {prop: ""},
},
form: {
container: "form",
props: ["input", "click"],
methods: {
click: function(){
event.preventDefault();
var text = this.parent.props.input.getProp();
//вызвали событие в форме и передали в него данные
this.rootLink.eventProps["emiter-entry-user"].setEventProp(text);
window.localStorage.setItem('user_name', text);
}
},
},
greeting: {
container: "greeting",
props: [ "user_name", ['listen_entry_user', "emiter-entry-user", "" ] ], //добавили слушатель события "emiter-entry-user"
methods: {
listen_entry_user: function(){
//получили данные из события и обновили свойство
this.parent.props.user_name.setProp( this.emiter.getEventProp() );
},
},
},
logout: {
container: "logout",
props: [ "user_name", ["listen_entry_user", "emiter-entry-user", "" ]], //свойство слушатель события "emiter-entry-user"
methods: {
listen_entry_user: function(){
//получили данные из события и обновили свойство
this.parent.props.user_name.setProp( this.emiter.getEventProp() );
},
},
},
//здесь слушатель события добавляется в свойство массива, т.к. если добавить его в контейнер оно будет вызвано для каждого контейнера
users_array: {
arrayProps: [ ['listen_entry_user', "emiter-entry-user", ""] ], //свойство слушатель события "emiter-entry-user"
arrayMethods: {
listen_entry_user: function(){
//получили данные из события и создали новый контейнер
this.parent.add( {user_name: this.emiter.getEventProp()} );
},
},
container: "user",
props: [ "user_name", ],
methods: {
}
},
}
window.onload = function(){
var HM = new HTMLixState(StateMap);
var name = window.localStorage.getItem('user_name');
if(name != null)HM.eventProps["emiter-entry-user"].setEventProp(name); ///вызвали событие "emiter-entry-user" при загрузке сайта и передали в него данные
}
Таким образом мы избавились от промежуточного метода «entryUser» и просто вызываем событие передав в него новые данные в форме и после загрузки страницы. Теперь если нам нужно изменить что-либо в компоненте, или вообще удалить его нам не потребуется поправлять код в каждой функции, которых может быть сколько угодно. А чтобы отписаться от какого либо события можно просто удалить слушателя в компоненте, или временно отключить его с помощью метода disableEvent() в свойстве — подписчике.
Полный код данного примера вместе со вторым событием «emiter-exit-user» можно посмотреть здесь.