;(function(window) { var StdLib = window.StdLib; var Http = StdLib.Http; var YANDEX_COUNTER_ID = window.YANDEX_COUNTER_ID; var forms = {}; var metric = { yandex: { sendEvent: function(target, params) { params = params || {}; if (!YANDEX_COUNTER_ID) { console.error('metric: missing id'); return; } if (!window.ym) { console.error('metric: missing ym(' + target + ')'); return; } try { window.ym(YANDEX_COUNTER_ID, 'reachGoal', target, params); } catch (e) { console.error('metric: error - ', e); } } } }; var DOM = { setID: function(node, id) { node.id = id; return node; }, node: function(parent) { return function(selector) { return parent.querySelector(selector); }; }, nodes: function(parent) { return function(selector) { return parent.querySelectorAll(selector); }; }, nodeByID: function(id) { return document.getElementById(id); }, addClass: function(node, className) { node.classList.add(className); }, removeClass: function(node, className) { node.classList.remove(className); }, attr: function(node, name, value) { return value !== null && value !== undefined ? node.setAttribute(name, value) : node.getAttribute(name); }, read: function(node) { return node.textContent; }, write: function(node, text) { node.innerHTML = text; }, append: function(parent, node) { parent.appendChild(node); }, remove: function(parent, node) { parent.removeChild(node); }, clone: function(node, deep) { deep = deep === undefined ? true : false; return node.cloneNode(deep); }, show: function(node) { return function() { node && (node.style.display = 'block'); }; }, hide: function(node) { return function() { node && (node.style.display = 'none'); }; } }; function each(arr, fn) { if (!arr) { return; } for (var i = 0; i < arr.length; i++) { fn(arr[i], i, arr); } } function generateID() { return '#tmp:' + Math.random(); } var Maybe = {}; Maybe.just = function(value) { return new Just(value); }; Maybe.nothing = function() { return new Nothing(); }; Maybe.from = function(value) { return value === null || value === undefined ? Maybe.nothing() : Maybe.just(value); }; function Just(value) { this._value = value; } Just.prototype.map = function(fn) { return Maybe.from(fn(this._value)); }; Just.prototype.filter = function(fn) { return Maybe.from(fn(this._value) ? this._value : null); }; Just.prototype.getOrElse = function() { return this._value; }; function Nothing() {} Nothing.prototype.map = function() { return this; }; Nothing.prototype.filter = function() { return this; }; Nothing.prototype.getOrElse = function(other) { return other; }; var burger = (function() { var menu = document.querySelector('#js-navigation-container-inner'); var container = document.querySelector('#js-navigation-container'); var burger = document.querySelector('#js-burger'); var isOpen = false; function close() { StdLib.element.delayer.delay(container, 300); StdLib.element.delayer.free(container); container.classList.remove('navigation-container--show'); menu.classList.remove('navigation-container__inner--show'); burger.classList.remove('burger--transformed'); isOpen = false; } function open() { StdLib.element.delayer.delay(menu, 300); StdLib.element.delayer.free(menu); container.classList.add('navigation-container--show'); menu.classList.add('navigation-container__inner--show'); burger.classList.add('burger--transformed'); isOpen = true; } return { init: function() { burger.addEventListener('click', function() { isOpen ? close() : open(); }); document.addEventListener('click', function(e) { if (!menu.contains(e.target) && e.target === container) { close(); } }); } }; })(); var modal = (function() { var openers = document.querySelectorAll('.js-modal-opener'); return { init: function() { Array.prototype.forEach.call(openers, function(opener) { opener.addEventListener('click', function() { var modal = document.querySelector('#' + opener.getAttribute('data-modal')); var source = opener.getAttribute('data-source'); var modalFacade = new StdLib.ModalFacade(modal); modalFacade.open(); forms.modal && forms.modal.setProp('source', source); }); }); } }; })(); modal.init(); burger.init(); function extend(parent, child) { child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; } function NativeElement(element) { this.element = element; this.classes = {}; } NativeElement.prototype.getElement = function() { return this.element; }; NativeElement.prototype.setClass = function(name, value) { this.classes[name] = value; }; NativeElement.prototype.addClass = function(name) { var value = this.classes[name]; value && this.element.classList.add(value); }; NativeElement.prototype.removeClass = function(name) { var value = this.classes[name]; value && this.element.classList.remove(value); }; NativeElement.prototype.on = function(event, handler) { this.element.addEventListener(event, handler); }; function FormWrapper(form) { this.form = form; var self = this; this.form.on('submit', function(e) { e.preventDefault(); self.submit(); }); } FormWrapper.prototype.submit = function() { this.form.validate(); if (this.form.isValid()) { this.form.submit(); } }; FormWrapper.prototype.reset = function() { this.form.reset(); }; FormWrapper.prototype.subscribe = function(event, fn) { this.form.subscribe(event, fn); }; FormWrapper.prototype.setProp = function(key, value) { this.form.setProp(key, value); }; function FormSuccessFacade(success, main) { this.success = success; this.main = main; this.success.setClass('show', 'form-success--show'); this.main.setClass('hide', 'form-main--hide'); } FormSuccessFacade.prototype.show = function() { this.success.addClass('show'); this.main.addClass('hide'); }; FormSuccessFacade.prototype.hide = function() { this.success.removeClass('show'); this.main.removeClass('hide'); }; function Submitter(element) { NativeElement.call(this, element); this.setClass('pending', 'btn-pending'); } extend(NativeElement, Submitter); Submitter.prototype.onPending = function() { this.addClass('pending'); }; Submitter.prototype.offPending = function() { this.removeClass('pending'); }; function Form(config) { NativeElement.call(this, config.element); this.state = { isValid: true, isPending: false }; this.props = {}; this.controls = config.controls; this.submitter = config.submitter; this.success = new FormSuccessFacade(config.success, config.main); this.subscribers = {}; } extend(NativeElement, Form); Form.prototype.subscribe = function(event, fn) { this.subscribers[event] = this.subscribers[event] || []; this.subscribers[event].push(fn); }; Form.prototype.emit = function(event) { let handlers = this.subscribers[event]; handlers = handlers || []; each(handlers, function(handler) { handler && handler(); }); }; Form.prototype.setProp = function(key, value) { this.props[key] = value; }; Form.prototype.getProp = function(key) { return this.props[key]; }; Form.prototype.validate = function() { this.state.isValid = true; var self = this; this.controls.forEach(function(control) { control.validate(); if (control.isValid()) { control.resetError(); control.hideError(); } else { control.setError(); control.showError(); self.state.isValid = false; } }); }; Form.prototype.isValid = function() { return this.state.isValid; }; Form.prototype.getData = function() { var data = {}; this.controls.forEach(function(control) { data[control.getName()] = control.getValue(); }); var metric = this.getMetric(); if (metric) { data.source_type = metric; } return data; }; Form.prototype.getMetric = function() { return this.element.getAttribute('data-metric'); }; Form.prototype.isPending = function() { return this.state.isPending; }; Form.prototype.onPending = function() { this.state.isPending = true; this.submitter.onPending(); }; Form.prototype.offPending = function() { this.state.isPending = false; this.submitter.offPending(); }; Form.prototype.submit = function() { if (this.isPending()) { return; } this.onPending(); var self = this; this.request(function(response) { var data = JSON.parse(response); if (data.status === 'ok') { self.success.show(); self.emit('success'); } }); }; Form.prototype.reset = function() { this.offPending(); this.success.hide(); this.controls.forEach(function(control) { control.reset(); }); this.state.isValid = true; }; Form.prototype.request = function() {}; function DynamicForm(config) { Form.call(this, config); this.controls.forEach(function(control) { control.on('input', function() { control.validate(); if (control.isValid()) { control.setSuccess(); control.dirty(); control.hideError(); } else { if (!control.isPristine()) { control.setError(); control.showError(); } } }); }); } extend(Form, DynamicForm); function FormControlError(element) { NativeElement.call(this, element); this.setClass('show', 'form-error--show'); } extend(NativeElement, FormControlError); FormControlError.prototype.show = function() { this.addClass('show'); }; FormControlError.prototype.hide = function() { this.removeClass('show'); }; function FormControl(element, error, name) { NativeElement.call(this, element); this.name = name; this.error = error; this.state = { isValid: true, isPristine: true }; this.setClass('error', 'form-control-error'); this.setClass('success', 'form-control-success'); } extend(NativeElement, FormControl); FormControl.prototype.validate = function() { this.state.isValid = true; }; FormControl.prototype.isValid = function() { return this.state.isValid; }; FormControl.prototype.setError = function() { this.removeClass('success'); this.addClass('error'); }; FormControl.prototype.resetError = function() { this.removeClass('error'); }; FormControl.prototype.setSuccess = function() { this.removeClass('error'); this.addClass('success'); }; FormControl.prototype.resetSuccess = function() { this.removeClass('success'); }; FormControl.prototype.isPristine = function() { return this.state.isPristine; }; FormControl.prototype.dirty = function() { this.state.isPristine = false; }; FormControl.prototype.getValue = function() { return this.getElement().value; }; FormControl.prototype.setValue = function(value) { this.getElement().value = value; }; FormControl.prototype.getName = function() { return this.name; }; FormControl.prototype.showError = function() { this.error.show(); }; FormControl.prototype.hideError = function() { this.error.hide(); }; FormControl.prototype.reset = function() { this.resetSuccess(); this.resetError(); this.state.isPristine = true; this.state.isValid = true; this.setValue(''); this.hideError(); }; function TextFormControl() { FormControl.apply(this, arguments); } extend(FormControl, TextFormControl); TextFormControl.prototype.validate = function() { this.state.isValid = this.getValue().trim().length > 0; }; function EmailFormControl() { FormControl.apply(this, arguments); this.reg = /^[0-9a-zA-Z\-_\.\+]+@.+\..+$/; } extend(FormControl, EmailFormControl); EmailFormControl.prototype.validate = function() { this.state.isValid = this.reg.test(this.getValue().trim()); }; function PhoneFormControl(element, name) { FormControl.apply(this, arguments); this.input = new IMask(element, { mask: '+{7} (000) 000-00-00' }); } PhoneFormControl.VALUE_LENGTH = 11; extend(FormControl, PhoneFormControl); PhoneFormControl.prototype.validate = function() { this.state.isValid = this.input.unmaskedValue.length === PhoneFormControl.VALUE_LENGTH; }; PhoneFormControl.prototype.setValue = function(value) { FormControl.prototype.setValue.call(this, value); this.input.updateValue(); this.input.unmaskedValue = value; }; PhoneFormControl.prototype.getValue = function() { return this.input.unmaskedValue; }; function LeadForm(config) { DynamicForm.call(this, config); } extend(DynamicForm, LeadForm); LeadForm.prototype.request = function(success) { var http = new Http(); var data = this.getData(); var metricName = this.getMetric(); this.emit('send'); http.post({ url: '/lead/', body: JSON.stringify(data), headers: {'Content-Type': 'application/json'}, success: function(response) { success && success(response); var data = JSON.parse(response); if (data.status === 'ok') { metricName && metric.yandex.sendEvent(metricName); } } }); }; function ModalLeadForm(config) { DynamicForm.call(this, config); } extend(DynamicForm, ModalLeadForm); ModalLeadForm.prototype.request = function(success) { var http = new Http(); var data = this.getData(); var source = this.getProp('source'); var metricName = this.getMetric(); http.post({ url: '/lead/', body: JSON.stringify(data), headers: {'Content-Type': 'application/json'}, success: function(response) { success && success(response); var data = JSON.parse(response); if (data.status === 'ok') { var params = source ? {source: source} : null; metricName && metric.yandex.sendEvent(metricName, params); } } }); }; function FeedbackForm(config) { DynamicForm.call(this, config); } extend(DynamicForm, FeedbackForm); FeedbackForm.prototype.request = function(success) { var http = new Http(); var data = this.getData(); http.post({ url: '/feedback/', body: JSON.stringify(data), headers: {'Content-Type': 'application/json'}, success: function(response) { success && success(response); } }); }; function FormsBuilder(selector) { this.forms = document.querySelectorAll(selector); } FormsBuilder.prototype.build = function(cb) { Array.prototype.forEach.call(this.forms, cb); }; (new FormsBuilder('.js-feedback-form')).build(function(form) { new FormWrapper(new FeedbackForm({ element: form, main: new NativeElement(form.querySelector('.js-form-main')), success: new NativeElement(form.querySelector('.js-form-success')), submitter: new Submitter(form.querySelector('.js-feedback-form-submitter')), controls: [ new TextFormControl( form.querySelector('.js-feedback-form-text-control'), new FormControlError(form.querySelector('.js-feedback-form-text-control-error')), 'comment' ), new EmailFormControl( form.querySelector('.js-feedback-form-email-control'), new FormControlError(form.querySelector('.js-feedback-form-email-control-error')), 'email' ) ] })); }); (new FormsBuilder('.js-lead-form')).build(function(form) { new FormWrapper(new LeadForm({ element: form, main: new NativeElement(form.querySelector('.js-form-main')), success: new NativeElement(form.querySelector('.js-form-success')), submitter: new Submitter(form.querySelector('.js-lead-form-submitter')), controls: [ new TextFormControl( form.querySelector('.js-lead-form-text-control'), new FormControlError(form.querySelector('.js-lead-form-text-control-error')), 'text' ), new PhoneFormControl( form.querySelector('.js-lead-form-phone-control'), new FormControlError(form.querySelector('.js-lead-form-phone-control-error')), 'phone' ) ] })); }); (new FormsBuilder('.js-exit-lead-form')).build(function(form) { StdLib.cache('forms', 'exit', (function() { return new FormWrapper(new LeadForm({ element: form, main: new NativeElement(form.querySelector('.js-exit-lead-form-main')), success: new NativeElement(form.querySelector('.js-exit-lead-form-success')), submitter: new Submitter(form.querySelector('.js-exit-lead-form-submitter')), controls: [ new TextFormControl( form.querySelector('.js-exit-lead-form-text-control'), new FormControlError(form.querySelector('.js-exit-lead-form-text-control-error')), 'text' ), new PhoneFormControl( form.querySelector('.js-exit-lead-form-phone-control'), new FormControlError(form.querySelector('.js-exit-lead-form-phone-control-error')), 'phone' ) ] })); })()); }); forms.modal = (function() { var form = document.querySelector('.js-modal-lead-form'); return new FormWrapper(new ModalLeadForm({ element: form, main: new NativeElement(form.querySelector('.js-form-main')), success: new NativeElement(form.querySelector('.js-form-success')), submitter: new Submitter(form.querySelector('.js-modal-lead-form-submitter')), controls: [ new TextFormControl( form.querySelector('.js-modal-lead-form-text-control'), new FormControlError(form.querySelector('.js-modal-lead-form-text-control-error')), 'text' ), new PhoneFormControl( form.querySelector('.js-modal-lead-form-phone-control'), new FormControlError(form.querySelector('.js-modal-lead-form-phone-control-error')), 'phone' ) ] })); })(); var contentableDialogLead = (function() { var documentNode = DOM.node(document); var documentNodes = DOM.nodes(document); var store = {}; function processContentLink(link) { link.addEventListener('click', contentLinkClickHandle(link)); } function contentLinkClickHandle(link) { return function() { var id = generateID(); var href = DOM.attr(link, 'href'); var title = DOM.read(link); var targetID = href.substr(1); Maybe .from(store[targetID]) .map(function(payload) { DOM.remove(payload.parent, payload.dialog); return payload; }); Maybe .from(documentNode('#contentable-dialog-template')) .filter(function(dialogClone) { return DOM.node(dialogClone)('.js-contentable-dialog-entry-title'); }) .filter(function(dialogClone) { return DOM.node(dialogClone)('.js-contentable-dialog-frame.active'); }) .filter(function(dialogClone) { return DOM.nodeByID(targetID); }) .map(function(dialogTemplate) { return DOM.clone(dialogTemplate); }) .map(function(dialogClone) { return DOM.setID(dialogClone, id); }) .map(function(dialogClone) { DOM.write(DOM.node(dialogClone)('.js-contentable-dialog-entry-title'), title); return dialogClone; }) .map(function(dialogClone) { var parent = DOM.nodeByID(targetID); DOM.append(parent, dialogClone); store[targetID] = { dialog: dialogClone, parent: parent }; return dialogClone; }) .map(function(dialogClone) { buildForm({ dialog: dialogClone, showUser: function() { DOM.removeClass(DOM.node(dialogClone)('.js-contentable-dialog-user'), 'hidden') } }); return dialogClone; }) .map(function(dialogClone) { var activeFrame = DOM.node(dialogClone)('.js-contentable-dialog-frame.active'); each(DOM.nodes(dialogClone)('.js-contentable-dialog-frame-changer'), processFrameChanger({ dialog: dialogClone, activeFrame: activeFrame })); return dialogClone; }) .map(function(dialogClone) { DOM.show(dialogClone)(); return dialogClone; }); }; } function processFrameChanger(data) { return function(changer) { changer.addEventListener('click', frameChangerClickHandle(changer, data)); }; } function frameChangerClickHandle(changer, data) { return function() { var activeFrame = data.activeFrame; var dialog = data.dialog; var frameID = DOM.attr(changer, 'data-frame'); var hideUser = function() { DOM.addClass(DOM.node(dialog)('.js-contentable-dialog-user'), 'hidden'); }; Maybe .from(DOM.node(dialog)('#' + frameID)) .map(function(targetFrame) { var isHideUser = Boolean(DOM.attr(targetFrame, 'data-hide-user')); isHideUser && hideUser(); DOM.addClass(targetFrame, 'active'); DOM.removeClass(activeFrame, 'active'); return targetFrame; }); }; } function buildForm(data) { var dialog = data.dialog; var showUser = data.showUser; Maybe .from(DOM.node(dialog)('.js-contentable-dialog-form')) .map(function(form) { var formNode = DOM.node(form); var contentableLeadForm = new LeadForm({ element: form, main: new NativeElement(formNode('.js-contentable-dialog-form-main')), success: new NativeElement(formNode('.js-contentable-dialog-form-success')), submitter: new Submitter(formNode('.js-contentable-dialog-form-submitter')), controls: [ new TextFormControl( formNode('.js-contentable-dialog-form-text-control'), new FormControlError(formNode('.js-contentable-dialog-form-text-control-error')), 'text' ), new PhoneFormControl( formNode('.js-contentable-dialog-form-phone-control'), new FormControlError(formNode('.js-contentable-dialog-form-phone-control-error')), 'phone' ) ] }); contentableLeadForm.subscribe('success', function() { showUser(); }); new FormWrapper(contentableLeadForm); return form; }); } return { init: function() { Maybe .from(documentNodes('.content-links')) .map(function(links) { each(links, processContentLink); return links; }); } }; })(); contentableDialogLead.init(); })(window);