Когда я познакомился с шаблонами проектирования, я сразу стал искать возможности применения в js (Где паттерны встречаются на практике, для решения каких практических задач их нужно использовать). Поначалу я недоумевал: все с чем я работал — шаблоны не использовало. Я попросту не мог понять зачем. внятного ответа на вопрос для каких практических задач нужно использовать тот или иной шаблон проектирования я не нашел. Со временем я переступил ту черту, которая отделяет «Верстальщика» от «веб-разработчика», и те поиски кажутся для меня наивными. НО претендент создан, в рунете я такой инфы не мог найти, а значит, пора этой инфой поделиться.

Если вы не в теме,то для начала рекомендую ознакомиться с тем что такое шаблон проектирования «Абстрактная Фабрика», вот тут хорошее описание в доступной форме .

Теперь собственно задача: Страница редактирования профиля пользователя, на которой отражена информация о пользователе — Имя, место работы, аватар, страна, мэйл, сайт, соц. сети, краткая биография. Инфа доступна для редактирования с помощью контекстных инструментов редактирования информации (по-простому: карандашик возле текста, по щелчку на который появляется форма редактирования вместо текста).

По определенным причинам на серверной стороне, было принято решение для каждой отдельной порции информации о пользователе использовать отдельную форму, атрибут action которой, будет содержать URL на котором принимается AJAX запрос для каждой формы свой.

Что имеем по факту: Известное множество форм, с индивидуальным количеством полей и индивидуальным контейнером в верстке для вывода статичных данных (значения полей формы). Также необходимо учитывать что у каждой порции информации о пользователе есть 2 состояния: Состояние редактирования, и состояние отображения, которые переключаются по нажатию на «карандашик».

Логически это набор схожих сущностей с различным набором данных, но общей структурой. Как раз то, что описывает паттерн фабрики. Чтобы в этом убедиться, проведем анализ и спроектируем наш функционал в виде UML диаграммы (прошу сильно не ругать, последний раз работал с UML на первых курсах универа.)


Ниже я привожу пример своей реализации этой задумки.

var MYAPP = MYAPP || {}; //isolated namespace
if (MYAPP.company){
	console.warn('WARNING: MYAPP.company is already defined!');
}else{

	MYAPP.company = (function() {
	// variables
		var that = {};

			
		// factory for on page form objects
		function Forms(){}
		// methods to perform ajax responses to send and receive data from the server
		Forms.prototype = {
			/**
			 * submit() realization
			 */
			// ajax request realization
			submit: function (dataToSend) {
				var self = this;
				if(!dataToSend){
					dataToSend = '';
				}
				$.ajax({
					type: 'POST',
					url: self.ajaxPath,
					data:dataToSend,
					processData: false,
					contentType: false,
					success: function(data) {
						self.afterSubmit(data);
					}
				});

			},
			// check if there exists custom data manipulation handler and evaluate it
			beforeSubmit: function (data, formName) {
				var handler = this.beforeSubmitHandler,
					formData = data;
				
				this.formData = JSON.parse('{"' + decodeURI(formData).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}');
				
				this.formId = formName;

				if(typeof handler == 'function'){
					// if particular data change required
					this.formData = handler(data, formName);
					this.submit(data);
				} else{
					this.submit(data);
				}

			},
			afterSubmit: function (data) {
				var handler = this.afterSubmitHandler;
				if(typeof handler == 'function'){
					handler(data);
				} else{
					throw{
						name: 'Error',
						message: " handler function for processing response after form submit not defined, please define it!"
					};
				}

			},
			/**
			 * refreshes form state: if value was entered by user, then there will be label with rhis value
			 * or there will be prompt to enter value
			 * @function
			 * @public
			 */
			refreshHtml: function () {
				var pureText = $.trim( this.$staticValue.text()).replace(/[^0-9a-zA-Zа-я\s]/g,'').replace( /(\d)\s/, '' ).replace(/\s+/g,'');
				if(($.trim( this.$staticValue.text()) == '' || pureText  == 'NoneNone') && this.$wrapper.length && this.refrashable){
					this.$editable.addClass('hidden');
					this.$editable.removeClass("active");
					this.$description.removeClass('hidden');

				}else if(this.$wrapper.length  && this.refrashable){
					this.$editable.removeClass('hidden');
					this.$description.addClass('hidden');
				}
			}
		};
		/**
		 * factory object directly
		 * @param {String} type - type of fabricated object 
		 * @param {String} ajaxpath - attribute action from each form html element
		 */
		Forms.factory = function (type, ajaxpath) {
			var constr = type,
				newForm;
			if (typeof Forms[constr] !== 'function'){
				throw{
					name: 'Error',
					message: constr + "doesen't exist"
				};
			}
			if(typeof Forms[constr].prototype.beforeSubmit !== "function"){
				Forms[constr].prototype = new Forms();
			}
			newForm = new Forms[constr](ajaxpath);
			return newForm;
		};

		/**
		 * set of child objects that will override the factory default
		 */	
		Forms.name_form = function (path) {
			this.afterSubmitHandler = function (data) {
				if (data.success){
					$('#static-name-value').text($('#id_name').val());
					this.refreshHtml();

				}else{
					this.$editable.addClass('err');
				}
			};
			this.ajaxPath = path;

		};
		Forms.email_form = function (path) {
			var mailValue;
			this.afterSubmitHandler = function (data) {
				if (data.success){
					mailValue = $('#id_email').val();
					$('#static-email-value').attr('href', 'mailto:'+mailValue).text(mailValue);
					this.refreshHtml();
				}else{
					this.$editable.addClass('err');
				}
			};
			this.ajaxPath = path;

		};
		Forms.found_form = function (path) {
			var currValue,
				$input;
			this.afterSubmitHandler = function (data) {
				if (data.success){
					var $fullComplete = $('.full-complete',this.$staticValue);

					$input = $('#id_foundation');
					currValue = $.trim($input.val());

					$fullComplete.text(currValue);

					this.refreshHtml();

				}
			};
			this.refreshHtml = function () {

				var $fullComplete = $('.full-complete',this.$staticValue),
					$notComplete = $('.not-complete',this.$staticValue);
				if($.trim($fullComplete.text()) == ''){
					$fullComplete.addClass('hidden');
					$notComplete.removeClass('hidden');

				}else{
					$fullComplete.removeClass('hidden');
					$notComplete.addClass('hidden');
				}
				return false;
			};

			this.ajaxPath = path;

		};
	
		 /**
		 * initialize and configure the whole module
		 */
		that.init = function(options) {

			$.extend(this.lang, options.lang);
			$.extend(this.opt, options);

			this.forms = {};
			$(function () {
				

			
				//forms init
				$('.ajax-form').each(function () {
					var formName = $(this).attr('id'),
						path = $(this).attr('action'),
						inputData,
						$wrapper = $(this).closest('.'+self.opt.formWrapperClass),
						$staticValue = $('.'+self.opt.staticValueClass, $wrapper),
						$description = $('.'+self.opt.descriptionClass, $wrapper),
						$editable = $('.'+self.opt.editableClass, $wrapper),
						$closeButton = $('.'+self.opt.closeButtonClass, $wrapper),
						

					//make and initialize form objects
					/**
					 * It is where is magic happen
					 * Forms.factory(formName,path) factory method call
					 */
					self.forms[formName] = Forms.factory(formName,path);
					/*
					 * form object properties assignation 
					 */
					self.forms[formName].$form = $(this);
					self.forms[formName].$wrapper = $wrapper;
					self.forms[formName].$staticValue = $staticValue;
					self.forms[formName].$description = $description;
					self.forms[formName].$editable = $editable;
				

					// submit events handler
					$(this).off('submit');
					$(this).on('submit', function () {
						inputData = $(this).serialize();
						self.forms[formName].beforeSubmit(inputData, formName);
						
					});
					$closeButton.on('click', function() {
						$editable.removeClass(self.opt.formCurrentClass);
						self.forms[formName].refreshHtml();
						return false;
					});
				});
			});
		};
		return that;
	}());
}

В итоге: Мы с вами видим конкретный рабочий пример полезного применения шаблона проектирования «Абстрактная фабрика» в js.

Надеюсь был полезен, все вопросы и предложения оставляйте в комментариях ниже.



1 Комментарии

добавить
Сергей
06.01.2017 20:35:22
Полезный паттерн.
Чтобы комментировать, нужно авторизоваться

Советуем почитать


Почему стоит изучать Ruby on Rails
Администратор 0

Почему стоит изучать Ruby on Rails читать далее

Вы начинающий программист? Или просто думаете какой бы язык изучить? Очень рекомендуем вам обратить внимание на Ruby on Rails. Не смотря на обилие языков программирования и доступных фреймворков, Ruby on Rails очень популярен среди web-разработчиков. Всё благодаря функционалу и скорости разработки.

0 28.01.2018 17:12:42

Федеральная система
Сергей 0

Федеральная система "Город" читать далее

В прошлый раз описал процесс работы с платёжной системой Cyberplat, теперь хочу поделиться опытом работы с ФСГ (Федеральная система город).

Разработано сие чудо ЦФТ. Старались делать все по ГОСТ, поэтому произвести интеграцию не так просто, как хотелось бы (рассматриваем PHP).

0 11.07.2016 17:40:15