Skip to content

Instantly share code, notes, and snippets.

@kazminigor1988
Last active May 13, 2016 21:09
Show Gist options
  • Save kazminigor1988/4fe3942012bcd0539d939a421e5c3736 to your computer and use it in GitHub Desktop.
Save kazminigor1988/4fe3942012bcd0539d939a421e5c3736 to your computer and use it in GitHub Desktop.

Сразу видел 4 варианта решения.

  1. В одном "классе" реализизовать всю логику. С одной стороны все под рукой, с другой нету разделения обязанностей и тяжелее вносить изменения при расширении или рефакторинге.
  2. MVC - где M & V - тупые, все решает C. Выбрал его, так как, наверно ближе с момента работы на бэке и так же из за того что до сих пор работаю с Marionette.
  3. MVVM - сегодня для себя уточнил особенности данного патерна, но пробовать делать наброски думаю пока рано.
  4. 2-й вариинт, но с большим количеством классов и наследованием, можно было бы сделать базовый класс, с созданием ноды, подвязыванием и отвязыванием ивентов извне. Наследуюясь от него создать отдельно классы input, autocomliteList, label, loader (spinner) у которых уже была бы реализация как своей доп логики так и доработка существующих методов, как создание ноды. Отдельно сделать фабрику, которая в зависимости от входящих данных создавала нужный метод onAutocomliteChange. Не все успел реализовать:
  • задержку при вводе символов, до вызова функции onAutocompliteChange,
  • вывод автокомплита с возможностями управления кнопками клавиатуры,
  • отображение и скрытие спинера.

Минусы:

  • очень умный контроллер,
  • предполагаю что мог не правильно понять назначение onAutocompliteChange, и не правильно реализовать логику.
  • лучше бы было по одному принципу реализовывать onAutocompliteChange, использую я обоих случая Promise.
  • в случае огромных объемов, при которых поиск по options начинал бы тормозить, было бы хорошо реализовать некий воркер для поиска, что бы не блокировать работу всего потока.
  • нету проверок, на случай если будут не коректные данные, что может привести к неожиданным результатам и поломкам,
  • нету гибкой возможности конфигурировать html елементы.
const options = {
};
(function(parentNode, options) {
function Model(options, value) {
this.options = options;
this.value = value;
}
Model.prototype.setOptions = function setOptions(options) {
this.options = options;
}
Model.prototype.setValue = function setValue(value) {
this.value = value;
}
Model.prototype.getValue = function getValue(value) {
return this.value;
}
function View() {
this.createNode();
}
View.prototype.createNode = function createNode() {
const baseNode = document.createElement('div');
baseNode.className = 'autocomplite';
const label = document.createElement('label');
label.textContent = 'Autocomplite: ';
this.input = document.createElement('input');
const loader = document.createElement('div');
loader.className = 'loader hide';
baseNode.appendChild(label);
baseNode.appendChild(this.input);
baseNode.appendChild(loader);
this.node = baseNode;
};
View.prototype.bindEventListenersForInput = function bindEventListenersForInput(eventMap) {
eventMap.forEach((event) => {
this.input.addEventListener(event.type, event.handler);
});
};
View.prototype.unbindEventListenersForInput = function unbindEventListenersForInput(eventMap) {
eventMap.forEach((event) => {
this.input.removeEventListener(event.type, event.handler);
});
};
View.prototype.getNode = function getNode() {
return this.node;
};
View.prototype.showAutocomplite = function showAutocomplite(options) {
// create ul and append to div element
};
View.prototype.hideAutocomplite = function hideAutocomplite(options) {
// remove ui element
};
function AutocompliteController(parentNode, options) {
this.parentNode = parentNode;
this.options = options;
this.initModel();
this.initView();
};
AutocompliteController.prototype.destroy = function destroy() {
delete this.parentNode;
delete this.options;
this.destroyView();
this.destroyModel();
};
AutocompliteController.prototype.destroyView = function destroyView(options) {
this.parentNode.removeChild(this.view.getNode());
this.view.unbindEventListenersForInput(this.getInputEventsMap());
delete this.view;
};
AutocompliteController.prototype.destroyModel = function destroyModel(options) {
delete this.model;
};
AutocompliteController.prototype.initModel = function initModel(options) {
this.model = new Model(this.options.options, this.options.value);
};
AutocompliteController.prototype.initView = function initView(options) {
this.view = new View();
this.view.bindEventListenersForInput(this.getInputEventsMap());
this.parentNode.appendChild(this.view.getNode());
};
AutocompliteController.prototype.showAutocomplite = function showAutocomplite(matches) {
this.view.showAutocomplite(matches);
this.view.bindAutocompliteEvent(this.getAutocompliteEvent());
};
AutocompliteController.prototype.hideAutocomplite = function showAutocomplite() {
this.view.unbindAutocompliteEvent(this.getAutocompliteEvent());
this.view.hideAutocomplite();
};
AutocompliteController.prototype.getAutocompliteEvent = function getAutocompliteEvent() {
return [
{type: 'click', (event) => {
this.model.setValue(event.currentTarget.value);
this.hideAutocomplite();
}}
];
};
AutocompliteController.prototype.onInputChange = function onInputChange(event) {
const matches = this.findMatches(event);
this.showAutocomplite(matches);
};
AutocompliteController.prototype.onAutocompliteChange = (function (onAutocompliteChange) {
if (onAutocompliteChange) {
return (event) => {
this.showSpinner();
onAutocompliteChange(event).then((options) => {
this.model.setOptions(options);
this.showAutocomplite(options);
this.hideSpinner();
}).catch((error) => {
this.hideSpinner();
if (typeof error === 'object') {
error = JSON.stringify(error);
}
throw new Error(`onAutocompliteChange end work with error: ${error}`);
});
}
} else {
return (event) => this.onInputChange(event);
}
})(options.onAutocompliteChange);
AutocompliteController.prototype.getInputEventsMap = function getInputEventsMap() {
return [
{type: 'input', handler: (...args) => this.onAutocompliteChange(...args)},
{type: 'blur', handler: () => {
this.autocompliteDestroy();
this.options.onChange();
}}
];
};
const autocomplite = new AutocompliteController(parentNode, options);
return {
setOptions: (options) autocomolite.model.setOptions(options),
getValue : () => autocomolite.model.getValue()
};
})(document.body, options);
(function(cssLink){
var cssElement = document.createElement("link");
cssElement.rel = "stylesheet";
cssElement.href = cssLink;
document.getElementsByTagName("head")[0].appendChild(cssElement);
})('./styles.css');
body {
background-color: #eeeeee;
}
.autocomplite {
display: inline-block;
}
.hide {
display: none;
}
.loader {
font-size: 10px;
position: absolute;
top: 10px;
left: 255px;
text-indent: -9999em;
width: 2em;
height: 2em;
border-radius: 50%;
background: #333;
background: -moz-linear-gradient(left, #333 10%, rgba(255, 255, 255, 0) 42%);
background: -webkit-linear-gradient(left, #333 10%, rgba(255, 255, 255, 0) 42%);
background: -o-linear-gradient(left, #333 10%, rgba(255, 255, 255, 0) 42%);
background: -ms-linear-gradient(left, #333 10%, rgba(255, 255, 255, 0) 42%);
background: linear-gradient(to right, #333 10%, rgba(255, 255, 255, 0) 42%);
-webkit-animation: load3 1.4s infinite linear;
animation: load3 1.4s infinite linear;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.loader:before {
width: 50%;
height: 50%;
background: #333;
border-radius: 100% 0 0 0;
position: absolute;
top: 0;
left: 0;
content: '';
}
.loader:after {
background: #eee;
width: 75%;
height: 75%;
border-radius: 50%;
content: '';
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@-webkit-keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment