<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>Сергей Чикуёнок</title>
	
	<link>http://chikuyonok.ru</link>
	<description>веб-разработчик</description>
	<lastBuildDate>Wed, 18 Jan 2012 13:23:00 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/chikuyonok/vikk" /><feedburner:info uri="chikuyonok/vikk" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Canvas как способ оптимизации графики</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/d7cpADtZZvs/</link>
		<comments>http://chikuyonok.ru/2011/08/optimize-with-canvas/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 23:02:33 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Работа с графикой]]></category>
		<category><![CDATA[blending]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[compositing]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=763</guid>
		<description><![CDATA[Мы постепенно начинаем обновлять дизайн сайта Аймобилко и уже выкатили пару новых макетов. Самое заметное изменение — это главная страница сайта: Дизайнер очень хорошо постарался: страница выглядит очень красиво и современно. Осталось только перенести всё эту красоту из фотошопа в веб. Центральный элемент страницы — сцена, на которой показываются новинки нашего каталога. На фоне сцены [...]]]></description>
			<content:encoded><![CDATA[<p>Мы постепенно начинаем обновлять дизайн сайта <a href="http://www.imobilco.ru/">Аймобилко</a> и уже выкатили пару новых макетов. Самое заметное изменение — это главная страница сайта:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/ss01.jpg" alt="ss01" title="ss01" width="500" height="428" class="alignnone size-full wp-image-764" /></p>
<p>Дизайнер очень хорошо постарался: страница выглядит очень красиво и современно. Осталось только перенести всё эту красоту из фотошопа в веб.</p>
<p>Центральный элемент страницы — сцена, на которой показываются новинки нашего каталога. На фоне сцены находится очень большая картинка с кулисами. Если присмотреться, то эти кулисы покрыты лёгким шумом, что придаёт им особый колорит:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/example.png" alt="example" title="example" width="486" height="328" class="alignnone size-full wp-image-775" /></p>
<p>Однако вся эта красота на проверку оказалась очень тяжёлой: в одной картинке объединилось всё худшее, что плохо влияет на сжатие. Это и красный цвет (даёт очень сильные артефакты сжатия в JPEG), и мелкий шум (сильные артефакты в JPEG; плохо упаковывается в PNG). Приемлемое качество картинки было достигнуто при размере в <em>330 КБ</em>, что, на мой взгляд, довольно много для одной картинки. Очень хочется, чтобы главная страница загружалась как можно быстрее. Поэтому я решился на один эксперимент.</p>
<h2>Изучаем картинку</h2>
<p>При детальном изучении картинки можно заменить, что состоит из двух слоёв: собственно, сама сцена с кулисами и слой с шумом. Но шум — это <a href="http://ru.wikipedia.org/wiki/Процедурное_текстурирование">процедурная текстура</a>, которую можно легко сгенерировать прямо в браузере пользователя с помощью canvas. Тогда я смогу отдавать пользователю только базовую картинку, которая очень хорошо сжимается за счёт плавных цветовых переходов.</p>
<p>Простой алгоритм монохромного шума выглядит так:</p>
<pre class="brush: js">
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 200;
var ctx = canvas.getContext('2d');

// получаем все пиксели изображения
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imageData.data;

for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	var color = Math.round(Math.random() * 255);
	// так как шум монохромный, в каналы R, G и B кладём одно и то же значение
	pixels[i] = pixels[i + 1] = pixels[i + 2] = color;

	// делаем пиксель непрозрачным
	pixels[i + 3] = 255;
}

// записываем пиксели обратно на холст
ctx.putImageData(imageData, 0, 0);

document.body.appendChild(canvas);
</pre>
<p>Получим вот такой результат:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/noise.png" alt="noise" title="noise" width="200" height="200" class="alignnone size-full wp-image-765" /></p>
<p>Вполне неплохо для начала. Однако нам не достаточно просто сгенерировать слой с шумной текстурой и наложить его на кулисы с полупрозрачностью. Если ещё внимательней присмотреться к картинке, то можно заменить, что там нет светлых пикселей, есть только тёмные. То есть <em>монохромный шум должен быть наложен на картинку в режиме Multiply</em>.</p>
<h2>Режимы наложения</h2>
<p>Каждый, кто работал с фотошопом и другими продвинутыми графическими редакторами, знает, что такое режимы наложения слоёв:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/blending-modes.png" alt="blending-modes" title="blending-modes" width="210" height="47" class="alignnone size-full wp-image-766" /></p>
<p>Кому-то они могут показаться запредельно сложными с точки зрения программной реализации, однако на самом деле практически все режимы наложения основаны на очень простых алгоритмах. Например, алгоритм режима наложения Multiply выглядит так:</p>
<pre>(colorA * colorB) / 255</pre>
<p>То есть просто умножаем два цвета и делим результат на 255 (отсюда и название Multiply: «умножение»).</p>
<p>Доработаем нашу функцию: загрузим картинку, сгенерируем шум и наложим его в режиме Multiply:</p>
<pre class="brush: js">
// Загружаем картинку. Обязательно ждём, пока она полностью загрузится
var img = new Image;
img.onload = function() {
	addNoise(img);
};
img.src = "stage-bg.jpg";

function addNoise(img) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');

	// нарисуем картинку на холсте, чтобы получить её пиксели
	ctx.drawImage(img, 0, 0);

	// получаем все пиксели изображения
	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data;

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random() * 255;

		// накладываем пиксель шума в режиме multiply на каждый канал
		pixels[i] = pixels[i] * color / 255;
		pixels[i + 1] = pixels[i + 1] * color / 255;
		pixels[i + 2] = pixels[i + 2] * color / 255;
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Получим что-то типа этого:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/stage-noise.png" alt="stage-noise" title="stage-noise" width="323" height="224" class="alignnone size-full wp-image-767" /></p>
<p>Уже похоже на правду, но шум получился грубый: нужно <em>наложить его с меньшей прозрачностью</em>.</p>
<h2>Альфа-композиция</h2>
<p>Процесс смешивание двух цветов с учётом прозрачности называется «<a href="http://en.wikipedia.org/wiki/Alpha_compositing">альфа-композиция</a>». В простейшем варианте алгоритм смешивания выглядит так:</p>
<pre>colorA * alpha + colorB * (1 - alpha)</pre>
<p>где <code>alpha</code> — это коэффициент смешивания (прозрачность) от 0 до 1. В данном случае важно правильно выбрать, что будет фоновым изображением (<code>colorB</code>), а что будет накладываемым (<code>colorA</code>). В нашем случае фоновой будет сцена, а шум — накладываемым.</p>
<p>Добавим в функцию <code>addColor()</code> дополнительный параметр <code>alpha</code> и модифицируем сам алгоритм с учётом альфа-композиции:</p>
<pre class="brush: js">
var img = new Image;
img.onload = function() {
	addNoise(img, 0.4);
};
img.src = "stage-bg.jpg";

function addNoise(img, alpha) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');
	ctx.drawImage(img, 0, 0);

	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data, r, g, b;

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random() * 255;

		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
		r = pixels[i] * color / 255;
		g = pixels[i + 1] * color / 255;
		b = pixels[i + 2] * color / 255;

		// альфа-композиция
		pixels[i] =     r * alpha + pixels[i] * (1 - alpha);
		pixels[i + 1] = g * alpha + pixels[i + 1] * (1 - alpha);
		pixels[i + 2] = b * alpha + pixels[i + 2] * (1 - alpha);
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Получаем именно то, что нам нужно: слой шума, наложенный на картинку в режиме Multiply и прозрачностью 20%:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/stage-noise-alpha.png" alt="stage-noise-alpha" title="stage-noise-alpha" width="323" height="224" class="alignnone size-full wp-image-768" /></p>
<h2>Оптимизация</h2>
<p>У меня картинка генерируется примерно за 400 мс, что довольно заметно. Поэтому мы оптимизируем код, чтобы он работал быстрее.</p>
<p>Размер моей картинки 1293×897 пикселей, что в итоге даёт 1 159 821 итераций цикла. Это довольно много, поэтому в первую очередь нужно оптимизировать операции вычисления, а именно убрать ненужные и повторяющиеся операции.</p>
<p>Например, в цикле три раза высчитывается значение <code>1 - alpha</code>, хотя это постоянное значение для всей функции, поэтому делаем новую переменную за пределами цикла:</p>
<pre class="brush: js">var alpha1 = 1 - alpha;</pre>
<p>Далее, при генерации пикселя шума используется формула <code>Math.random() * 255</code>, однако дальше мы делим этот цвет на 255: <code>r = pixels[i] * color / 255</code>. Соответственно, умножение и деление на 255 можно смело убирать.</p>
<p>Эти простые операции снизили время выполнения скрипта с 400 мс до 300 мс (-25%).</p>
<p>Помним, что у нас больше миллиона итераций по массиву, поэтому каждая мелочь на счету. В цикле мы два раза обращаемся к массиву <code>pixels</code>, сначала чтобы получить значения для вычисления multiply-пикселя, а затем чтобы сделать альфа-композицию. Доступ к массиву довольно дорогая операция, поэтому сохраняем оригинальное значение пикселя в переменную (то есть оставляем только одно обращение к массиву):</p>
<pre class="brush: js">
var origR = pixels[i],
	origG = pixels[i + 1],
	origB = pixels[i + 2];
</pre>
<p>Это экономит ещё около 40 мс.</p>
<p>С учётом всех оптимизаций функция <code>addNoise()</code> выглядит вот так:</p>
<pre class="brush: js">
function addNoise(img, alpha) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');
	ctx.drawImage(img, 0, 0);

	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data, r, g, b, origR, origG, origB;
	var alpha1 = 1 - alpha

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random();

		origR = pixels[i];
		origG = pixels[i + 1];
		origB = pixels[i + 2];

		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
		r = origR * color;
		g = origG * color;
		b = origB * color;

		// альфа-композиция
		pixels[i] =     r * alpha + origR * alpha1;
		pixels[i + 1] = g * alpha + origG * alpha1;
		pixels[i + 2] = b * alpha + origB * alpha1;
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Скорость выполнения скрипта — около 170 мс (было 400 мс), что довольно неплохо.</p>
<h2>Ещё больше оптимизаций</h2>
<p>Внимательный читатель мог заметить, что картинка с кулисами — красная. То есть информация об изображении присутствует только в красном канале, в синем и зелёном её нет. Если её нет, зачем делать вычисления для этих каналов? Поэтому оставляем расчёты только для красного канала: <em>конкретно в моём случае</em> это даст аналогичный результат, а время выполнения снизит до 80 мс:</p>
<pre class="brush:js">
for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	origR = pixels[i];
	pixels[i] = origR * Math.random() * alpha + origR * alpha1;
}
</pre>
<p><strong>Добавлено:</strong> читатель <a href="https://twitter.com/#!/sergeyev">@Sergeyev</a> указал, что можно ещё сократить время выполнения скрипта (-20%), убрав ненужные операции:</p>
<pre class="brush:js">
for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	pixels[i] = pixels[i] * (Math.random() * alpha + alpha1);
}
</pre>
<h2>Результат</h2>
<p>Результат получился довольно неплохим:</p>
<ul>
<li>Вес изображения снизился с 330 КБ до 70 КБ + 1 КБ пожатого JS-кода. На самом деле, картинку можно было бы ещё больше ужать, потому что слой с шумом скроет большинство артефактов JPEG-сжатия.</li>
<li>Такая оптимизация соответствует практикам progressive enhancement: пользователи с браузерами, в которых нет canvas (например, IE6) или отключён JS всё равно получат картинку, но менее детализированную.</li>
</ul>
<p>Единственный минус, который я вижу — это выполнение наложения каждый раз при загрузке страницы, в то время как обычная картинка может быть просто закэширована браузером. Но, во-первых, время выполнения наложения довольно низкое (80 мс), а во-вторых, как вариант, результат можно хранить в localStorage в виде data:url и при следующей загрузке страницы доставать из кэша. Но моя картинка занимает более 1 МБ, так что я не стал сохранять её — доступное пространство можно и нужно использовать с большей пользой.</p>
<p>В целом, считаю эксперимент удавшимся, посмотрим, будут ли жалобы со стороны пользователей. Кто хочет попробовать что-то подобное на своих проектах — добро пожаловать в <a href="http://media.chikuyonok.ru/canvas-blending/">онлайн-демо</a>, в котором я собрал реализации популярных режимов наложения, чтобы оценить их скорость и качество.</p>
<p><strong>Добавлено:</strong> некоторые читатели и <em>Денис</em> в частности справедливо заметил в комментариях, что практически идентичного результата можно добиться <a href="http://float-left.ru/im/">наложением слоя с шумом поверх картинки</a>. Тут скорее речь идёт о не совсем удачном примере, выбранном для статьи, нежели о неправильности подхода в целом.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/d7cpADtZZvs" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/08/optimize-with-canvas/feed/</wfw:commentRss>
		<slash:comments>52</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2011/08/optimize-with-canvas/</feedburner:origLink></item>
		<item>
		<title>Веб-разработка в Eclipse: JavaScript</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/i1UKWQYJ7TY/</link>
		<comments>http://chikuyonok.ru/2011/07/eclipse-webdev2/#comments</comments>
		<pubDate>Tue, 19 Jul 2011 22:03:16 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[code complete]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[jsdt]]></category>
		<category><![CDATA[web tools]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=754</guid>
		<description><![CDATA[Как отмечалось ранее, для работы JS вместо Spket IDE я теперь использую Eclipse JSDT, который входит в состав Eclipse Web Tools Project. Причины для такого перехода вполне естественные: проекты, с которыми я работаю, становятся всё сложнее и больше, нужно больше удобства и контроля над ситуацией. В JSDT меня больше всего привлекло следующее: Рефакторинг: переименование объектов, [...]]]></description>
			<content:encoded><![CDATA[<p>Как отмечалось <a href="/2011/07/eclipse-webdev1/">ранее</a>, для работы JS вместо <a href="http://www.spket.com/">Spket IDE</a> я теперь использую <a href="http://wiki.eclipse.org/JSDT">Eclipse JSDT</a>, который входит в состав<a href="http://www.eclipse.org/webtools/"> Eclipse Web Tools Project</a>. Причины для такого перехода вполне естественные: проекты, с которыми я работаю, становятся всё сложнее и больше, нужно больше удобства и контроля над ситуацией.</p>
<p>В JSDT меня больше всего привлекло следующее:</p>
<ul>
<li>Рефакторинг: переименование объектов, выделение блока в отдельную переменную, объединение определения переменной и присваивания и т.д. Некоторые вещи вроде выделения в метод пока толком не работают, но, надеюсь, в ближайшее время это будет исправлено.</li>
<li>Валидация кода. Помимо обычной проверки синтаксиса, можно настроить более сложные проверки вроде поиска неиспользованных переменных, недостижимый код, переопределение переменной из внешней области видимости и т.д.</li>
<li>Выделение фрагментов camelCase-переменных с помощью Shift+Alt+← и Shift+Alt+→</li>
<li>Поддержка JSDoc.</li>
<li>Удобный Outline/Quick outline; дополнительные окна, в которых показывается документация и код определения текущего объекта.</li>
<li>Автоматическая отбивка кода при его перемещении из/в блок.</li>
<li>Подключение внешних библиотек.</li>
<li>Встроенный дебаггер.</li>
</ul>
<p>Лучше всего будет, если читатель поставит <a href="http://www.eclipse.org/downloads/packages/eclipse-ide-javascript-web-developers/indigor">Eclipse for JavaScript Web Developers</a> и изучит все настройки и менюшки — в том числе контекстные — JSDT (лучше включить перспективу JavaScript), потому что возможностей действительно очень много и их сложно описать в одной статье.</p>
<p>Однако при всей «крутости» этой среды разработки, в ней есть ряд проблем, с которыми пришлось столкнуться прежде, чем окончательно перейти на JSDT.</p>
<h2>Начинаем работу</h2>
<p>Для того, чтобы полноценно использовать все возможности JSDT, обязательно нужно создать проект с JavaScript-природой. Делается очень легко: вызываем File &gt; New &gt; Project&#8230; и в появившемся окошке выбираем JavaScript Project. В появившемся диалоговом окне вводим название проекта и жмём Finish. По умолчанию создаётся веб-проект с поддержкой DOM, папкой с JS-исходниками является сам проект. Когда вы лучше освоитесь с JSDT, то сможете более тонко настраивать проект: указывать подключаемые библиотеки, исключать ненужные файлы и папки из индекса. Пока оставим как есть.</p>
<h2>Module pattern</h2>
<p>Главным преимуществом для меня в Spket IDE была поддержка современных паттернов, в том числе и модуля:</p>
<pre class="brush: js">
var module = (function() {
	return {
		method: function() {}
	};
})();
</pre>
<p>В Spket такая конструкция без проблем отображается в outline и по ней работает code complete, но в JSDT ни то, ни другое не работает:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss011.png" alt="ss011" title="ss011" width="751" height="407" class="alignnone size-full wp-image-755" /></p>
<p>Небольшой JSDoc  исправит ситуацию:</p>
<pre class="brush: js">
/**
 * @type module
 */
var module = (function() {
	return {
		/**
		 * @memberOf module
		 */
		method: function() {}
	};
})();
</pre>
<p>Теперь работает как надо:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss021.png" alt="ss021" title="ss021" width="751" height="407" class="alignnone size-full wp-image-756" /></p>
<p>Но есть ещё одна проблема: приватные переменные и методы модуля не отображаются в Outline, а очень хотелось бы. Это можно исправить, описав самовызывающуюся функцию как конструктор несуществующего класса:</p>
<pre class="brush: js">
/**
 * @memberOf __module
 * @type module
 */
var module = (/** @constructor */ function() {

	function myPrivateMethod() {

	}

	return {
		/**
		 * @memberOf module
		 */
		method: function() {}
	};
})();
</pre>
<p><img src="http://chikuyonok.ru/u/2011/07/ss031.png" alt="ss031" title="ss031" width="751" height="407" class="alignnone size-full wp-image-757" /></p>
<p>Как видно из примера, я описал несуществующий класс <code>__module</code>, двойное подчёркивание я использую в качестве своеобразного соглашения об именовании объектов. Проблема в том, что этот несуществующий класс попадёт в code complete всего проекта, и использование двойного подчёркивания — простой и понятный способ отфильтровать ненужные данные при вызове code complete. Однако этот недостаток очень легко можно превратить в достоинство: таким образом можно описывать структуры объектов, доступ к описанию которых затруднён из-за отсутствия строгой типизации в JS:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss041.png" alt="ss041" title="ss041" width="775" height="476" class="alignnone size-full wp-image-758" /></p>
<h2>Поддержка популярных библиотек</h2>
<p>Базовый набор библиотек для JSDT довольно невелик: это стандартные объекты JavaScript (<code>Array</code>, <code>String</code> и прочее) и стандартный DOM (<code>HTMLElement</code>, <code>document</code> и так далее). То есть если мы напишем, например, <code>document.</code> и вызовем code complete, то увидем список свойств и методов объекта <code>document</code>, экземпляра класса <code>Document</code>. Но современная веб-разработка немыслима без использования популярных библиотек вроде jQuery.</p>
<p>Имея в своём распоряжении JSDoc, можно создать описание практически любого популярно фреймворка и использовать его для подсказок в коде. Так как таких описаний в интернете найдено не было, я воспользовался своим любимым принципом «если хочешь, чтобы что-то было сделано хорошо, сделай это сам» и запустил проект, в котором создаю описания популярных библиотек и фреймворков, с которыми работаю:</p>
<h3><a href="https://github.com/sergeche/jsdt-docs">jsdt-docs</a> — JSDoc для популярных библиотек</h3>
<p>В этом проекте сейчас есть следующие библиотеки:</p>
<ul>
<li><a href="http://www.modernizr.com/">Modernizr 2</a></li>
<li>Browser Addons — разные методы и свойства, которые почему-то отсутствуют в стандартном описании DOM в JSDT.</li>
<li>console — небольшое описание объекта <code>console</code>, который присутствует в современных браузерах.</li>
<li>CSS2Properties — список CSS-свойств для свойства <code>style</code> DOM-элементов. Несмотря на то, что он называется CSS2, в нём присутствуют и CSS3-свойства: такое название выбрано потому, что в стандартном описании <code>Element.prototype.style</code> является объектом класса <code>CSS2Properties</code>.</li>
<li><a href="https://github.com/millermedeiros/js-signals">JS SIgnals</a></li>
<li><a href="http://nodejs.org">Node.JS</a></li>
<li><a href="http://socket.io/">Socket.IO</a></li>
<li><a href="http://documentcloud.github.com/underscore/">Underscore.js</a>.</li>
<li><a href="http://zeptojs.com/">Zepto.js</a></li>
</ul>
<p>Добавить библиотеку довольно просто:</p>
<ol>
<li>Идём в настройки: Preferences &gt; JavaScript &gt; Include Path &gt; User Libraries.</li>
<li>Создаём новую библиотеку: нажимаем кнопку New&#8230;</li>
<li>Вводим название библиотеки в появившемся окне и жмём ОК.</li>
<li>Выделив только что добавленную библиотеку, нажимаем на кнопку Add .js file&#8230; и выбираем один или несколько файлов, относящихся к данной библиотеке.</li>
</ol>
<p>После того, как библиотека была создана, нужно добавить её в проект:</p>
<ol>
<li>Идём в настройки проекта: Project &gt; Properties &gt; JavaScript &gt; Include Path &gt; Libraries.</li>
<li>Жмём кнопку Add JavaScript Library, выбираем User Library, а затем и библиотеки, которые хотим добавить.</li>
</ol>
<p>Теперь у вас в проекте будет работать code complete для указанных библиотек:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss051.png" alt="ss051" title="ss051" width="775" height="476" class="alignnone size-full wp-image-759" /></p>
<h3>Поддержка jQuery</h3>
<p>Добавление поддержки своего любимого фреймворка оказалась не такой уж и простой задачей. Во-первых, он довольно большой и содержит внушительных объемов документацию. Во-вторых — многие методы в нём «перегружены»: например, <code>val()</code> возвращает текстовое значение поля, а <code>val(str)</code> — записывает его и возвращает уже jQuery-объект.</p>
<p>К счастью, у документации к jQuery есть публичный API, который выдаёт описание всех методов в XML. Я написал <a href="https://github.com/sergeche/jsdt-docs/tree/master/jquery-parser">парсер</a> на node.js, который опрашивает API и преобразует XML в JSDoc, понятный для JSDT (и, надеюсь, другим IDE). Так что в случае выхода новой версии jQuery можно быстро перегенерировать всё документацию. Парсер я писал для себя и по-простому, он не поддерживает передачу параметров через командную строку, всё настраивается в <a href="https://github.com/sergeche/jsdt-docs/blob/master/jquery-parser/main.js">main.js</a>:</p>
<ul>
<li>Можно сгенерировать документацию для определённой версии jQuery (<code>TARGET_VERSION</code>).</li>
<li>Можно сгенерировать описание в виде нескольких файлов. Это связано с особенностью JSDT. Как уже отмечалось ранее, в jQuery много «перегруженных» методов, и если их описать в одном JS-файле, то JSDT будет использовать только одно (самое последнее) описание. Однако если сохранить описание методов с одинаковым названием в разных файлах, то JSDT вполне неплохо покажет по ним code complete и документацию. Так что у вас есть выбор: при создании библиотеки jQuery добавить все файлы вида <code>jquery-jsdoc-N.js</code> (Eclipse JSDT) или же только <code>jquery-jsdoс.js</code> если ваша IDE способна прочитать описание с перегруженными методами.</li>
<li>Можно переписать некоторые определения для более удобной работы с code complete (<code>class_map</code>, <code>prefix_map</code>). Например, так переписываются описания для <code>Deferred</code> и <code>Promise</code> объектов, чтобы можно было использовать их описание в JSDoc:</li>
</ul>
<p><img src="http://chikuyonok.ru/u/2011/07/ss06.png" alt="ss06" title="ss06" width="775" height="476" class="alignnone size-full wp-image-760" /></p>
<p>В описании есть ряд классов, которые можно использовать в JSDoc: <code>__jQueryDeferred</code>, <code>__jQueryPromise</code> и <code>__jQueryEvent</code>.</p>
<h3>Пишем плагины к jQuery</h3>
<p>Собственно, как писать плагины рассказывать нет смысла, это <a href="http://docs.jquery.com/Plugins/Authoring">подробно описано в документации</a>. Покажу лишь как сделать так, чтобы ваш плагин появился в code complete. А сделать это очень просто, достаточно методу плагина указать, что он является членом класса jQuery:</p>
<pre class="brush: js">
jQuery.extend(jQuery.fn, {
	/** @memberOf jQuery */
	myPlugin: function() {

	}
});

$('div').m // тут можно вызвать code complete
</pre>
<h3>Поддержка Node.JS</h3>
<p>Отдельно стоит упомянуть Node.JS, так как к модулям нужно обращаться не напрямую, а через функцию <code>require()</code>:</p>
<pre class="brush: js">
var http = require('http');
http.createServer(function() {

});
</pre>
<p>То есть подсказки для модуля должны зависеть от того, какой аргумент передали в функцию <code>require()</code>. В целом, в JSDT эта ситуация решаема: нужно написать отдельный плагин в виде подключаемой библиотеки, который будет находить вызовы функции <code>require()</code>, смотреть на аргумент и возвращать виртуальный объект с необходимыми методами, и может быть я когда-нибудь напишу такую библиотеку. А пока будем довольствоваться малым: указывать тип модуля через <code>JSDoc</code>.</p>
<p>Все модули в <a href="https://github.com/sergeche/jsdt-docs/blob/master/node.js">моей документации</a> описаны в виде классов <code>Node{Name}Module</code>, например: <code>http</code> → <code>NodeHttpModule</code>, <code>util</code> → <code>NodeUtilModule</code>. Поэтому для переменной, в которую записывается модуль, указываем через JSDoc нужный тип:</p>
<pre class="brush: js">
/** @type NodeHttpModule */
var http = require('http');
http. // вызываем code complete
</pre>
<h2>Советы</h2>
<p>В качестве заключения дам несколько советов, которые помогут вам в работе с JSDT:</p>
<ul>
<li>Не используйте фигурные скобки при указании типа переменной с помощью тэга <code>@type</code> — в некоторых случаях JSDT не сможет подцепить тип переменной. Лучше писать так: <code>@type Array</code> (вместо <code>@type {Array}</code>).</li>
<li>При описании модуля тэг <code>@memberOf moduleName</code> достаточно указать только у одного свойства/метода в литерале, все остальные подцепятся автоматически.</li>
<li>Если у вас в проекте есть непосредственно код библиотек (например, jQuery), то его лучше исключать из индекса, чтобы он не мешал работе code complete. Делается это так: заходите в настройки проекта Project &gt; Properties &gt; JavaScript &gt; Include Path &gt; Source. Разворачиваете папку с исходниками, двойной клик по фильтру Excluded и в диалоговом окне в секцию Exclusion patterns добавляете файлы, которые надо исключить.</li>
</ul>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/i1UKWQYJ7TY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/07/eclipse-webdev2/feed/</wfw:commentRss>
		<slash:comments>31</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2011/07/eclipse-webdev2/</feedburner:origLink></item>
		<item>
		<title>Веб-разработка в Eclipse: HTML и CSS</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/MYXgkul_0mQ/</link>
		<comments>http://chikuyonok.ru/2011/07/eclipse-webdev1/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 21:34:19 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[wtp]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=737</guid>
		<description><![CDATA[Когда-то давно, работая ещё в Студии Лебедева, я рассказывал про использование Eclipse IDE в веб-разработке. Тогда моими основными инструментами были Aptana для HTML и CSS и Spket IDE для JS. С тех пор много воды утекло: Aptana всё дальше отходила от идей Eclipse IDE, всё больше превращаясь в самостоятельный продукт для Rails-разработки, а Spket IDE [...]]]></description>
			<content:encoded><![CDATA[<p>Когда-то давно, работая ещё в Студии Лебедева, <a href="http://www.artlebedev.ru/tools/technogrette/soft/eclipse-introduction/">я рассказывал</a> про использование Eclipse IDE в веб-разработке. Тогда моими основными инструментами были <a href="http://www.aptana.com/">Aptana</a> для HTML и CSS и <a href="http://spket.com/">Spket IDE</a> для JS. С тех пор много воды утекло: Aptana всё дальше отходила от идей Eclipse IDE, всё больше превращаясь в самостоятельный продукт для Rails-разработки, а Spket IDE не обновлялся с октября 2009. В это же время проекты, с которыми я работаю, становились всё больше и сложнее и текущего инструментария уже не хватало. Руководствуясь принципом «если хочешь, чтобы что-то было сделано хорошо — сделай это сам» я принялся допиливать IDE до нужного мне состояния.</p>
<p>Самая главная проблема всех редакторов для веб-разработки: они пишутся людьми, которые этой самой веб-разработкой не занимаются. Нет, они [программисты], конечно, могут на досуге собрать в своём редакторе сайтик-другой, но что делать тем, кто занимается веб-разработкой профессионально? Выход я вижу только один: взять хорошую платформу и доработать её до нужного состояния.</p>
<p>Как я уже написал, Aptana превратилось в некого монстра, с избыточным для не-Rails технологам функционалом. Поэтому в качестве платформы я выбрал более простой и, главное, лучше интегрированный в IDE плагин — Eclipse Web Tools Project.</p>
<h2>Eclipse Web Tools Project</h2>
<p>Как можно понять из названия, <a href="http://www.eclipse.org/webtools/">Eclipse WTP</a> — это инструменты для веб-разработки, а не законченный проект (как Aptana, например). Цель этого проекта: создать платформу, на основе которой можно создавать другие, узкоспециализированные инструменты. Этот проект включает в себя редакторы для CSS, HTML, XML, XSL, JavaScript (отдельный большой проект под названием Eclipse JSDT; о нём расскажу в следующих частях). Проект частично включён практически во <a href="http://www.eclipse.org/downloads/">все официальные сборки Eclipse</a>, но для начинающих пользователей рекомендую ставить Eclipse for JavaScript Web Developers, в ней есть всё.</p>
<p>Чтобы не пересказывать <a href="http://help.eclipse.org/indigo/topic/org.eclipse.wst.sse.doc.user/topics/csrcedt004.html">документацию</a>, кратко приведу основные возможности:</p>
<ul>
<li>подсветка кода;</li>
<li>сворачивание/разворачивание кусков кода (code folding);</li>
<li>outline и quick outline (на последний рекомендую обратить особое внимание, по умолчанию вызывается через <span class="pc">Ctrl+O</span><span class="platform-un"> или </span><span class="mac">⌘O</span>);</li>
<li>выделение границ тэгов (aka Balancing), смотрите в основном меню Edit &gt; Expand Selection To;</li>
<li>content assist по тэгам, атрибутам и значениям некоторых атрибутов;</li>
<li>quick fix: переименование тэга (автоматически меняет открывающий и закрывающий тэги), обрамление тэгом;</li>
<li>форматирование всего/выделенного кода;</li>
<li>автоматическое закрытие тэгов.</li>
</ul>
<p>Итак, с платформой определились, теперь нужно её настроить для комфортной работы.</p>
<h2>Включаем поддержку HTML5 и CSS3</h2>
<p>Eclipse WTP в версии 3.3 (которая доступна для Eclipse Indigo) появилась поддержка тэгов HTML5 и некоторых свойств CSS3. Однако сразу они не доступны, нужно немного пошаманить с проектом:</p>
<ol>
<li>Открываем свойства проекта: Project &gt; Properties.</li>
<li>Переходим на вкладку Project Facets и жмём на ссылку Convert to faceted form&#8230;</li>
<li>В появившемся окне выбираем фасет Static Web Module, жмём Apply.</li>
<li>Закрываем окно с настройками проекта.</li>
</ol>
<p>Теперь в content assist будут доступны новые тэги и свойства. Если нет — снова открываем свойства проекта и идём на вкладку Web Content Settings, где настраиваем профили для HTML и CSS.</p>
<h2>Включаем проверку правописания</h2>
<p>Eclipse IDE с версии 3.4 (если мне не изменяет память) поддерживает проверку правописания, и по умолчанию доступен только английский словарь. Чтобы включить проверку правописания русского языка, делаем следующее:</p>
<ol>
<li>Скачиваем и распаковываем <a href="http://media.chikuyonok.ru/eclipse/ru.dict.zip">русский словарь</a>.</li>
<li>Открываем настройки Eclipse, идём в General &gt; Editors &gt; Text Editors &gt; Spelling</li>
<li>В User defined dictionary выбираем распакованный ru.dict.</li>
<li>Ставим кодировку UTF-8</li>
</ol>
<p>Всё, теперь, если редактор, которым вы пользуетесь, написан по гайдам Eclipse Platform, вам будет доступна проверка правописания.</p>
<h2>Eclipse WTP Sugar</h2>
<p>Самую большую ломку при переходе с Aptana на Eclipse WTP у меня вызывало отсутствие content assist для путей к файлам (например, в <code>&lt;script src="..."&gt;</code>): приходилось писать все пути к файлам вручную, что сильно утомляло. Да и в Aptana он был не идеальным: работал только внутри атрибута <code>src</code> (<code>&lt;script src="..."&gt;</code>, <code>&lt;img src="..."&gt;</code> и т.д.), но не работал внутри <code>href</code> (<code>&lt;link href="..."&gt;</code>, <code>&lt;a href="..."&gt;</code>) и уж тем более не в CSS (<code>background url(...)</code>, <code>@import url(...)</code>). </p>
<p>Поэтому, потратив несколько месяцев на изучение Java и архитектуры Eclipse IDE, я написал свой content assist, который исправит этот недостаток, а заодно несколько других (о них чуть позже).</p>
<p>Свой проект я назвал Eclipse WTP Sugar, репозиторий для установки: <a href="http://media.chikuyonok.ru/eclipse/webdev/updates/">http://media.chikuyonok.ru/eclipse/webdev/updates/</a>, исходный код <a href="https://github.com/sergeche/wtp-sugar">доступен на GitHub</a>.</p>
<p>Плагин устанавливается очень просто, как и все остальные:</p>
<ol>
<li>Идём в Help &gt; Install New Software&#8230;</li>
<li>В качестве репозитория (поле Work with) вбиваем <a href="http://media.chikuyonok.ru/eclipse/webdev/updates/">http://media.chikuyonok.ru/eclipse/webdev/updates/</a>.</li>
<li>В появившемся списке плагинов выбираем необходимые и жмём кнопку Next.</li>
<li>Далее проходим стандартную процедуру установки (соглашаемся с лицензией, всё время жмём Next или Finish) и перезапускаем Eclipse.</li>
</ol>
<p>Теперь, при нажатии Ctrl+пробел внутри атрибутов <code>src</code> и <code>href</code> можно увидеть список файлов:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss01.png" alt="ss01" title="ss01" width="413" height="288" class="alignnone size-full wp-image-741" /></p>
<p>Список файлов также выдаётся и в CSS, внутри функции <code>url()</code>:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss02.png" alt="ss02" title="ss02" width="456" height="259" class="alignnone size-full wp-image-742" /></p>
<p>Список файлов по возможности фильтруется. Например, если его вызвал внутри тэга <code>&lt;script&gt;</code>, то увидите список файлов с расширением <code>js</code>, если внутри <code>&lt;link rel="stylesheet"&gt;</code> — файлы с расширением <code>css</code>.</p>
<p>Возвращаясь к разговору о том, что создатели программ для вёрстки сами толком сайты не верстают. Что если нужно использовать абсолютные пути для ссылок на файлы? По умолчанию считается, что абсолютный путь нужно резолвить относительно папки с проектом. Но почему никому не пришла в голову мысль, что веб-пространство проекта не всегда совпадает с корневой папкой проекта? В этом плагине проблема решена: в настройках проекта можно указать, относительно какой папки нужно резолвить абсолютные пути (Project &gt; Properties &gt; Document root):</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss03.png" alt="ss03" title="ss03" width="716" height="334" class="alignnone size-full wp-image-743" /></p>
<h3>Quick outline</h3>
<p>Следующее, что хотелось изменить, так это quick outline: специальное контекстное окошко, которое показывает в компактном виде текущую структуру документа:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss04.png" alt="ss04" title="ss04" width="589" height="451" class="alignnone size-full wp-image-744" /></p>
<p>Эту структуру можно фильтровать, чтобы найти нужны элемент (поиск осуществляется с начала строки, можно использовать символы * и ?). Но, как видно из скриншота, в структуре выводятся только названия тэгов, что делает её малопригодной. WTP Sugar исправляет этот недостаток, выводя гораздо больше полезной информации, по которой, в том числе, можно искать:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss05.png" alt="ss05" title="ss05" width="586" height="401" class="alignnone size-full wp-image-745" /></p>
<h2>Eclipse XV Browser</h2>
<p>Не так давно я написал просмотрщик XML-файлов под названием <a href="http://chikuyonok.ru/2011/02/xv/">XV Browser</a>. В том посте я показал, что его можно использовать внутри Eclipse, однако не учёл один важный факт: работать это будет только если у вас Mac, а в качестве основного браузера используется Safari с установленным плагином XV <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Eclipse XV Browser исправляет этот недостаток. Он представляет из себя обёртку для встроенного браузера (это может быть Webkit либо Mozilla, в зависимости от ваших настроек и версии Eclipse). С помощью этого плагина можно прросматривать <em>любой</em> сайт в виде XML-структуры (Window &gt; Show View &gt; Other&#8230; &gt; XV Browser), либо открывать просматривать XML-файлы проекта (правый клик по файлу, Open With&#8230; &gt; XV Browser).</p>
<h2>Пожелания?</h2>
<p>Если есьт какие-то идеи по улучшению плагинов — пишите в <a href="https://github.com/sergeche/wtp-sugar/issues">Issues</a> проекта. Пока в планах:</p>
<ul>
<li>content assist по классам и идентификаторам, описанных в CSS;</li>
<li>быстрый переход к CSS-определению класса или идентификатора;</li>
<li>виджеты для редактирования CSS Gradients и CSS Box Shadow.</li>
</ul>
<p>В следующей статье рассмотрим, как улучшить работу с Eclipse JSDT (JavaScript).</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/MYXgkul_0mQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/07/eclipse-webdev1/feed/</wfw:commentRss>
		<slash:comments>52</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2011/07/eclipse-webdev1/</feedburner:origLink></item>
		<item>
		<title>Список блоков с разным вертикальным выравниванием</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/ehgUFEdSbZo/</link>
		<comments>http://chikuyonok.ru/2011/04/inline-vertical-align/#comments</comments>
		<pubDate>Tue, 05 Apr 2011 22:00:04 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[inline-block]]></category>
		<category><![CDATA[vertical-align]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=723</guid>
		<description><![CDATA[Когда я работал над очередным улучшением сайта БигБаззи, возникла задача добавления нового режима просмотра предложений — по три штуки в ряд, более компактными блоками (было по два крупных блока в ряд): Дизайнер хочет, чтобы блоки с ценой, статистикой продаж и картинкой были всегда выровнены по одной горизонтальной линии. Но проблема в том, что название предложения [...]]]></description>
			<content:encoded><![CDATA[<p>Когда я работал над очередным улучшением сайта <a href="http://bigbuzzy.ru">БигБаззи</a>, возникла задача добавления нового режима просмотра предложений — по три штуки в ряд, более компактными блоками (было по два крупных блока в ряд):</p>
<p><img src="http://chikuyonok.ru/u/2011/04/example.png" alt="example" title="example" width="800" height="600" class="alignnone size-full wp-image-724" style="max-width:100%;height:auto" /></p>
<p>Дизайнер хочет, чтобы блоки с ценой, статистикой продаж и картинкой были всегда выровнены по одной горизонтальной линии. Но проблема в том, что название предложения всегда произвольной длины и, соответственно, получаются блоки разной высоты.</p>
<p>«Классическое» решение проблемы — прописать минимальную высоту блоку с названием, чтобы угадать некую среднюю величину, выше которой вряд ли что-то появится. Но у этого способа есть два недостатка: во-первых, практика показывает, что обязательно появится название, которое не впишется в отведённое пространство и развалит весь ряд. А во-вторых: при слишком коротких названиях будут появляться неприятные дыры между заголовком и блоком с ценой.</p>
<p>По сути, надо было придумать, <em>как сделать так, чтобы блоки в одном ряду имели разное выравнивание по вертикали</em>:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/scheme.png" alt="scheme" title="scheme" width="500" height="231" class="alignnone size-full wp-image-725" /></p>
<p>Такое расположение усложняется тем, что это не единственный режим просмотра: можно переключится на более крупный, двухколоночный режим. Причём желательно по-меньше трогать DOM-дерево — не делать специальных обёрток вокруг трёх или двух блоков, чтобы потом скриптом их перестраивать. Идеальный вариант — просто менять класс у контейнера и отдать всё изменение структуры на откуп CSS.</p>
<p>Как ни странно, но решение задачи нашлось, причём довольно элегантное.</p>
<p>Обычно такие последовательности блоков делаются самым очевидным образом: через <code>float</code>-элементы. Но этот способ нынче не в моде: все правильные ребята уже давно прочитали статью <a href="http://blog.mozilla.com/webdev/2009/02/20/cross-browser-inline-block/">в блоге Мозиллы</a> о том, как делать их через <code>display: inline-block;</code>. </p>
<p>А что будет, если поставить рядом блоки разной высоты?</p>
<pre class="brush: html">
&lt;style type=&quot;text/css&quot;&gt;
	.wrap, .h, .f {
		display: inline-block;
	}

	.h {
		background: red;
		height: 100px;
		padding: 0 10px;
	}

	.f {
		background: blue;
		height: 20px;
		padding: 0 10px;
		color: #fff;
	}
&lt;/style&gt;

&lt;span class=&quot;wrap&quot;&gt;
	&lt;span class=&quot;h&quot;&gt;header&lt;/span&gt;
	&lt;span class=&quot;f&quot;&gt;footer&lt;/span&gt;
	&lt;span class=&quot;h&quot;&gt;header&lt;/span&gt;
	&lt;span class=&quot;f&quot;&gt;footer&lt;/span&gt;
&lt;/span&gt;
</pre>
<p>Получим вот такой результат:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss1.png" alt="ss1" title="ss1" width="250" height="100" class="alignnone size-full wp-image-727" /></p>
<p>По умолчанию у всех блоков <code>vertical-align: baseline</code>, то есть вертикальное выравнивание по базовой линии (в данном случае, если проще — по последней строке текста). Если указать для <code>.h{ vertical-align: top; }</code> и для <code>.f { vertical-align: bottom; }</code>, то получим довольно интересное решение:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss2.png" alt="ss2" title="ss2" width="250" height="100" class="alignnone size-full wp-image-728" /></p>
<p>Блоки выровнены именно так, как нужно. Убеждаемся в правильности решения, изменив высоту одного из блоков:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss3.png" alt="ss3" title="ss3" width="250" height="200" class="alignnone size-full wp-image-729" /></p>
<p>Дальше всё просто: у шапки и подвала одинаковая и вполне определённая ширина, поэтому прописываем её каждому блоку, а подвал убираем из потока с помощью отрицательного margin’a:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss4.png" alt="ss4" title="ss4" width="184" height="200" class="alignnone size-full wp-image-730" /></p>
<p>Чтобы семантически объединить шапку и подвал, можно использовать любой элемент с <code>display: inline</code>. Итоговая версия доступна на <a href="/u/inline-va/">тестовой странице</a>.</p>
<p>Самое интересное, что этот код работает даже в IE6. Помним, что <code>inline-block</code> в этом браузере эмулируется с помощью <code>display: inline</code> и волшебного hasLayout, например, так: <code>display: inline; zoom: 1;</code>.</p>
<p>В этом решении важно помнить следующее:</p>
<ol>
<li>Шапка и подвал в примере <em>пересекаются</em>, поэтому шапке нужно снизу давать отступ и подтягивать подвал любым доступным способом.</li>
<li>Между элементами конструкции не должно быть переводов строк и пробелов, иначе блоки визуально будут разделены.</li>
</ol>
<p>В данном случае мне очень помогло то, что у подвала вполне предсказуемая высота, поэтому у шапки был добавлен снизу большой фиксированный отступ, чтобы блоки не перекрывались. Это решение полностью решило проблему и отображения, и переключения режимов. Можно пойти дальше и подключить CSS3 Media Queries, чтобы обладатели новых браузеров и больших мониторов могли видеть более трёх предложений в ряду, но это уже другая история. Думаю, читатели смогут найти более творческие и интересные применения этого небольшого трюка <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/ehgUFEdSbZo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/04/inline-vertical-align/feed/</wfw:commentRss>
		<slash:comments>37</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2011/04/inline-vertical-align/</feedburner:origLink></item>
		<item>
		<title>XV — удобная работа с XML-файлами</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/mRkCZ9L3sCM/</link>
		<comments>http://chikuyonok.ru/2011/02/xv/#comments</comments>
		<pubDate>Wed, 16 Feb 2011 00:38:21 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[chrome]]></category>
		<category><![CDATA[safari]]></category>
		<category><![CDATA[xml]]></category>
		<category><![CDATA[xpath]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=711</guid>
		<description><![CDATA[Мне по долгу службы приходится много работать с XML-файлами, которые генерирует CMS. Как правило, для онлайн-просмотра XML-файлов используется Firefox или Internet Explorer, но что делать таким как я, которые используют Safari/Chrome в качестве основного инструмента разработки? Есть плагин xmlview для Safari на Маке от Marc Liyanage, который хоть и спасает ситуацию, но не сильно. Поэтому [...]]]></description>
			<content:encoded><![CDATA[<p>Мне по долгу службы приходится много работать с XML-файлами, которые генерирует CMS. Как правило, для онлайн-просмотра XML-файлов используется Firefox или Internet Explorer, но что делать таким как я, которые используют Safari/Chrome в качестве основного инструмента разработки? Есть плагин <a href="http://www.entropy.ch/software/macosx/xmlviewplugin/">xmlview</a> для Safari на Маке от Marc Liyanage, который хоть и спасает ситуацию, но не сильно.</p>
<p>Поэтому решил написать свой просмотрщик XML-файлов для браузеров на движке Webkit. Встречайте — <a href="https://github.com/sergeche/xmlview">XV</a>:</p>
<p><img src="http://chikuyonok.ru/u/2011/02/overview-2.png" alt="overview-2" title="overview-2" width="984" height="679" class="alignnone size-full wp-image-712" style="max-width:100%;height:auto" /></p>
<h2>Что умеет</h2>
<ul>
<li>Сворачивание/разворачивание элементов. Для избавления от визуального шума соответствующие стрелочки появляются при наведении на элемент. Alt-клик по стрелке сворачивает/разворачивает все внутренние элементы.</li>
<li>Outline для удобного обзора больших документов</li>
<li>Поиск по названию или XPath. По умолчанию ищет вхождение подстроки в названии тэгов и атрибутов, если используются спецсимволы вроде &#8216;/&#8217; или &#8216;[&#8216; то поиск автоматически переключается в XPath-режим.</li>
<li>Режим Quick XPath — моя самая любимая фича. Зажмите клавишу Control (Mac) или Ctrl (PC) при наведении курсора на название элемента или атрибута чтобы войти в этот режим (появится специальный тултип). Нажимайте на клавишу Shift чтобы переключаться между доступными вариантами XPath, а затем просто перетаскивайте текущий элемент в текстовый редактор (используется drag’n’drop). Пользователи Google Chrome могут кликнуть по элементу, чтобы скопировать XPath в буфер обмена. Этот режим очень удобно использовать внутри IDE, например, вот так:</li>
</ul>
<p><img src="http://chikuyonok.ru/u/2011/02/ide.png" alt="ide" title="ide" width="964" height="684" class="alignnone size-full wp-image-713"  style="max-width:100%;height:auto"/></p>
<h3>Скачать</h3>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/eeocglpgjdpaefaedpblffpeebgmgddk">Расширение для Google Chrome</a></li>
<li><a href="https://github.com/sergeche/xmlview/downloads">Плагин для Safari (Mac)</a> — обновлённый xmlview Марка</li>
</ul>
<p>Можно также использовать и в Firefox как стандартный стиль для XML-файла, если скачать соответствующий <a href="https://github.com/downloads/sergeche/xmlview/xv-browser.xsl">XSL-файл</a>:<br />
<code>&lt;?xml-stylesheet type="text/xsl" href="xv-browser.xsl"?&gt;</code>.</p>
<h3>Демонстрация</h3>
<p>Посмотреть, как это выглядит, можно на <a href="http://media.chikuyonok.ru/xmlview/">тестовой страничке</a> (хорошо работает в Safair/Chrome, в Opera и Firefox глючит drag’n’drop).</p>
<p>Исходный код доступен на GitHub:<a href=" http://github.com/sergeche/xmlview/"> http://github.com/sergeche/xmlview/</a><br />
Там же можно сообщения об ошибках (тестировал только на своих XML-файлах) и пожелания.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/mRkCZ9L3sCM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/02/xv/feed/</wfw:commentRss>
		<slash:comments>48</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2011/02/xv/</feedburner:origLink></item>
		<item>
		<title>История одной оптимизации</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/qwO3VI-ypl4/</link>
		<comments>http://chikuyonok.ru/2010/11/optimization-story/#comments</comments>
		<pubDate>Mon, 29 Nov 2010 23:16:30 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[firebug]]></category>
		<category><![CDATA[float]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[repaint]]></category>
		<category><![CDATA[web inspector]]></category>
		<category><![CDATA[оптимизация]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=687</guid>
		<description><![CDATA[Летом мы запустили новый купонный проект BigBuzzy. Таких проектов к тому времени было довольно много и чтобы выделиться из толпы мы решили немного поменять бизнес-модель: вместо одного предложения в день выдавать четыре. Но, как это обычно бывает, аппетит приходит во время еды, поэтому уже спустя несколько месяцев на главной странице красовалось не 4, а 30 [...]]]></description>
			<content:encoded><![CDATA[<p>Летом мы запустили новый купонный проект <a href="http://www.bigbuzzy.ru">BigBuzzy</a>. Таких проектов к тому времени было довольно много и чтобы выделиться из толпы мы решили немного поменять бизнес-модель: вместо одного предложения в день выдавать четыре. Но, как это обычно бывает, аппетит приходит во время еды, поэтому уже спустя несколько месяцев на главной странице красовалось не 4, а 30 предложений.</p>
<p>И мы сразу же начали получать жалобы о жутких тормозах на главной странице. На поиск и устранение проблем у меня ушло два дня. О том, как находились узкие места и будет сегодняшний рассказ. А заодно научимся пользоваться инструментами вроде Web Inspector&#8217;s Timeline (если вы их ещё не освоили).</p>
<h2>Поиск проблемы</h2>
<p>Итак, мы столкнулись с фактом, что наша главная страница тормозит. Источник проблем был найден сразу: это анимированные таймеры у каждого предложения:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/01-timers.png" alt="01-timers" title="01-timers" width="493" height="68" class="alignnone size-full wp-image-688" /></p>
<p>Самые большие проблемы наблюдались в Firefox: загрузка процессора на главной странице доходила до 70%. Поэтому я начал рассматривать скрипт таймера под микроскопом, а именно в Web Inspector, который по умолчанию входит в состав браузеров Safari и Chrome. Вообще, многие ребята довольно снисходительно относятся к этому инструменту, продолжая по привычке работать в Firebug&#8217;е, а зря. Лично для меня Web Inspector стал основным инструментом для отладки: выглядит он приятнее и содержит ряд полезных нововведений.</p>
<h2>Исследуем узкие места</h2>
<p>Так как сам скрипт таймера довольно простой, то не было смысла заниматься его профилированием — проблема явно где-то в reflow и repaint. Поэтому скрипт нужно исследовать через Timeline:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/02-timeline.png" alt="02-timeline" title="02-timeline" width="659" height="314" class="alignnone size-full wp-image-689" /></p>
<p>Полагаю, что многие читатели ещё ни разу не сталкивались с этим инструментом, поэтому принцип его работы и поиска проблем опишу в небольшом уроке. Стоит отметить, что Web Inspector в Chrome немного круче, чем в Safari, поэтому рекомендую пользоваться первым браузером.</p>
<p>Timeline показывает нам практически все процессы, которые происходят в браузере: запуск скрипта, отработка события, перерисовка экрана, установка таймера, отправка аякс-запроса и т.д. Так как данных довольно много и в них легко запутаться, я рекомендую изолировать исследуемый скрипт — выделить его в отдельную страницу. Для практики можно начать с <a href="/u/performance/">простого шаблона</a>, на котором мы будем экспериментировать.</p>
<p>Открываем шаблон в браузере и запускаем Web Inspector, вкладка Timeline. На странице есть красный квадратик и кнопка «Test». Чтобы начать исследование, нужно нажать на кнопку записи вкладки Timeline <img src="http://chikuyonok.ru/u/2010/11/03-rec.png" alt="03-rec" title="03-rec" width="30" height="22" class="alignnone size-full wp-image-690" />, а потом нажать на кнопку «Test» в основном окне браузера. Наш квадратик посинел и стал больше по высоте, а в Timeline записались следующие события:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/04-test1.png" alt="04-test1" title="04-test1" width="736" height="271" class="alignnone size-full wp-image-691" /></p>
<p>Первые три записи относятся непосредственно к кнопке, которую нажали: применили псевдо-класс <code>:active</code> (Recalculate style), отобразили изменения на экране (Paint), вернули кнопку в исходное состояние, убрав <code>:active</code> (Recalculate style). После того, как пользователь отпустил кнопку мышки сработало событие <code>click</code>, и именно оно и всё, что ниже, нас будет интересовать.</p>
<p>Во время клика сработал следующий скрипт:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	el.style.backgroundColor = 'blue';
	el.style.height = '100px';
}
</pre>
<p>Ничего особенного: просто получили ссылку на элемент и поменяли у него цвет фона и высоту. Этот процесс был отображён на временной шкале: пересчитали стили (Recalculate style), пересчитали геометрию объектов (Layout) и отобразили изменения (Paint).</p>
<p>Как видите, несмотря на то, что мы поменяли два СSS-свойства, пересчёт стилей произошёл всего один раз. Поменяем скрипт:</p>
<pre class="brush:js">
function test() {
	el.style.backgroundColor = 'blue';
	var height = el.offsetHeight;
	el.style.width = '100px';
}
</pre>
<p>Между присваиванием новых стилей мы решили получить высоту объекта. Но временная шкала сильно преобразилась:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/05-test2.png" alt="05-test2" title="05-test2" width="371" height="246" class="alignnone size-full wp-image-692" /></p>
<p>Теперь у нас уже два события Recalculate style, а у самого события <code>click</code> появилась группировка (треугольник слева от жёлтой полоски), которая указывает, какие именно события произошли во время клика.</p>
<p>Этот небольшой пример указывает на две очень важные особенности браузеров — это <em>откладывание перерисовки на момент выхода из функции</em> (первый пример) и <em>существование определённых свойств у элемента, которые принудительно вызывают пересчёт стилей</em> (далее <em>restyle</em>; второй пример). О существовании особых свойств, вызывающих restyle, думаю, многие уже знали: это свойства вроде <code>offsetLeft/Right/Width/Height</code>, <code>clientLeft/Right/Width/Height</code> и так далее. Во втором примере, после установки свойства <code>backgroundColor</code> браузер пометил дерево элементов как требующего пересчёта стилей. А обращение к <code>offsetHeight</code> принудительно вызвало этот  пересчёт. Затем мы установили свойство <code>width</code>, которое отложило пересчёт стилей, геометрии и отображения на момент выхода из потока JS-функций.</p>
<p>Отсюда первое правило: <em>нужно стараться не смешивать получение и запись CSS-свойств</em>. Лучше, например, сначала получить нужные свойства элемента, а затем присвоить новые.</p>
<p>Для любителей jQuery более красноречивым будет вот такой пример:</p>
<pre class="brush:js">
function test() {
	var e = $('#test');
	var width = e.css('width');
	if (width == '50px')
		e.css('width', '100px');

	var height = e.css('height');
	if (height == '50px')
		e.css('height', '100px');
}
</pre>
<p>Вот его шкала:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/06-test3.png" alt="06-test3" title="06-test3" width="303" height="220" class="alignnone size-full wp-image-693" /></p>
<p>Как видите, помимо лишнего Recalculate style появился Layout (reflow), что сделало выполнение скрипта более медленным. Если немного оптимизировать, переместив получение высоты выше в коде:</p>
<pre class="brush:js">
function test() {
	var e = $('#test');
	var width = e.css('width'),
		height = e.css('height');

	if (width == '50px')
		e.css('width', '100px');

	if (height == '50px')
		e.css('height', '100px');
}
</pre>
<p>&#8230;получим совершенно иную картину:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/07-test4.png" alt="07-test4" title="07-test4" width="299" height="190" class="alignnone size-full wp-image-694" /></p>
<p>Лишний Layout (помимо Recalculate style) объясняется тем, что jQuery каждый раз при получении CSS-свойств вызывал <code>window.getComputedStyle()</code>, который принудительно запускает reflow. Справедливости ради стоит отметить, что в функции <code>jQuery.css()</code> есть оптимизация, которая сначала проверяет наличие запрашиваемого свойства в <code>element.style</code> и если его там нет, вызывает <code>window.getComputedStyle()</code>. Но в любом случае, лучше всегда разделять чтение и изменение свойств.</p>
<h2>Таймеры</h2>
<p>Напомню, что я занимался оптимизацией таймеров, которых было несколько. И каждый таймер работает через <em>свой <code>setTimeout()</code></em>. Посмотрим, что это означает на практике:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	setTimeout(function(){
		el.style.backgroundColor = 'blue';
	}, 10);
	setTimeout(function(){
		el.style.width = '100px';
	}, 10);
}
</pre>
<p><img src="http://chikuyonok.ru/u/2010/11/08-test5.png" alt="08-test5" title="08-test5" width="379" height="196" class="alignnone size-full wp-image-695" /></p>
<p>У обоих таймеров одинаковый период ожидания и момент исполнения. На шкале видно, что после каждого таймера был запущен пересчёт стилей. Но в реальности момент исполнения будет далеко не всегда одинаковым. Поэтому поменяем задержку у последнего таймера — поставим 11 мс вместо 10 мс:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	setTimeout(function(){
		el.style.backgroundColor = 'blue';
	}, 10);
	setTimeout(function(){
		el.style.width = '100px';
	}, 11);
}
</pre>
<p>И мы видим, что на шкале появился дополнительный repaint:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/09-test6.png" alt="09-test6" title="09-test6" width="487" height="209" class="alignnone size-full wp-image-696" /></p>
<p>В итоге получалось, что из-за нескольких запущенных <code>setTimeout()</code> срабатывали ненужные перерисовки, которые пользователь всё равно не увидит, но процессор это нагружало прилично. Я переписал код таймеров таким образом, чтобы всё работало через один глобальный таймаут.</p>
<p>Итак, суммируя всё вышесказанное, для оптимизации я</p>
<ul>
<li>разделил чтение и запись CSS-свойств;</li>
<li>дополнительно сделал кэширование текущих значений анимации, чтобы меньше обращаться к элементам;</li>
<li>заменил несколько таймаутов на один.</li>
</ul>
<p>В итоге в Firefox нагрузка на процессор снизилась&#8230; всего на 10%. Вообще, это было крайне странно: даже при наличии всего одного анимированного таймера на странице Firefox грузил процессор на 60%, при том что Webkit грузил всего на 5%. Нужно копать дальше.</p>
<h2>Влияние вёрстки на производительность</h2>
<p>Два года назад я делал большое исследование на тему того, <a href="http://www.artlebedev.ru/tools/technogrette/html/browser-performance/">как вёрстка влияет на производительность браузера</a>. Похоже, у моей проблемы схожие корни, поэтому начал перебирать все известные мне варианты оптимизации. Но, к сожалению, ничего не помогало. </p>
<p>Так как все restyle и reflow процессы я оптимизировал, проблема явно была где-то в repaint. Вспомнил, что в Firefox 3.5 появилось событие <a href="https://developer.mozilla.org/en/Gecko-Specific_DOM_Events#MozAfterPaint">mozAfterRepaint</a>, которое позволяет увидеть области, которые были перерисованы во время repaint. Для удобства было поставлено расширение <a href="https://addons.mozilla.org/en-US/firefox/addon/9620/">Firebug Paint Events</a>, которое позволяет отслеживать перерисовки экрана.</p>
<p>Чтобы описать всю бурю эмоций, которые я испытал после просмотра логов, предлагаю читателю посмотреть на скриншот, где указана область перерисовки во время работы <em>всего одного таймера</em> на странице:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/10-bbz.jpg" alt="10-bbz" title="10-bbz" width="400" height="1000" class="alignnone size-full wp-image-698" /></p>
<p>Я специально оставил только 8 из 30 предложений, чтобы картинка не распирала страницу, но смысл, думаю, ясен: во время анимации даже одного таймера перерисовывалось примерно 90% страницы, 15 раз в секунду. И это при условии, что у цифр таймера указан <code>position:absolute</code>, а у их контейнера <code>overflow:hidden</code>. То есть сама анимация по определению никак не могла повлиять на области вне контейнера (на скриншоте обозначен синим прямоугольником), но перерисовывалась почти вся страница.</p>
<p>Около часа мне понадобилось на то, чтобы найти причину такого странного поведения. Ей оказалось&#8230; свойство <code>float:left</code> у одного из контейнеров. Как только я заменял его на <code>float:none</code> нагрузка на процессор падала ниже 10% (с <code>float:left</code> была около 60%). </p>
<p>Проблема проявляется стабильно, причём не только в Firefox, но и в Opera и IE8. Я сделал простую <a href="/u/performance/fx-demo.html">демку</a>, где можно в живую увидеть эту проблему. В ней всего несколько блоков, однако у них указан <code>box-shadow</code> — очень тяжелое в плане нагрузки на процессор CSS-свойство. В правом верхнем углу есть кнопка, которая всего лишь переключает <code>float</code> у контейнера. Понаблюдайте за нагрузкой на процессор при разных состояниях кнопки, а также за областью перерисовки.</p>
<p>В общем виде проблему можно описать так:</p>
<blockquote><p>Repaint срабатывает на контейнере самого дальнего родителя, у которого указан <code>float:left|right</code>.</p></blockquote>
<p>Схематично это выглядит так:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/11-tree.png" alt="11-tree" title="11-tree" width="595" height="409" class="alignnone size-full wp-image-699" /></p>
<p>Причём проблема не только во <code>float</code>. Я перепробовал различные варианты горизонатльной группировки блоков: <code>display:inline-block</code>, <code>display:table-cell</code>, таблицы и даже новомодные flex box — во всех случаях проблема оставалось. Помогало только абсолютное позиционирование боковых блоков.</p>
<p>В общем виде я проблему решил: поставил боковую панель в коде перед основным контейнером и только ей указал <code>float</code>. Основной контейнер был без <code>float</code> и repaint происходил именно там, где нужно. Однако на живом сайте решить проблему не удалось, так как на большинстве страниц стояли clearfix-элементы, из-за которых макет разваливался. Поэтому пришлось пока отключить анимацию с таймеров <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> </p>
<p style="text-align:center">***</p>
<p>Честно говоря, после таких браузерных крендебобелей на всякие пузомерки типа Peackeeper, которыми так хвастаются разработчики с каждым новым релизом своего браузера, без слёз смотреть не получается. Поэтому мой вам совет: заранее узнавайте о всех интерактивных элементах на странице, не увлекайтесь новым CSS3, продумывайте рост сайта заранее и пользуйтесь правильными инструментами для отладки производительности — тогда будет вам счастье и высокая производительность.</p>
<p>Если хотите узнать больше о профилировании производительности, очень рекомендую <a href="http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/">статью Стояна Стефанова</a> на эту тему.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/qwO3VI-ypl4" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/11/optimization-story/feed/</wfw:commentRss>
		<slash:comments>40</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2010/11/optimization-story/</feedburner:origLink></item>
		<item>
		<title>Идея для сниффинга браузеров</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/kbGfHuwDNMc/</link>
		<comments>http://chikuyonok.ru/2010/10/browser-sniffing/#comments</comments>
		<pubDate>Sun, 24 Oct 2010 10:23:10 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[browser sniffing]]></category>
		<category><![CDATA[chunked encoding]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[идея]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=680</guid>
		<description><![CDATA[Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера. Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у [...]]]></description>
			<content:encoded><![CDATA[<p>Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера.</p>
<p>Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у нас гораздо больше возможностей: мы можем не только проверить User Agent браузера, но и его возможности (например, поддержку определённых CSS и JS свойств), что даст нам более точные характеристики браузера.</p>
<p>Собственно, сама идея как раз заключается в том, чтобы скрестить эти два способа: определить с помощью CSS и JS характеристики браузера и в нужный момент сообщить их на сервер. Основа алгоритма — <a href="http://en.wikipedia.org/wiki/Chunked_transfer_encoding">Chunked Transfer Encoding</a>, позволяющий передать ответ (например, HTML-документ) не целиком, а кусками. Итак, сам алгоритм:</p>
<ol>
<li>Когда пользователь запрашивает HTML-страницу, отдаём ему первый фрагмент (chunk), в котором содержится sniffing-код. После этого соединение удерживается на некоторое время, чтобы отдать второй фрагмент документа.</li>
<li>Браузер, получив первый фрагмент, тут же его парсит и пытается отобразить.</li>
<li>В этом первом фрагменте у нас sniffing-код, результатом работы которого является CSS-свойство <code>background-image</code>, применяемое к элементу <code>&lt;html&gt;</code>.</li>
<li>Путь к этой фоновой картинке является уникальным для каждого браузера и так уж получилось, что как только мы применили это свойство элементу, браузер открывает второе соединение и тут же пытается загрузить эту картинку.</li>
<li>Сервер, получив запрос к нашей «картинке» понимает, каким браузером мы зашли и отдаёт вторую часть ответа в первом соединении.</li>
<li>Если обращения к «картинке» не было (например, зашли поисковым роботом), то через какой-нибудь небольшой промежуток времени отдаём стандартный ответ.</li>
<li>После первого «вынюхивания» можно поставить куку, чтобы потом сразу отдавать нужный ответ.</li>
</ol>
<p>В итоге получаем следующие преимущества:</p>
<ul>
<li>Нет никаких редиректов: ответ о типе браузера получаем в одном соединении (полезно для SEO).</li>
<li>Очень сложно обмануть такой сниффер: можно до посинения менять строку User Agent, но результат останется прежним. <s>Гугл сможет эффективней банить Оперу.</s></li>
<li>Более сложные проверки: можно не просто определять название браузера, но и его возможности, благодаря CSS Media Queries.</li>
</ul>
<p>Для желающих поэкспериментировать я написал <a href="https://gist.github.com/31010560af349c448e20">демонстрацию этого подхода</a> на <a href="http://nodejs.org">node.js</a>. К сожалению, у меня на VPS не хватает памяти, чтобы собрать node.js, поэтому предлагаю читателю самому его установить и проверить у себя. <s>Если кто выложит это в онлайн — буду премного благодарен.</s> Выложили: <a href="http://tbms.ru:8126/">http://tbms.ru:8126/</a> (спасибо <a href="http://tbms.ru/">Николаю Митину</a>).</p>
<h2>Полезные уроки</h2>
<p>Пока писал эту демонстрацию, вынес для себя много ценной информации:</p>
<ol>
<li>Первый чанк должен быть достаточно большим, чтобы все браузеры смогли его применить. Самый большой лимит — у движка Webkit: около 2 КБ. Поэтому приходится «добивать» первый фрагмент пробелами, чтобы всё работало как надо.</li>
<li>Webkit очень плохо ведёт себя при переопределении CSS-свойств. Например:
<pre class="brush: css">
html { background-image:url(image1.png); }
html { background-image:url(image2.png); }
		</pre>
<p>		В этом случае Webkit (и десктопный, и мобильный) загрузит обе картинки, хотя понятно, что нужна только последняя. Это ещё одно подтверждение, что мобильные версии сайта лучше делать отдельным проектом, а не вставлять через CSS Media Queries. Иначе попытки сократить траффик заменой больших фоновых картинок на маленькие приведёт к обратному эффекту. Хотя тут нужно больше исследований, возможно, это издержки работы с локальным сервером.
	</li>
<li>Firefox не отработает первый чанк до тех пор, пока не получит элемент <code>&lt;body&gt;</code>. Это довольно серьёзный недостаток в том случае, если вы захотите выдавать уникальные стили и скрипты для браузера в секции <code>&lt;head&gt;</code>. С другой стороны, если послать <code>&lt;script&gt;alert(1)&lt;/script&gt;</code>, то чанк отработает нормально, даже без <code>&lt;body&gt;</code>. Что наталкивает на мысль о существовании неких триггеров, которые заставят браузер сделать то, что нам нужно, осталось только найти наиболее безопасный.</li>
</ol>
<p>Повторюсь, что это только идея, а не призыв к действию. Буду рад, если кто-то на её основе сделает что-то крутое и интересное.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/kbGfHuwDNMc" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/10/browser-sniffing/feed/</wfw:commentRss>
		<slash:comments>38</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2010/10/browser-sniffing/</feedburner:origLink></item>
		<item>
		<title>XSL Tracer</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/JdxxuY6sHl0/</link>
		<comments>http://chikuyonok.ru/2010/10/xsl-tracer/#comments</comments>
		<pubDate>Mon, 04 Oct 2010 18:27:50 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Без рубрики]]></category>
		<category><![CDATA[debug]]></category>
		<category><![CDATA[trace]]></category>
		<category><![CDATA[xsl]]></category>
		<category><![CDATA[xslt]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=676</guid>
		<description><![CDATA[Я, наконец-то, более-менее довёл до ума свой новый проект, которым хочу поделиться с народом. Проект называется XSL Tracer. Его задача заключается в том, чтобы ответить на простой вопрос: откуда после XSL-трансформации пришёл тот или иной элемент? Как это работает можно увидеть на демо-странице. Там отображается результат XSL-преобразования — обычный HTML-документ. Кликнув по любому элементу можно [...]]]></description>
			<content:encoded><![CDATA[<p>Я, наконец-то, более-менее довёл до ума свой новый проект, которым хочу поделиться с народом.</p>
<p>Проект называется <a href="http://github.com/sergeche/xsl-tracer">XSL Tracer</a>. Его задача заключается в том, чтобы ответить на простой вопрос: откуда после XSL-трансформации пришёл тот или иной элемент? Как это работает можно увидеть на <a href="http://media.chikuyonok.ru/xsl-tracer/">демо-странице</a>. Там отображается результат XSL-преобразования — обычный HTML-документ. Кликнув по любому элементу можно получить подробную информацию о нём:</p>
<ul>
<li><strong>XSL</strong>: шаблон, где выводится элемент.</li>
<li><strong>Context</strong>: контекстный XML (на который сработал матч, в случае xsl:apply-templates). Сам элемент выводится урезанным (только 10 внутренних тэгов), чтобы в случае, если это будет корневой элемент, браузер не умер в муках (да и толку от полной структуры никакого).</li>
<li><strong>Source</strong>: место в шаблоне, где непосредственно генерируется выбранный элемент. Очень удобно в случае, если делаете сopy-of результатирующей структуры (в примере это html-код, заданный внтутри переменной).</li>
<li><strong>Call stack</strong>: стэк вызовов apply-template/call-template/apply-imports, через который дошли до вызова текущего шаблона</li>
<li><strong>Inner calls</strong>: список внутренних вызовов шаблонов, которые по каким-то причинам нельзя отобразить (по крайней мере пока) в документе. Например, вызов шаблонов, выдающих атрибуты для выбранного элемента, или его текстовое содержимое. Список отображается под Call stack, и только в том случае, если такие вызовы есть.</li>
</ul>
<p>Демка нормально работает в Safari/Firefox/Chrome, Opera жутко тормозит на больших документах, а в IE даже не открывал.</p>
<p>Везде, где это возможно, выводится xpath к выбранному элементу, который можно скопировать одним кликом, имена файлов и номер строки, чтобы можно было легко найти нужный шаблон в редакторе.</p>
<h2>Как это работает</h2>
<p>Проект состоит из двух частей: <a href="http://github.com/sergeche/xsl-tracer-backend">бэкэнд</a> и <a href="http://github.com/sergeche/xsl-tracer">фронтэнд</a>.</p>
<p>На бэкэнде используется Saxon 6.5 (пока эта версия, так как в 9.2 лично у меня вылезло куча проблем с EXSLT). У него есть встроенный механизм трассировки, который по умолчанию генерирует результат в виде XML (такой файл на реальных проектах вырастает до нескольких мегабайт). Я написал свой трассировочный класс, который выводит данные в более компактном и лёгком JSON-формате, а также дополнительные данные вроде списка используемых в трансформации XSL и XML (подключённых через <code>document()</code>) документов. А также он делает дополнительные штуки вроде резолвинга result-tree фрагментов при вызове <code>xsl:copy-of</code>.</p>
<p>Этот трассировщик генерирует JSON-документ и результатирующий документ (то есть результат предобразования) и собирает их в одном HTML-файле, который можно открывать в браузере. </p>
<p>Дальше за работу берётся фронтэнд, который связывает трассировочные данные с результатом трансформа и обеспечивает всю работу интерфейса.</p>
<h2>Как запускать</h2>
<ol>
<li>Должна быть установлена Java (версия особо не важна).</li>
<li>Скачиваем <a href="http://prdownloads.sourceforge.net/saxon/saxon6-5-5.zip">Saxon 6.5</a></li>
<li>Скачиваем <a href="http://github.com/sergeche/xsl-tracer-backend/downloads">трэйсер</a></li>
<li>Генерируем трассировку вот такой командой в консоли:</li>
</ol>
<p><code>java -classpath /path/to/tracer.jar:/path/to/saxon.jar ru.imobilco.XSLTracer -to /path/to/trace-result.html /path/to/input.xml /path/to/template.xsl</code></p>
<p>В случае успешной работы трэйсера сгенерируется файл <code>trace-result.html</code>, который можно сразу открывать в браузере (CSS и JS берутся с моего сервера). Если возникла ошибка во время трансформа, то файл будет сгенерирован, но вместо документа увидите нормальное сообщение об ошибке. В случае ошибки работы Java, увидите сообщение в консоли.</p>
<p>Надо помнить, что Saxon очень чувствителен ко всяким всяким отхождениям от спецификации, поэтому то, что работало в Xalan/libxsl/где-то ещё может не сработать в Saxon. Но исправления, как правило, довольно мелкие и незначительные.</p>
<h2>Онлайн-версия</h2>
<p>Мой коллега Лёха Баранов написал сервлет, который позволяет запускать через веб, передавая ссылки на XML и XSL файлы. Для него был заведён специальный проект <a href="http://xmltools.ru">xmltools.ru</a>. Работает просто: указываете ссылки на файлы и, если нужно, данные для http-авторизации. Далее сам трансформ находит все зависимые файлы, скачивает их и делает трансформ.</p>
<p>Но с онлайн-версией не так всё просто. В частности, когда результат отдаётся фронтэнду, он тоже начинает скачивать внешние файлы через аякс. И, как вы уже, наверно, догадались, включается ограничение Same-Origin Policy, которое не позволяет загружать данные с другого домена. Вариантов решения два: либо вы свой сервер настраиваете на <a href="http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/">cross-domain ajax</a>, либо используете xmltools.ru как сервис, запрашивая трансформ через какой-нибудь php/python/ruby скрипт, отдавая результат трассировки со своего домена.</p>
<p>Онлайн-версия пока находится в состоянии «альфа», поэтому может быть много багов. И пока бесплатна. Подчёркиваю, <em>пока бесплатна</em>. Дело в том, что такие трассировки генерируют очень большую нагрузку, и если она возрастёт до каких-то критических значений, то придётся прикрыть лавочку и предоставлять услугу на коммерческой основе.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/JdxxuY6sHl0" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/10/xsl-tracer/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2010/10/xsl-tracer/</feedburner:origLink></item>
		<item>
		<title>От простого к сложному</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/Mb5mPmonMvY/</link>
		<comments>http://chikuyonok.ru/2010/07/simple-things/#comments</comments>
		<pubDate>Tue, 06 Jul 2010 12:24:44 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Статьи]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[интерфейс]]></category>
		<category><![CDATA[формы]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=666</guid>
		<description><![CDATA[Насколько сложной может быть форма из двух полей, отправляющая данные аяксом на сервер? С современными фреймворками вроде jQuery, код может выглядеть примерно так: $.post('my-url.php', $('form#my-form').serialize()); Но это минимальный код; код из серии «создаём свой блог/цмс за 5 минут», которым так любят заманивать создатели всяких движков и фреймворков. А что нужно сделать, чтобы такая форма стала [...]]]></description>
			<content:encoded><![CDATA[<p>Насколько сложной может быть форма из двух полей, отправляющая данные аяксом на сервер? С современными фреймворками вроде jQuery, код может выглядеть примерно так:</p>
<pre class="brush: js">
$.post('my-url.php', $('form#my-form').serialize());
</pre>
<p>Но это минимальный код; код из серии «создаём свой блог/цмс за 5 минут», которым так любят заманивать создатели всяких движков и фреймворков. А что нужно сделать, чтобы такая форма стала удобной и понятной для конечного пользователя, то есть «человеческой»?</p>
<p>Недавно мы запустили новый скидочный проект <a href="http://www.bigbuzzy.ru">BigBuzzy</a>. Так как на рынке уже существует масса подобных проектов, единственный способ выделиться из толпы — это сделать проект очень удобным для пользователя, как с концептуальной точки зрения (не надо ждать, пока наберётся определённая группа; удобные способы оплаты), так и с технологической.</p>
<p>Одна из фич проекта — отправка купленного купона себе на телефон по СМС. Так как на месте проведения акции нужно предъявить только номер купона, нет особого смысла печатать его на бумаге, можно показать и с телефона. Увидеть форму в действии можно <s>на бигбаззи, купив 10 купонов</s> на <a href="http://media.chikuyonok.ru/coupon/">этой странице</a>. В этой форме как раз два поля: код и номер телефона. Посмотрим, что можно в ней улучшить.</p>
<h3>Алгоритм работы</h3>
<p>Прежде, чем приступать к работе, нам нужно определиться, какие функции несёт в себе эта форма. Когда пользователь оплатил купон, ему предоставляется на выбор два действия: распечатать или отправить по СМС. Чтобы отправить по СМС, нужно знать номер телефона пользователя. В принципе, можно заставить его ввести этот номер при регистрации, однако это с большой долей вероятности отпугнёт потенциальных покупателей: с чего это вдруг сайт требует с меня номер телефона? Поэтому форму будем показывать только тогда, когда пользователь нажмёт на кнопку «Отправить по СМС»: в этом случае ему будет понятно, почему требуется ввести номер телефона.</p>
<p>А что делать, если пользователь уже ввёл номер при отправке первого купона и хочет отправить второй? Логично, что он захочет отправить купон на тот же самый номер. Нужно ли в этом случае показывать форму? Решаем, что нужно. Во-первых, пользователь может захотеть отправить купон своему другу в качестве подарка или этот друг только что позвонил и попросил выручить, докупив ещё два купона на боулинг. Заставлять пользователя идти в Личный кабинет и менять там номер телефона как-то уж совсем неправильно. Во-вторых, появляющаяся форма будет своеобразным подтверждением действия (вдруг он случайно нажал на эту кнопку?). Поэтому на нажатие конпки будем запускать анимацию появления формы, а повторное нажатие на эту же кнопку отправить данные на сервер:</p>
<pre class="brush: js">
$(document.body).delegate('.coupon-button', 'click', function(/* Event */ evt) {
	var elem = $(this);
	if (elem.hasClass('coupon-button-sms')) {
		var coupon = elem.closest('.coupon');
		if (!coupon.length)
			return;

		if (!coupon.hasClass('coupon-sms-mode')) {
			// переключаем купон в режим отправки СМС
			switchSMSMode(coupon);
		} else  {
			// пытаемся отправить данные на сервер
			tryToSendSMS(coupon);
		}
	}
});
</pre>
<p>Функцию анимации появления формы с вводом номера телефона я тут не привожу, так как она довольно большая и скучная (желающие могут посмотреть исходник). Первая проблема, которая может возникнуть на этом шаге: пользователь может случайно дважды кликнуть на кнопку, что приведёт к моментальной отправке СМС, если там уже был вбит номер. Поэтому нужно поставить <em>блокировку на выполнение действия</em>. Это стандартный подход в анимированных интерфейсах, который позволяет избежать случайных нажатий или даже поломки всего интерфейса (может наложиться несколько анимаций друг на друга). Введём переменную <code>is_animating</code>, которая будет равна <code>true</code> в момент анимации. Обнулять эту переменную будем после завершения анимации через callback-функцию для <code>switchSMSMode()</code>:</p>
<pre class="brush: js">
$(document.body).delegate('.coupon-button', 'click', function(/* Event */ evt) {
	var elem = $(this),
		is_animating = false;

	if (elem.hasClass('coupon-button-sms')) {
		var coupon = elem.closest('.coupon');
		if (!coupon.length || is_animating)
			return;

		if (!coupon.hasClass('coupon-sms-mode')) {
			// переключаем купон в режим отправки СМС
			is_animating = true;
			switchSMSMode(coupon, function(){
				is_animating = false;
			});
		} else  {
			// пытаемся отправить данные на сервер
			tryToSendSMS(coupon);
		}
	}
});
</pre>
<h3>Ввод данных</h3>
<p>Форму мы показали, теперь нужно упростить пользователю ввод данных. Тут надо помнить, что существует множество моделей поведения пользователя. Кто-то быстро введёт код и переключится табом на следующее поле, кто-то будет минуту набирать три цифры и схватиться за мышку, чтобы переключиться на следующее поле, кто-то будет ожидать, что введённая последовательность из 10 цифр автоматически распределиться между полями. Нужно постараться удовлетворить максимальное количество пользователей. Поэтому будем следить за полем «код», и когда там будет 3 цифры — автоматически перебросим фокус на поле с номером:</p>
<pre class="brush: js">
$('.phone-code').keyup(function(){
	if (this.value.length == 3)
		$(this).next('.phone-num').focus();
});
</pre>
<p>Так делают многие и так делать ни в коем случае нельзя. Если пользователь допустил ошибку в коде, то попытка исправить её используя только клавиатуру превратиться в сущий ад: например, нажатие на стрелки клавиатуры всегда будет приводить к перебросу фокуса, потому что сработает событие событие keyup, а в поле уже будет 3 символа. Чтобы сделать поведение формы более естественным, нужно следить за вводом данных: перебрасывать фокус будем только если в предыдущем нажатии на кнопку (<code>last_length</code>) было меньше символов, чем в этом нажатии, количество символов равно трём и каретка находится в самом конце поля:</p>
<pre class="brush:js">
$('.phone-code').bind('keyup change', function(/* Event */ evt) {
	var field = $(evt.target),
		last_length = field.data('last_length') || 0,
		cur_length = field.val().length,
		max_length = parseInt(field.attr('maxlength')) || 3,
		selection = getSelectionRange(field[0]);

	if (cur_length > last_length &amp;&amp; cur_length == max_length &amp;&amp; selection &amp;&amp; selection.start == cur_length) {
		field.next('.phone-number').focus();
	}

	field.data('last_length', cur_length);
});

function getSelectionRange(elem) {
	if ('selectionStart' in elem) { // W3C's DOM
		return {
			start: elem.selectionStart,
			end: elem.selectionEnd
		};
	} else if (document.selection) { // IE
		elem.focus();

		var range = document.selection.createRange(),
			content = elem.value;

		if (range === null) {
			return {
				start: 0,
				end: content.length
			};
		}

		var re = elem.createTextRange();
		var rc = re.duplicate();
		re.moveToBookmark(range.getBookmark());
		rc.setEndPoint('EndToStart', re);

		return {
			start: rc.text.length,
			end: rc.text.length + range.text.length
		};
	} else {
		return null;
	}
}
</pre>
<p>Но тут возникает ещё одна проблема. Далеко не каждый пользователь догадается, что при заполнении поля с кодом фокус перекинется на следующее поле. Он может не глядя на монитор набрать 3 цифры, нажать на таб и набрать ещё 7 цифр. В этом случае наша «помощь» только помешает пользователю, потому что фокус будет где-то совершенно в другом месте. Чтобы такого не случилось, нужно временно заблокировать нажатие на таб на поле с номером телефона после того, как фокус был автоматически переброшен:</p>
<pre class="brush: js">
$('.phone-code').bind('keyup change', function(/* Event */ evt) {
	var field = $(evt.target),
		last_length = field.data('last_length') || 0,
		cur_length = field.val().length,
		max_length = parseInt(field.attr('maxlength')) || 3,
		selection = getSelectionRange(field[0]);

	if (cur_length > last_length &amp;&amp; cur_length == max_length &amp;&amp; selection &amp;&amp; selection.start == cur_length) {
		var num_field = field.next('.phone-number');

		// временно блокируем Tab на следующем поле
		num_field.data('tab_locked', true);
		setTimeout(function() {
			num_field.data('tab_locked', false);
		}, 1000);

		num_field.focus();
	}

	field.data('last_length', cur_length);
});

$('.phone-number').bind('keydown keyup keypress', function(/* Event */ evt) {
	var field = $(evt.target);
	if (field.data('tab_locked') === true &#038;&#038; evt.keyCode == 9) {
		// блокируем работу клавиши Tab
		evt.preventDefault();
	}
});
</pre>
<h3>Валидация данных</h3>
<p>Перед отправкой данных на сервер нужно сделать простую валидацию данных, чтобы убедиться, что введённые данные похожи на номер телефона. Если нет — то намекнём об этом пользователю.</p>
<p>Вводить жёсткий формат записи числовых данных вроде номера телефона или кредитной карты — первый признак полового бессилия разработчика. Для номера телефона нам нужно 10 цифр (код + сам номер), и не важно, написаны цифры слитно или разделены пробелами или дефисами. Пользователь должен написать номер в привычном ему формате, чтобы легче было проверять правильность. Поэтому при валидации удалим из полей все не числовые символы и проверим длину того, что осталось:</p>
<pre class="brush: js">
function validatePhoneNumber(coupon) {
	coupon = $(coupon);

	var re_num = /[^0-9]/g,
		phone_code = coupon.find('.phone-code'),
		phone_num = coupon.find('.phone-number'),
		is_valid_code = phone_code.val().replace(re_num, '').length == 3,
		is_valid_num = phone_num.val().replace(re_num, '').length == 7;

	if (!is_valid_code)
		blinkField(phone_code);
	if (!is_valid_num)
		blinkField(phone_num);

	return is_valid_code &#038;&#038; is_valid_num;
}
</pre>
<p>Если введённые данные не верны, то мы должны сообщить об этом пользователю. Пространство у нас очень ограничено, поэтому будем «моргать» полем, указывая, что там что-то не так. Функция добавляет и удаляет определённый класс у элемента через заданный промежуток времени, в CSS укажем этому классу красный цвет:</p>
<pre class="brush: js">
function blinkField(fld) {
	fld = $(fld);
	if (fld.data('blink_timer'))
		clearInterval(fld.data('blink_timer'));

	var blink_count = 6;

	fld.data('blink_timer', setInterval(function() {
		fld.toggleClass('coupon-field-warning');
		if (blink_count-- < 0) {
			fld.removeClass('coupon-field-warning');
			clearInterval(fld.data('blink_timer'));
		}
	}, 100));
}
</pre>
<h3>Отправка данных</h3>
<p>Данные от пользователя получили и проверили, настало время отправить их на сервер. Опять же, тут есть две потенциальные проблемы. Первая — это слишком медленный интернет. Нужно показать пользователю, что процесс запущен и скоро что-то произойдёт. Вторая проблема не так очевидна — это <em>слишком быстрый интернет</em>. Настолько быстрый, что пользователь может и не понять, а произошло ли что-то, отправились ли данные или возникла ошибка? Чтобы избавиться от этих проблем, нужно показать показать некий индикатор процесса, причём этот индикатор должен искусственно замедлить получение фидбэка от сервера. Он должен отображаться на странице некоторое время, чтобы пользователь успел понять, что запущен некий процесс.</p>
<p>Кто-то решает эту задачу так: показывает индикатор, ждёт определённое время и уже после этого отправляет запрос на сервер, а после получения ответа прячет индикатор. Так делать не правильно: в случае с медленным интернетом таймаут слишком сильно увеличивается; сам запрос мог бы запросто отработать за то время, пока показывается индикатор. Поэтому мы устроим небольшую гонку: что последним закончится выполняться — таймер индикатора или запрос на сервер — то и покажет фидбэк пользователю:</p>
<pre class="brush: js">
function sendSMSRequest(coupon) {
	coupon = $(coupon);
	var phone_code = coupon.find('.phone-code').val(),
		phone_num = coupon.find('.phone-number').val();

	coupon.addClass('coupon-preloader');
	// запускаем индикатор
	preloader.start();

	// два условия отображения фидбэка: закончился таймер и пришёл ответ от сервера
	var timer_reached = false,
		response_received = false;

	function checkStatus() {
		if (timer_reached &#038;&#038; response_received) {
			// останавливаем индикатор
			preloader.start();
			coupon.removeClass('coupon-preloader');

			// прячем форму с номером телефона
			switchSMSMode(coupon);
		}
	}

	// устраиваем гонку между таймером и запросом
	setTimeout(function() {
		timer_reached = true;
		checkStatus();
	}, 1000);

	$.get(sms_url, {
		id: coupon.attr('id'),
		action: 'send_sms',
		phone: phone_code + phone_num
	}, function(data) {
		response_received = true;
		checkStatus();
	}, 'json');
}
</pre>
<p>Вот так, двигаясь от простого к сложному, всего за несколько часов работы мы улучшили форму из целых двух полей, которую не стыдно показывать людям. Можно, конечно, улучшить ещё немного, явно сообщая пользователю о статусе отправки сообщения, но это уже другая история. Для сравнения: что было и что стало.</p>
<p><img src="http://chikuyonok.ru/u/2010/07/code-example.png" alt="code-example" title="code-example" width="451" height="1310" class="alignnone size-full wp-image-670" /></p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/Mb5mPmonMvY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/07/simple-things/feed/</wfw:commentRss>
		<slash:comments>88</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2010/07/simple-things/</feedburner:origLink></item>
		<item>
		<title>Content assist для элемента textarea</title>
		<link>http://feedproxy.google.com/~r/chikuyonok/vikk/~3/zNnYeiA1Jwk/</link>
		<comments>http://chikuyonok.ru/2010/06/content-assist-for-textarea/#comments</comments>
		<pubDate>Mon, 14 Jun 2010 18:09:24 +0000</pubDate>
		<dc:creator>Сергей Чикуенок</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[content assist]]></category>
		<category><![CDATA[textarea]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=655</guid>
		<description><![CDATA[Очередной эксперимент в поисках идеального средства для простого и быстрого написания кода. &#60;textarea&#62;, да и веб-технологии в целом привлекают меня своей простотой, доступностью и возможностью сделать так, как нужно мне. Есть Zen Coding, который позволяет быстро и удобно работать с большим объёмом XHTML-данных, но для пущего удобства не хватает автодополнения кода. Поиск в интернете не [...]]]></description>
			<content:encoded><![CDATA[<p>
	<em>Очередной эксперимент в поисках идеального средства для простого и быстрого написания кода. </em>
</p>
<p><code>&lt;textarea&gt;</code>, да и веб-технологии в целом привлекают меня своей простотой, доступностью и возможностью сделать так, как нужно мне. Есть <a href="http://code.google.com/p/zen-coding/">Zen Coding</a>, который позволяет быстро и удобно работать с большим объёмом XHTML-данных, но для пущего удобства не хватает автодополнения кода. Поиск в интернете не дал никаких вменяемых результатов, поэтому решил написать свой:</p>
<p><img src="http://chikuyonok.ru/u/2010/06/content-assist.png" alt="content-assist" title="content-assist" width="493" height="134" class="alignnone size-full wp-image-656" /></p>
<p>Проверить автодополнение можно на <a href="http://media.chikuyonok.ru/content-assist/">тестовой странице</a>. Попробуйте написать несколько английских слов, начинающихся на «a» (awesome, animal, amazing).  Должно работать везде, включая IE6.</p>
<p>Само по себе автодополнение, по большому счёту, практически бесполезно: непонятно, в какой момент какие данные нужно показывать. Поэтому решение представляет из себя несколько отдельных классов, каждый из которых можно переопределить, расширить и дополнить. За основу была взята архитектура автодополнения для Eclipse IDE, поэтому ряд классов и методов называются одинаково.</p>
<ul>
<li><code>TextViewer</code> — по сути, обёртка для <code>&lt;textarea&gt;</code>. Основное предназначение: дать удобный интерфейс для получения содержимого элемента. Один из основных методов — <code>getCharacterCoords(char_ix)</code>, возвращает координаты для определённого символа. Метод возвращает объект со свойствами <code>x</code> и <code>y</code> в пикселях, отсчёт идёт от левого верхнего угла <code>&lt;textarea&gt;</code> до <em>левого верхнего угла</em> нужного символа. Метод <code>getAbsoluteCharacterCoords(char_ix)</code> возвращает то же самое, только относительно <code>offsetParent</code>. Как не трудно догадаться, этот класс можно переписать для работы с content-editable блоком, и у вас будет автодополнение для более серьёзного редактора.</li>
<li><code>ContentAssistProcessor</code> — процессор, который высчитывает и предлагает список дополнений. По умолчанию ищет левую и правую границу английского слова (см. метод <code>isAllowedChar</code>), ищет префикс (от левой границы слова до каретки) и предлагет по этому префиксу список дополнений.</li>
<li><code>CompletionProposal</code> — предложение для автодополнения. Главный метод — <code>apply()</code>, который применяет текущее предложение к <code>TextViewer</code>-объекту.</li>
<li><code>ContentAssist</code> &#8211; основной класс. Связывает между собой <code>TextViewer</code> и <code>ContentAssistProcessor</code>, а также управляет всплывающим окном с подсказками.</li>
<li><code>tx_utils</code> — набор всяких DOM-утилит.</li>
</ul>
<p>Для добавления поддержки автодополнения для <code>&lt;textarea&gt;</code> нужно инициализировать все эти классы:</p>
<pre class="brush: js">
var viewer = new TextViewer(document.getElementById('my-textarea'));
var processor = new ContentAssistProcessor(['word1', 'word2', 'word3']);
var content_assist = new ContentAssist(viewer, processor);
</pre>
<p>Для удобства есть класс <code>BasicContentAssist</code>, который делает всё то же самое для простых случаев (автодополнение по фиксированному словарю):</p>
<pre class="brush: js">
new BasicContentAssist(document.getElementById('my-textarea'), ['word1', 'word2', 'word3']);
</pre>
<p>У класса <code>ContentAssistProcessor</code> есть метод <code>completitionProposalFactory</code>, который должен возвращать объект класса <code>CompletionProposal</code>. Внешний вид попапа с предложениями был нагло позаимствован из редактора <a href="http://macrabbit.com/espresso/">Espresso</a> и полностью управляется через CSS. Окошко с предложениями можно вызвать принудительно по Ctrl+Space (не работает в Опере и немного тупит в Файерфоксе) либо по Alt+Space (хвала разработчикам Оперы: это тоже не работает, по крайней мере на Маке) и заменить текущее слово (зависит от позиции каретки).</p>
<p>Как видите, все классы можно удобно перекрывать, расширять и наследовать для достижения необходимого функционала. Баги, предложения и улучшения принимаются в комментариях и на <a href="http://github.com/sergeche/tx-content-assist/">странице проекта</a>.</p>
<p><strong>UPD:</strong> немного дотюнил процессор, теперь он находит границы слов вне зависимости от языка (то есть можно отдавать словарь с русскими словами), а также немного улучшил поддержку больших словарей.</p>
<img src="http://feeds.feedburner.com/~r/chikuyonok/vikk/~4/zNnYeiA1Jwk" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/06/content-assist-for-textarea/feed/</wfw:commentRss>
		<slash:comments>34</slash:comments>
		<feedburner:origLink>http://chikuyonok.ru/2010/06/content-assist-for-textarea/</feedburner:origLink></item>
	</channel>
</rss>

