25 сентября 2014 в 06:58

Визуальный конфигуратор окон, написанный за один час tutorial recovery mode

Решал интересную задачу – сделать визуальный редактор-конфигуратор окон.

Подробностями процесса разработки я с вами, коллеги, и поделюсь.


UPD. Добавил скриншоты.
UPD2. Речь идет об окнах оффлайновых, застекленных, деревянных или пластиковых — через которые на улицу из дома смотрят

Спасибо за отклики!


Бизнес-требования


Интервьюирую заказчика.

1. Это модуль для сайта, который должен работать в произвольных популярных кейсах.
2. В режиме редактирования программа должна позволять указывать количество и расположение проемов в окнах.
3. В режиме редактирования программа должна позволять указывать способ открывания проемов в окнах, пять вариантов: нет открывания, налево, направо, налево и откидывается, направо и откидывается.
4. В режиме отображения программа должна картинкой в произвольном масштабе отображать конфигурацию окна.
5. Не нужно хранить и работать со сведениями о размере, пропорциях, цвете и других характеристиках окна. Картинки должны быть цветными и понятными. ЕСКД в данном случае не при делах.
6. Не должно глючить, тупить, должно быть кроссбраузерно, должно работать на в браузерах планшетных ПК и на смартфонах и т.д.

На этом этапе мы совместно с заказчиком поиском по картинкам Google просматриваем интерфейс аналогичных продуктов. Поиском по сайтам находим продавцов окон, и посещаем десяток сайтов, чтобы посмотреть на интерфейс онлайн-конфигураторов и вообще ассортимент конфигураций окон. Обсуждаем, что у нас должно быть, и чего, быть не должно.

ТУ и ТЗ


Теперь дополняем бизнес-требования техническими условиями, для того, чтобы в итоге сформировать техническое задание.
1. Изходя из требования произвольного масштабирования – возникает понимание, что графика должна быть векторной. Кроссбраузерное решение, которое удовлетворит – HTML5 canvas.
2. Очевидно, должно быть два режима: режим редактирования и режим отображения.
3. В режиме редактирования данные должны сохраняться в input type=hidden. Я не буду вносить изменений в CMS – зачем мне лишние головняки? Просто добавлю одно поле в формы для добавления и редактирования, в СУБД и в соответствующие модели (у меня реально это происходит одним действием, если у вас нет – вероятно имеет смысл пересмотреть структуру программы).
4. В режиме редактирования ранее созданная визуальная конфигурация окна должна восстанавливаться из данных, находящихся и подставленных автоматически в поле input type=hidden.
5. В режиме отображения CMSка отдаст данные, как свойство какого-нибудь div, и моя программа должна эти данные: а) обнаружить, б) нарисовать по ним окно.
В данном случае спецификацию я делать не буду, а пойду по пути наименьшего сопротивления. Хорошая часть видения решения присутствует уже на данный момент, поэтому я начну реализацию немедленно.

Разработка


Суровая программисткая реальность: не хочу усложнять себе жизнь, и поэтому изначально создаю масштабируемые и сопровождаемые решения. Поэтому DRY, поэтому абстракции и слои – сразу, по умолчанию.

Когда просматривал разновидности окон, зарисовал в тетрадке карандашом небольшой каталог, чтобы понять, что предстоит рисовать. Когда я делал эти зарисовки, пришло понимание, что я не хочу делать это на CSS (вероятно зря), и продолжать работать с <canvas />.
Иду искать библиотеку для работы с canvas. Нахожу calebevans.me/projects/jcanvas, бегло просматриваю документацию, оцениваю качество исходников и понимаю, что это то, что мне нужно сейчас.
Понимаю, что рисование будет самой низкоуровневой функцией. И вообще, давно хочется порисовать. Пробую несколько функций по документации, нахожу примеры онлайн в песочнице. Все работает, все устраивает.

Начинаем рисовать


Создам функцию-основу для рисования окна.
function windows_init(selector)
{
	window_canvas = $('<canvas></canvas>').
		attr('width',window_width).
		attr('height',window_height).
		attr('background','blue').
		insertAfter(selector);
}

Естественно, функции не хранят параметры (это называется данными). Внутри функций – переменные.
В тот момент совесть не просыпалась, поэтому они в глобальной области видимости. Если она проснется – просто положу все в класс. Если проснется одновременно с ленью (или здравым смыслом) – буду писать на CoffeeScript. Сейчас звезды встали в определенное положение, и есть некоторое понимание того, что конечный продукт будет маленькой программой, состоящей из десятка фунций jQuery, в связи с чем целесообразность подобных действий в настоящий момент просто не рассматривается. Сначала сделать, чтобы работало. Рефакторинг – потом.
Глядя на свои зарисовки, вижу, что я могу рисовать оконные проемы, как прямоугольники, и обозначать открывание с помощью ровных ломаных линий внутри них.

function make_leaf(canvas, x,y, width, height, window)
{
	canvas.drawRect({
		layer: true,
		strokeStyle: window_silver,
		fillStyle: window_blue,
		strokeWidth: 1,
		x: x, y: y,
		width: width,
		height: height,
		fromCenter: false,
	}); 
}


Теперь – линии, обозначающие открывание. Left — налево, right – направо, tilt – откидывание. Кейса с фрамугой вниз нет (переспрашивал, когда интервьюировал заказчика), поэтому и заморачиваться сейчас не буду. Если возникнет потребность – потом можно будет легко его добавить.
// window opening draw
function open_left(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x, y1: y,
		x2: x + width, y2: y + (height / 2),
		x3: x, y3: y + height,
	});
}

function open_right(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x + width, y1: y,
		x2: x, y2: y + (height / 2),
		x3: x + width, y3: y + height,
	});
}

function tilt(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x, y1: y + height,
		x2: x + (width / 2), y2: y,
		x3: x + width, y3: y + height,
	});
}


Пишу несколько очень быстрых тестов, чтобы попробовать это. Все работает, поэтому перехожу дальше.

Виды окон


Собственно, по конфигурации проемов все окна можно поделить на “вертикальные” (как обычно делают в квартирах), Т-образные. Реже встречаются “горизонтальные” — в подъездах и в учреждениях.
Сначала нарисую что-нибудь попроще. Параметр leafs – количество проемов.
function window_vertical(canvas, x, y, width, height, leafs, window)
{
	var leaf = width / leafs;
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x + (leaf * i);
		var leaf_y = y;
		var leaf_width = leaf;
		var leaf_height = height;
		var leaf_num = i;
		make_leaf(canvas, leaf_x, leaf_y, leaf_width, leaf_height, window, leaf_num);
	}
}


Посредством небольшой отладки и серии мелких тестов привожу функцию в рабочий вид.
Руками передаю параметры и вызываю функции, рисующие открывание – для того, чтобы сверху отображались ломанные линии.
Поворачиваю на 90 градусов, и получаю “горизонтальное” окно.
function window_horisontal(canvas, x, y, width, height, leafs, window)
{
	var leaf = height / leafs;
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x;
		var leaf_y = y + (leaf * i);
		var leaf_width = width;
		var leaf_height = leaf;
		var leaf_num = i;
		make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num);
	}
}


Тестирую, добиваюсь работоспособности.
Красивая пропорция – 1 к 2. Так как в бизнес-требованиях есть указание не заморачиваться с пропорциями, для Т-образного окна сделаю вот такой дизайн.
function window_t(canvas, x,y,width, height,leafs, window)
{
	var w = width / leafs;
	make_leaf(canvas, x, y, width, height / 3, window, 0);
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x + (w * i);
		var leaf_y = y + (height / 3 );
		var leaf_width = w;
		var leaf_height = height * 2 / 3;
		var leaf_num = i + 1;
		make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num);
	}
}


Делаю тесты, заставляю все работать ровно, без рывков.

Каталог


Нарисую все виды окон, с которыми должна работать программа.

function windows_catalog()
{
	window_horisontal(
			window_canvas,
			0,
			padding,
			catalog_height,
			catalog_height, 
			1,
			{type: 'single', leafs: 1, from: 'catalog'});
	var offset = catalog_height + padding;
	for (var i = 2; i < 5; i++)
	{
		window_vertical(
			window_canvas, 
			offset, 
			padding,
			catalog_height * (i / 2),
			catalog_height,
			i,
			{type: 'vertical', leafs: i, from: 'catalog'});
		offset += padding + (catalog_height * (i / 2));
	}
	window_horisontal(
		window_canvas,
		offset,
		padding,
		catalog_height,
		catalog_height, 
		2,
		{type: 'horisontal', leafs: 2, from: 'catalog'});
	offset += padding + catalog_height;
	for (var i = 0; i < 3; i++)
	{
		window_t(
			window_canvas,
			offset,
			padding,
			catalog_height,
			catalog_height,
			i + 2,
			{type: 't', leafs: i + 2, from: 'catalog'});
		offset += padding + catalog_height
	}
}


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

function make_leaf(canvas, x,y, width, height, window, leaf_num)
{
	canvas.drawRect({
		layer: true,
		strokeStyle: window_silver,
		fillStyle: window_blue,
		strokeWidth: 1,
		x: x, y: y,
		width: width,
		height: height,
		fromCenter: false,
		click: function(layer) {
			leaf_clicked(window, leaf_num)
			}
	}); 
}


И функция, которая ловит клик по створке большого окна или маленькому окну в каталоге.

function leaf_clicked(window, leaf_num)
{
	if ( ! window)
	{
		return;
	}
	window_canvas.clearCanvas();
	windows_catalog();
	if (window.size == 'big')
	{
		trigger_opening(leaf_num);
	}
	big_window(window.type, window.leafs);
}


Была мысль сделать раздельные коллбеки, но в процессе причин для совершения лишней работы не нашел.
Добавил функцию-диспетчер, для удобства.

function opening(canvas, x, y, width, height, num)
{
	switch (window_opening[num])
	{
		case 'left':
			open_left(canvas, x, y, width, height); 
			break;
		case 'left tilt':
			open_left(canvas, x, y, width, height); 
			tilt(canvas, x, y, width, height); 
			break;
		case 'right':
			open_right(canvas, x, y, width, height); 
			break;
		case 'right tilt':
			open_right(canvas, x, y, width, height); 
			tilt(canvas, x, y, width, height); 
			break;
	}
}


Переключение открывания створок


Открывание створок будет переключаться щелчком. Что может быть проще?
Сохраню в массиве список створок, и определю во втором массиве возможности по их открыванию.
// window opening
var window_opening = [];
var opening_order = ['none', 'left tilt', 'right tilt', 'left', 'right'];

Заполню массив данными по умолчанию. Не лучший вариант, но на момент написания думал о другом – о вероятном сохранении данных.
function set_opening(leaf_count)
{
	for (var i = 0; i < leaf_count; i++)
	{
		window_opening.push(opening_order[0]);
	}
}


По щелчку должно меняться открывание створки. В цикле по возможностям открывания: нет, налево, направо, налево и откидывается, направо и откидывается.
function trigger_opening(num)
{
	var current = opening_order.indexOf(window_opening[num]);
	if ((current + 2) > opening_order.length)
	{
		current = 0;
	}
	else
	{
		current++;
	}
	window_opening[num] = opening_order[current];
	window_data();
}


И тут же, не уходя далеко…

Сохранение


Данные после редактирования нужно сохранять.
Сделаю сериализацию от руки.
function window_data()
{
	var string = order.type + '|' + order.leafs;
	for (var i in window_opening)
	{
		string += '|' + window_opening[i];
	}
	var select = $('input[name="window_type"]');
	select.val(string);
}


И, теперь никто не мешает рисовать окна из сохраненных данных.

function window_from_string(string)
{
	if ( ! string.length)
	{
		return;
	}
	var data = string.split('|');
	for (var i = 0; i < 10; i++)
	{
		window_opening[i] = data[i + 2];
	}
	big_window(data[0],data[1]);
}


Конфигурация окон может отрисовываться в списках заказов, это очень удобно. Маленькие картинки.
function small_window_from_string(element, string, width, height)
{
	if ( ! string.length)
	{
		return;
	}
	var small_canvas = $('<canvas></canvas>').
		attr('width',width).
		attr('height',height).
		appendTo(element);
	var data = string.split('|');
	for (var i = 0; i < 10; i++)
	{
		window_opening[i] = data[i + 2];
	}
	var leafs = data[1];
	switch (data[0])
	{
		case 'single':
			window_vertical(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 'vertical':
			window_vertical(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 'horisontal':
			window_horisontal(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 't':
			window_t(small_canvas, 0, 0, width, height, leafs, false);
			break;
	}
}


Когда же рисовать?


Программа должна каким-то образом понимать, что настало время рисовать окна.
Исходя из ТЗ, есть два варианта – поле формы и <div /> в произвольном месте.
function windows_handler()
{
	// add or edit
	var select = $('input[name="window_type"]');
	if (select.length)
	{
		select.hide();
		windows_init(select);
		window_from_string(select.val());
	}
	// show small window
	$('.magic_make_window').each(function() {
		small_window_from_string($(this),$(this).attr('window'), $(this).width(), $(this).height())
		});
}


Пожалуй, input[name=«window_type»] – не лучшее решение. Просто на этот момент у меня была цель запустить программу в работу, и я совсем не хотел модифицировать CMSку — поэтому обучил плагин искать свое поле по его имени: windows_type.

Если делать из этой программы библиотеку, нужно положить селектор в переменную. И обязательно завернуть это в класс, чтобы закрыть пространство имен, и т.д.

Итого


Вот переработанный код целиком. Это бета, и она же пошла в продакшн без изменений.
$(document).ready(function() {
	set_opening(10);
});

function windows_handler()
{
	// add or edit
	var select = $('input[name="window_type"]');
	if (select.length)
	{
		select.hide();
		windows_init(select);
		window_from_string(select.val());
	}
	// show small window
	$('.magic_make_window').each(function() {
		small_window_from_string($(this),$(this).attr('window'), $(this).width(), $(this).height())
		});
}

function small_window_from_string(element, string, width, height)
{
	if ( ! string.length)
	{
		return;
	}
	var small_canvas = $('<canvas></canvas>').
		attr('width',width).
		attr('height',height).
		appendTo(element);
	var data = string.split('|');
	for (var i = 0; i < 10; i++)
	{
		window_opening[i] = data[i + 2];
	}
	var leafs = data[1];
	switch (data[0])
	{
		case 'single':
			window_vertical(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 'vertical':
			window_vertical(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 'horisontal':
			window_horisontal(small_canvas, 0, 0, width, height, leafs, false);
			break;
		case 't':
			window_t(small_canvas, 0, 0, width, height, leafs, false);
			break;
	}
}

function window_from_string(string)
{
	if ( ! string.length)
	{
		return;
	}
	var data = string.split('|');
	for (var i = 0; i < 10; i++)
	{
		window_opening[i] = data[i + 2];
	}
	big_window(data[0],data[1]);
}

var window_width = 900;
var window_height = 350;
var catalog_height = window_width / 18;
var padding = 15;
var window_canvas;

var window_blue = '#8CD3EF';
var window_silver = 'white';
var window_gray = 'black';




var order = {type: undefined, leafs: undefined};

function window_data()
{
	var string = order.type + '|' + order.leafs;
	for (var i in window_opening)
	{
		string += '|' + window_opening[i];
	}
	var select = $('input[name="window_type"]');
	select.val(string);
}


function windows_init(selector)
{
	window_canvas = $('<canvas></canvas>').
		attr('width',window_width).
		attr('height',window_height).
		attr('background','blue').
		insertAfter(selector);
	windows_catalog();
}

function windows_catalog()
{
	window_horisontal(
			window_canvas,
			0,
			padding,
			catalog_height,
			catalog_height, 
			1,
			{type: 'single', leafs: 1, from: 'catalog'});
	var offset = catalog_height + padding;
	for (var i = 2; i < 5; i++)
	{
		window_vertical(
			window_canvas, 
			offset, 
			padding,
			catalog_height * (i / 2),
			catalog_height,
			i,
			{type: 'vertical', leafs: i, from: 'catalog'});
		offset += padding + (catalog_height * (i / 2));
	}
	//~ for (var i = 2; i < 6; i++)
	//~ {
		window_horisontal(
			window_canvas,
			offset,
			padding,
			catalog_height,
			catalog_height, 
			2,
			{type: 'horisontal', leafs: 2, from: 'catalog'});
		offset += padding + catalog_height;
	//~ }
	for (var i = 0; i < 3; i++)
	{
		window_t(
			window_canvas,
			offset,
			padding,
			catalog_height,
			catalog_height,
			i + 2,
			{type: 't', leafs: i + 2, from: 'catalog'});
		offset += padding + catalog_height
	}
}

function window_t(canvas, x,y,width, height,leafs, window)
{
	var w = width / leafs;
	make_leaf(canvas, x, y, width, height / 3, window, 0);
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x + (w * i);
		var leaf_y = y + (height / 3 );
		var leaf_width = w;
		var leaf_height = height * 2 / 3;
		var leaf_num = i + 1;
		make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num);
		if (window.from != 'catalog')
		{
			opening(canvas, leaf_x,leaf_y,leaf_width, leaf_height, leaf_num);
		}
	}
}

function window_vertical(canvas, x, y, width, height, leafs, window)
{
	var leaf = width / leafs;
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x + (leaf * i);
		var leaf_y = y;
		var leaf_width = leaf;
		var leaf_height = height;
		var leaf_num = i;
		make_leaf(canvas, leaf_x, leaf_y, leaf_width, leaf_height, window, leaf_num);
		if (window.from != 'catalog')
		{
			opening(canvas, leaf_x, leaf_y, leaf_width, leaf_height, leaf_num);
		}
	}
}

function window_horisontal(canvas, x, y, width, height, leafs, window)
{
	var leaf = height / leafs;
	for (var i = 0; i < leafs; i++)
	{
		var leaf_x = x;
		var leaf_y = y + (leaf * i);
		var leaf_width = width;
		var leaf_height = leaf;
		var leaf_num = i;
		make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num);
		if (window.from != 'catalog')
		{
			opening(canvas, leaf_x,leaf_y,leaf_width, leaf_height, leaf_num);
		}
	}
}

function make_leaf(canvas, x,y, width, height, window, leaf_num)
{
	canvas.drawRect({
		layer: true,
		strokeStyle: window_silver,
		fillStyle: window_blue,
		strokeWidth: 1,
		x: x, y: y,
		width: width,
		height: height,
		fromCenter: false,
		click: function(layer) {
			leaf_clicked(window, leaf_num)
			}
	}); 
}

function big_window(window_type, leafs)
{
	var padding_top = catalog_height + (padding * 2);
	if (window_width > window_height)
	{
		var segment = window_height - padding_top;
	}
	//~ else
	//~ {
		//~ var segment = (window_width - catalog_height - (padding * 3)) / 2;
	//~ }
	order.type = window_type;
	order.leafs = leafs;
	window_data();
	switch (window_type)
	{
		case 'single':
			window_vertical(
				window_canvas,
				0, 
				padding_top,
				segment,
				segment, 
				leafs,
				{type: 'single', leafs: 1, size: 'big'});
			break;
		case 'vertical':
			window_vertical(
				window_canvas,
				0, 
				padding_top,
				segment /2 * leafs,
				segment, 
				leafs,
				{type: 'vertical', leafs: leafs, size: 'big'});
			break;
		case 'horisontal':
			window_horisontal(
				window_canvas,
				0,
				padding_top,
				(segment * 2) / leafs,
				segment, 
				leafs,
				{type: 'horisontal', leafs: leafs, size: 'big'});
			break;
		case 't':
			window_t(
				window_canvas,
				0,
				padding_top,
				segment,
				segment, 
				leafs,
				{type: 't', leafs: leafs, size: 'big'});
			break;
	}
}

function leaf_clicked(window, leaf_num)
{
	if ( ! window)
	{
		return;
	}
	window_canvas.clearCanvas();
	windows_catalog();
	if (window.size == 'big')
	{
		trigger_opening(leaf_num);
	}
	big_window(window.type, window.leafs);
}


function opening(canvas, x, y, width, height, num)
{
	switch (window_opening[num])
	{
		case 'left':
			open_left(canvas, x, y, width, height); 
			break;
		case 'left tilt':
			open_left(canvas, x, y, width, height); 
			tilt(canvas, x, y, width, height); 
			break;
		case 'right':
			open_right(canvas, x, y, width, height); 
			break;
		case 'right tilt':
			open_right(canvas, x, y, width, height); 
			tilt(canvas, x, y, width, height); 
			break;
	}
}

// window opening draw
function open_left(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x, y1: y,
		x2: x + width, y2: y + (height / 2),
		x3: x, y3: y + height,
	});
}

function open_right(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x + width, y1: y,
		x2: x, y2: y + (height / 2),
		x3: x + width, y3: y + height,
	});
}

function tilt(canvas, x, y, width, height)
{
	canvas.drawLine({
		strokeStyle: window_gray,
		strokeWidth: 1,
		x1: x, y1: y + height,
		x2: x + (width / 2), y2: y,
		x3: x + width, y3: y + height,
	});
}

// window opening

var window_opening = [];
var opening_order = ['none', 'left tilt', 'right tilt', 'left', 'right'];

function set_opening(leaf_count)
{
	for (var i = 0; i < leaf_count; i++)
	{
		window_opening.push(opening_order[0]);
	}
}

function trigger_opening(num)
{
	var current = opening_order.indexOf(window_opening[num]);
	if ((current + 2) > opening_order.length)
	{
		current = 0;
	}
	else
	{
		current++;
	}
	window_opening[num] = opening_order[current];
	window_data();
}


Что не показано в статье. Функция windows_handler запускается другим JS-компонентом, по двум событиям: document.ready и успешной загрузке аяксовых данных. Таким образом, окна отрисовываются немедленно после загрузки страницы, и перерисовываются, если происходит интерактивное обновление данных (“живой режим”).
Все юзкейсы выполняются. Сделал простой тест с большим количеством перерисовываний без перезагрузок, оставил машину с запущенными хромом и мозилой на некоторое время – память не жрется. Погонял этот же тест несколько часов в хроме и в сафари на айпаде и макбуке. Проблем не обнаружено.

Скриншоты


Маленькая картинка, создается на клиенте на лету (распечатывается великолепно)


Большая картинка. Размеры можно и поправить, когда-нибудь.


В режиме редактирования. Щелчок на маленькое окошко в каталоге изменяет конфигурацию большого (и сразу же данные в input type=hidden).


Щелчок на створку большого окна изменяет открывание створки.


Красота!


Изменений в CMS не было. Окно добавляется и редактируется в скрытом поле, отрисовывается в div. Получается, что конфигуратор окон можно засунуть в произвольный вордпресс — просто подключив этот скрипт.

В настоящий момент благодаря этому решению продано, заказано и установлено уже очень много новых окон.

Хорошо бы засунуть этот код в какую-нибудь песочницу, вместе с тестами. Как вы считаете?

Сообщайте замечания в личку.

Спасибо!
Артем @customtema
карма
–4,8
рейтинг 0,0
Пользователь
Похожие публикации
Самое читаемое Разработка

Комментарии (26)

  • +1
    Было бы неплохо посмотреть демо на работающем сайте, так сказать в продакшене.
    • 0
      Там закрытая CRM. Вот и спрашиваю в конце статьи — может быть выложить в песочницу?

      Если да, то в какую? У меня с этим мало опыта.
      • +1
        Традиционно используют jsfiddle
        • 0
          Почему-то с ходу там не пошло. Не знаю, стоит-ли разбираться.

          Развернул демо: w-demo.arint.ru/
          • +1
            У вас демо использует ссылку на jQuery.com.
            В связи с вот этой новостью — не самый лучший выбор на ближайшую неделю (пока там всё досконально не проверят).
  • +10
    Визуальный конфигуратор окон, написанный за один час

    Любопытно, а сколько писалась эта статья? Мне просто сравнить скорость, похоже вы просто очень быстрый человек. Такую статью, с таким замечательным оформлением текста и кода, писал бы пару дней, а значит подобный инструмент уж точно больше одного часа.
    PS. И этот комментарий я писал минут 20.
    • +1
      Статья написалась за час, сегодня утром, спросонок.

      Программа писалась примерно за час, тестировалась в течении дня, параллельно с другими делами. Естественно, по минутам не считал, но расклад приблизительно следующий.

      Результат: 380 строк, 18 методов.

      5 минут на чтение документации (посмотрите — там читать нечего, несколько комментариев и готовые примеры под копирование)
      5 минут для того, чтобы нарисовать окно (квадратик и ломаная линия)
      10 минут для того, чтобы нарисовать окна нескольких типов (первая функция методом тыка, вторая и третья — копипастом с минимальными правками)
      5 минут на формирование каталога (все было в тетрадке)
      — веха: половина работы сделана
      20 минут на чай (без печенек)
      10 минут — внедрил «интерактивность» аспектом, это потребовало переработки 4 методов и добавления функции-диспетчера (окошки стали тыкабельными)
      10 минут на тесты (программа маленькая, все ранее написанное тестировалось в процессе, поэтому на этом этапе тестировать особо нечего)
      10 минут на сохранение данных и тест с CMS (очень примитивно — это просто текстовое поле)
      5 минут на отрисовку из сохраненных данных (обертка над интерактивной функцией)
      — веха: все сделано
      чай и тесты

      … упс, Вы правы, понадобилось больше часа. Хотя, по ощущениям — один час.
  • +4
    Не понимаю, о чём статья-то? И для чего она здесь? В ней совершен какой-то front-end прорыв? Или как-то особенно используются какие-то новые интересные инструменты? Сарказма нет. Может я действительно что-то не понимаю?
    • +8
      Не обязательно читать только о прорывах. Иногда просто интересно посмотреть со стороны, как другие программисты решают какие-то проблемы.
      • +1
        Каждый настоящий программист должен написать в своей жизни CMS, логгер и конфигуратор окон ?)

        Оно, конечно, может и верно, но совсем не интересно и не продуктивно.

        А по теме — ИМХО задачу можно решить куда как проще и визуально красивее, например вместо канваса и рисования на нём вставлять реальные красивые картинки створок окон — даже кода будет меньше..
        • –2
          Каталог векторных иллюстраций? И как будет выглядеть настройка проемов? Сколько должно быть иллюстраций?

          Естественно, мы тоже сначала думали именно об этом варианте. Грубо говоря, 10 типов окон, у каждого от 2 (условно) до 5 створок, каждая створка открывается в 5 вариантах. 250-1000 картинок, генерировать легко. А как этим управлять? Пользователь должен выбрать нужный вариант из огромного списка с иллюстрациями? Разбить его на фрагменты… Серьезно? Это далеко не 1 час времени, и в итоге получится [censored]. И, когда понадобится его развивать… вы хоть примерно представляете объем работ?

          Чуть-чуть вашу мысль развить можете? Может быть я что-то не понял.
          • 0
            Я не предлагал всё окно картинкой хранить, а лишь собственно створки (точно также как вы сейчас их храните только кодом рисования). А всё окно (конфигурацию) можно вставлять фоновой картинкой (для красоты), поверх которой уже и накладываются собственно створки (их даже можно масштабировать как вам хочется). По щелчку на створке — подменяем её картинку на следующую допустимую (точно также как делаете Вы кодом)… и т.д. Кодинга будет строк 30 наверно…
            • –1
              а лишь собственно створки

              Зачем?

              (точно также как вы сейчас их храните только кодом рисования).

              Я храню только это: «0|0|0|1|2|0|3|0». Это очень удобно, так как можно подключить плагин к любой CMSке. Вообще к любой.

              Кодинга будет строк 30 наверно…

              Нет. Попробуйте. Юзкейс прописан в начале статьи, и он очень минималистичен.
            • +1
              Вот потом берёшь код таких вот любителей графики, изменяешь размер окна и получаются тоненькие рамы на больших окнах или очень толстыми на маленьких окнах. Только CSS или SVG.
    • +7
      Я вообще только в середине статьи понял, о каких окнах речь идет)))
      • +1
        А окна то оказались аналоговыми!
        • +3
          Оффлайновыми же!!!
  • +3
    А пользователям действительно понятно, что эти странные ломанные линии показывают направление открытия створки окна?
    И да, не похоже что canvas был удачным решением.
    • 0
      Да. Пользователи работают с окнами, им и не только это понятно.

      Почему canvas неудачный?
      • –2
        Потому что у меня на телефоне «HTML5 canvas» нет, пока я не установил новые браузеры. А вот если бы Вы сделали несколько картинок (штук 10 должно хватить) — было бы абсолютно кроссбраузерно, весят они очень немного. Хоть в 4-м IE запускать. И писать проще — массив из картинок, перебирать по щелчку либо по параметрам. Вариантов-то немного, зато при развитии можно картинки усложнить и клиенту реальные окошки показывать.

        • 0
          Ой, в той CRMке еще очень много того, что не будет работать на вашем телефоне.
  • 0
    выложите на github.com, там же демо на github.io можно сделать
    скрипт маленький и функциональный, я ему на гитхабе звездочку поставил бы и при случае форкнул бы для своих нужд: )
  • 0
    Что-то помнится на фрилансим была задача сделать дизайн для этого редактора. =)
    • 0
      До выхода статьи — не может быть. Это первая публикация кода.
      • 0
        Значит это был какой-то похожий редактор. Я бы тогда взялся за дизайн, но за 1000 рублей я заниматься этим не хотел. =)
        • 0
          Ха, прикольно :)

          Это не редактор. Редактор — слишком громко… :)

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Интересные публикации