<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	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/"
	>

<channel>
	<title>CTAPbIu_MABP&#039;s BLOG &#187; plugin</title>
	<atom:link href="http://mabp.kiev.ua/tag/plugin/feed/" rel="self" type="application/rss+xml" />
	<link>http://mabp.kiev.ua</link>
	<description>энтузиазм = 1/опыт © Старый Мавр</description>
	<lastBuildDate>Sat, 12 May 2012 07:40:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2</generator>
		<item>
		<title>jQuery UI: fullscreen button for Dialog</title>
		<link>http://mabp.kiev.ua/2010/12/15/jquery-ui-fullscreen-button-for-dialog/</link>
		<comments>http://mabp.kiev.ua/2010/12/15/jquery-ui-fullscreen-button-for-dialog/#comments</comments>
		<pubDate>Wed, 15 Dec 2010 18:56:13 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[jqueryUI]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1316</guid>
		<description><![CDATA[Сегодня небольшой энчант для диалога - добавление кнопки развернуть на весь экран, хотя конечно не на весь экран а только на весь вьюпорт. Вот такой небольшой код добавляет новую кнопку в заголовок диалога. При первом нажатии на кнопку диалог разворачивается, при втором - сворачивается до первоначального размера. Ручной ресайз на кнопку никак не влияет. .ui-dialog [...]]]></description>
			<content:encoded><![CDATA[<p>Сегодня небольшой энчант для диалога - добавление кнопки развернуть на весь экран, хотя конечно не на весь экран а только на весь вьюпорт.</p>
<span id="more-1316"></span>

<link rel="stylesheet" type="text/css" media="screen" href="/content/css/jquery-ui-fullscreen-button-for-dialog.css"/>
<script type="text/javascript" src="/content/js/jquery-ui-fullscreen-button-for-dialog.js"></script>   

<p>Вот такой небольшой код добавляет новую кнопку в заголовок диалога. При первом нажатии на кнопку диалог разворачивается, при втором - сворачивается до первоначального размера. Ручной ресайз на кнопку никак не влияет. </p>
<pre><code class="css">
.ui-dialog .ui-dialog-titlebar-full {
	position: absolute;
	right: 2em;
	top: 50%;
	width: 19px;
	margin: -10px 0 0 0;
	padding: 1px;
	height: 18px;
}

.ui-dialog .ui-dialog-titlebar-full span {
	display: block;
	margin: 1px;
}

.ui-dialog .ui-dialog-titlebar-full:hover,
.ui-dialog .ui-dialog-titlebar-full:focus {
	padding: 0;
}
</code></pre>
<pre><code class="javascript">
(function(){
	var old = $.ui.dialog.prototype._create;
	$.ui.dialog.prototype._create = function(d){
		old.call(this, d);
		var self = this,
			options = self.options,
			oldHeight = options.height,
			oldWidth = options.width,
			uiDialogTitlebarFull = $('&lt;a href="#"&gt;&lt;/a&gt;')
				.addClass(
					'ui-dialog-titlebar-full ' +
					'ui-corner-all'
				)
				.attr('role', 'button')
				.hover(
					function() {
						uiDialogTitlebarFull.addClass('ui-state-hover');
					},
					function() {
						uiDialogTitlebarFull.removeClass('ui-state-hover');
					}
				)
				.toggle(
					function() {
						self._setOptions({
							height : window.innerHeight - 10,
							width : window.innerWidth - 30
						});
						self._position('center');
						return false;
					},
					function() {
						self._setOptions({
							height : oldHeight,
							width : oldWidth
						});
						self._position('center');
						return false;
					}
				)
				.focus(function() {
					uiDialogTitlebarFull.addClass('ui-state-focus');
				})
				.blur(function() {
					uiDialogTitlebarFull.removeClass('ui-state-focus');
				})
				.appendTo(self.uiDialogTitlebar),

			uiDialogTitlebarFullText = $('&lt;span&gt;&lt;/span&gt;')
				.addClass(
					'ui-icon ' +
					'ui-icon-newwin'
				)
				.text(options.fullText)
				.appendTo(uiDialogTitlebarFull)

	};
})();
</code></pre>

<p>Диалог принимает новую опцию options.fullText с текстом кнопки.</p>
<p>И еще для любителей пооптимизировать: _setOptions нельзя совместить с _position потому что позиционирование выполняется раньше ресайза, как ни странно.</p>



<div style="text-align:center;">
<input type="button" value="Тыцалка!" id="showDialog"/>
</div>
]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2010/12/15/jquery-ui-fullscreen-button-for-dialog/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Delayed DOM Manipulations</title>
		<link>http://mabp.kiev.ua/2010/08/31/delayed-dom-manipulations/</link>
		<comments>http://mabp.kiev.ua/2010/08/31/delayed-dom-manipulations/#comments</comments>
		<pubDate>Tue, 31 Aug 2010 13:02:53 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1242</guid>
		<description><![CDATA[Чебурашка решил начать разговор издалека. - Солнышко светит, травка зеленеет! - сказал он. - А нам вот так нужны гвозди! Не дадите немножко? - Это не травка зеленеет, - ответил кладовщик.- Это краску пролили. А гвоздей нет. Каждый ящик на учете. Пожалуй эта цитата лучше всего характеризует то, что я сегодня вам приготовил. А подготовка [...]]]></description>
			<content:encoded><![CDATA[<blockquote>
Чебурашка решил начать разговор издалека.
- Солнышко светит, травка зеленеет! - сказал он. - А нам вот так нужны гвозди! Не дадите немножко?
- Это не травка зеленеет, - ответил кладовщик.- Это краску пролили. А гвоздей нет. Каждый ящик на учете.
</blockquote>

<p>Пожалуй эта цитата лучше всего характеризует то, что я сегодня вам приготовил. А подготовка материала как показала практика дело очень интимное. Я пару раз просил читателей сказать о чем они хотели бы почитать, но ни разу не написал, ни на одну из предложенных тем. Зато могу бросить все и начать писать о том, что мне пришло в голову как шальная мысль. Так и в этот раз, просматривая очередную заметку "100500 типс энд трикс для жуквери" я решил, что меня утомил один из этих типсов.</p>
<span id="more-1242"></span>
<p>Самая простая вещь которая может быть это вынос за пределы цикла и отложенная работа с DOM деревом.</p>

<pre><code class="javascript">
var lis = [1,2,3,4,5,6];
$(lis).each(function(i, e){
	$("ul#my").append("&lt;li&gt;"+e+"&lt;/li&gt;")
});
</code></pre>

<p>Превращаеться в</p>

<pre><code class="javascript">
var lis = [1,2,3,4,5,6], html = "";
$(lis).each(function(i, e){
	html = "&lt;li&gt;"+e+"&lt;/li&gt;";
});
$("ul#my").html(html);
</code></pre>

<p>Кароче самая тривиальная и банальная вещь из существующих. Но писать append внутри цикла как-то логичнее и приятнее ну, по крайней мере, для меня. И решил я захачить жуквери так что бы можно было писать как хочеться а работало как надо. Вот тут собственно и уместно предисловие, пишем append внутри each и каждая миллисекунда на счету.</p>

<p>После дня ковыряния у меня родился вот такой код:</p>

<pre><code class="javascript">
(function($){

/*
 * @author CTAPbIu_MABP
 * @email CTAPbIuMABP@gmail.com
 * @link http://mabp.kiev.ua/2010/08/31/delayed-dom-manipulations/
 * @license GPL
 */

	var data = {},	// тут список всех элементов и отложенных операций над ними
		isOuterLoop = true, // определяет самый внешний вложенный цикл
		supportedFunctions = [ // список переопределяемых методов
			"append",
			"prepend",
			"before",
			"after"
		];
	
	/**
	 * Складывает аргументы оригинальных методов в data
	 * @param funcName {String} имя оригинального метода 
	 * @param args {array-like object} аргументы оригинального метода
	 * @return {jQuery} возвращяет jQuery для продолжения цепочки
	 */
	function delay (funcName, args){
		var i = $.inArray(this, data[funcName].elt),
			args = $(args).toArray(); // приводим аргументы к массиву
		if (i &gt; -1){
			data[funcName].tmp[i] = data[funcName].tmp[i].concat(args);
		}else{
			data[funcName].elt.push(this);
			data[funcName].tmp.push(args)
		}
		return this;
	}
	
	// создаем место для хранения данных
	// и обертки ко всем поддерживаемым методам 
	$(supportedFunctions).each(function(i, funcName){
		data[funcName] = {tmp : [], elt : []};
		$.fn[funcName+"Delayed"] = function(){
			return delay.call(this, funcName, arguments)
		}
	});
	
	// создаем обертку для each
	$.fn.eachDelayed = function(){
		var ret, isOuter = isOuterLoop;
		isOuterLoop = false;
		ret = $.fn.each.apply(this, arguments); // выполняем оригинальный each
		if (isOuter){ // после самого вернего вложенного цикла
			$.each(data, function(funcName, funcParam){ // обходим все данные
				$(funcParam.elt).each(function(i){ // и выполняем все оригинальные методы
					$.fn[funcName].apply(funcParam.elt[i], funcParam.tmp[i]);
				});
				data[funcName] = {tmp : [], elt : []}; // обнуляем данные 
			})
			isOuterLoop = true;
		}
		return ret;
	}
	
})(jQuery);
</code></pre>

<p>Ну что ж погнали, посмотрим что он делает. А делает он обертку для методов из массива supportedFunctions, грубо говоря AOP. На самом деле переопределенные методы не выполняют вставку в DOM дерево, а только вызывают функцию delayed которая складывает аргументы оригинальных методов в массив data. После конца цикла все накопленные данные проверяются и выполняются все отложенные методы.</p>

<p>Насколько я проверил это все прекрасно работает с несколькими вставками одновременно, во вложенных циклах и несколькими аргументами, а так же со всем этим вместе.</p>

<p>Теперь я думаю надо объяснить на чем основан хак. Так сложилось что все четыре метода из массива supportedFunctions используют метод domManip (line 4263) -> buildFragment (line 4355) -> clean (line 4417) передавая все свои аргументы, для построения DocumentFragment одним махом. Вот этим-то я и воспользовался, в самом внешнем цикле я один раз вызываю оригинальный метод с кучей параметров.</p>

<p>Еще два слова о том как пользоваться этим чудом</p>

<pre><code class="javascript">
$(document).ready(function(){
	var lis = [1,2,3,4,5,6],
            ul1 = $("ul#my1"),
            ul2 = $("ul#my2");
	
	$(lis).eachDelayed(function(i, e){
		ul1.beforeDelayed("&lt;div&gt;"+e+"&lt;/div&gt;");
		ul1.appendDelayed("&lt;li&gt;"+e+"&lt;/li&gt;");
		ul2.prependDelayed("&lt;li&gt;1+"+e+"&lt;/li&gt;", "&lt;li&gt;e+"+e+"&lt;/li&gt;");
		$(lis).eachDelayed(function(i, e){
			ul2.appendDelayed("&lt;li&gt;2+"+e+"&lt;/li&gt;");
		});
	});
	
});
</code></pre>

<p>Результат выполнения кода приводить не буду, там ничего красивого, зато работает. Все методы before, append и prepend выполняются по одному разу при окончании внешнего цикла.</p>

<p>Хотел еще сделать поддержку для всяких wrap, но что-то там как-то грустно все, они не используют domManip.</p>

<p>Скачать можно тут <a href="http://mabp.kiev.ua/content/source/jquery.delayed.js">DOWNLOAD LINK</a></p>
]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2010/08/31/delayed-dom-manipulations/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Eventify</title>
		<link>http://mabp.kiev.ua/2010/02/07/eventify/</link>
		<comments>http://mabp.kiev.ua/2010/02/07/eventify/#comments</comments>
		<pubDate>Sun, 07 Feb 2010 12:10:33 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1200</guid>
		<description><![CDATA[Не давали мне покоя рассуждения начатые в заметке Продвинутая теория создания плагинов и решил я написать плагин, который бы переводил все методы переданного объекта в события. Конечно же сам объект не должен быть нарушен, то есть должен иметь возможность вызывать свои методы напрямую, более того при любом вызове любого метода должны отрабатывать еще два события [...]]]></description>
			<content:encoded><![CDATA[<p>Не давали мне покоя рассуждения начатые в заметке <a href="http://mabp.kiev.ua/2009/12/08/advanced-theory-of-plugin-creation/">Продвинутая теория создания плагинов</a> и решил я написать плагин, который бы переводил все методы переданного объекта в события. Конечно же сам объект не должен быть нарушен, то есть должен иметь возможность вызывать свои методы напрямую, более того при любом вызове любого метода должны отрабатывать еще два события onBeforeMethod и onAfterMethod.</p>
<span id="more-1200"></span>
<p>Скорее всего сейчас вас мучает вопрос на кой хер это вообще надо. Это глубоко эзотерические размышления над архитектурой, которые простым людям нах не нужны. Но я надеюсь что в конце концов они приведут к какой-то универсальной форме создания плагинов не нарушая идеологию jQuery но позволяя передавать данные и получать результаты в плагин простым и красивым способом.</p>

<p>Итак что же вышло?</p>

<pre><code class="javascript">
(function($) {

    var Plugin = function (elt, obj) {
        var element = $(elt);
        for (var prop in obj){
            (function(self,obj,prop){
                if ($.isFunction(obj[prop])){
                    self[prop] = function(){
                        var result, eventName = prop.charAt(0).toUpperCase() + prop.substr(1);
                        element.trigger("onBefore" + eventName, [self, arguments]);
                        result = obj[prop].apply(self, arguments);
                        element.trigger("onAfter" + eventName, [self, arguments, result]);
                    };
                    element.bind(prop, $.proxy(function(){
                        self[prop].apply(self, Array.prototype.slice.call(arguments, 1));
                    }, self));
                }else{
                    self[prop] = obj[prop];
                }
            })(this,obj,prop);
        }
    };

    $.fn.plugin = function(obj) {
        this.each(function() {
            new Plugin(this, obj);
        });
        return this;
    };

})(jQuery);
</code></pre>

<p>Использовать можно примерно так</p>

<pre><code class="javascript">
$(document).ready(function(){
    $("#qwerty").bind("onBeforeMethod1 onAfterMethod1 onBeforeMethod2 onAfterMethod2", function(e){
        console.log(e.type, arguments);
    }).plugin({
        method1 : function (){
            console.log("method1",this);
            this.method2(1,2,3);
        },
        method2 : function (){
            console.log("method2",this);
            console.log("method2",arguments);
            return arguments;
        }
    }).trigger("method1").trigger("method2",[1,2,3])
});
</code></pre>]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2010/02/07/eventify/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>jqGrid</title>
		<link>http://mabp.kiev.ua/2010/01/31/jqgrid/</link>
		<comments>http://mabp.kiev.ua/2010/01/31/jqgrid/#comments</comments>
		<pubDate>Sun, 31 Jan 2010 17:53:05 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[grid]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1194</guid>
		<description><![CDATA[Хочу продолжить благое дело начатое TRAHOMOTO. И хотя с одной стороны хоть я с ним и не согласен в некоторых моментах, а с другой стороны не буду рассказывать про бэкэнд, моя статья должна стать хорошим дополнением к его статье. Начну я с того, что закидаю вас ссылками на блог для скачивания и wiki для установки [...]]]></description>
			<content:encoded><![CDATA[<p>Хочу продолжить благое дело <a href="http://www.linkexchanger.su/2010/118.html" rel="nofollow external">начатое TRAHOMOTO</a>. И хотя с одной стороны хоть я с ним и не согласен в некоторых моментах, а с другой стороны не буду рассказывать про бэкэнд, моя статья должна стать хорошим дополнением к его статье.</p>

<span id="more-1194"></span>

<link rel="stylesheet" type="text/css" media="screen" href="/content/source/jqgrid/css/ui.jqgrid.css" />
<script src="/content/source/jqgrid/js/i18n/grid.locale-ru.js" type="text/javascript"></script>
<script src="/content/source/jqgrid/js/jquery.jqGrid.min.js" type="text/javascript"></script>
<script src="/content/js/jqgrid-plugin.js" type="text/javascript"></script>

<p>Начну я с того, что закидаю вас ссылками на <a href="http://www.trirand.com/blog/?page_id=6" rel="nofollow external">блог</a> для скачивания и wiki для <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:how_to_install" rel="nofollow external">установки</a> плагина, а потом покажу как это все сконфигурировать.</p>

<p>Итак для построения таблицы нужны данные, я придумал вот такую JSON рыбу, она статичная так что вам ничего добавить не получиться :P</p>
<pre><code class="javascript">
{"total":1,"records":7,"page":1,
	"rows":[
		{"id":1,"cell":[1,"Москва",55.755786,37.617633,"Yes",["A","B","C"]]},
		{"id":2,"cell":[2,"Киев",50.440951,30.527181,"Yes",["A","B"]]},
		{"id":3,"cell":[3,"Минск",53.905117,27.561184,"Yes",["A","C"]]},
		{"id":4,"cell":[4,"Сан Франциско",37.77493,-122.419416,"No",["B","C"]]},
		{"id":5,"cell":[5,"Нью Йорк",40.714269,-74.005973,"No",["A"]]},
		{"id":6,"cell":[6,"Лондон",51.51333,-0.088947,"Yes",["B"]]},
		{"id":7,"cell":[7,"Наироби",0.878872,37.924805,"Yes",["C"]]}
	]
}
</code></pre>
<p>Тут каждая строка описывает имя города, широту, долготу, флажок является ли город столицей и какие-то буквы имитирующие список.</p>
<p>Есть еще словарик</p>
<pre><code class="javascript">
[
{value:"a",name:"A"},
{value:"b",name:"B"},
{value:"c",name:"C"},
{value:"d",name:"D"},
{value:"e",name:"E"}
]
</code></pre>
<p>и ответ о успешном добавлении</p>
<pre><code class="javascript">
{"message":"","id":8}
</code></pre>

<p>Ну и перед самым интересным еще HTML разметка, она совсем простая и требует задания всего одной таблицы и одного дива.</p>

<pre><code class="html">
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;Example from CTAPbIu_MABP's BLOG&lt;/title&gt;

&lt;link rel="stylesheet" type="text/css" media="screen" href="ui-lightness/jquery-ui-1.7.2.custom.css" /&gt;
&lt;link rel="stylesheet" type="text/css" media="screen" href="jqGrid/css/ui.jqgrid.css" /&gt;

 
&lt;script src="jquery-1.4.min.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;-- 
я подключил jquery-ui потому что он и так есть у меня на сайте
его можно не подключать а использовать плагины jqModal и jqDnR
которые можно скачать вместе с jqGrid, подключать и то и то НЕЛЬЗЯ
 --&gt;
&lt;script src="jquery-ui-1.7.2.custom.min.js" type="text/javascript"&gt;&lt;/script&gt;
    
&lt;script src="jqGrid/js/i18n/grid.locale-ru.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="jqGrid/jquery.jqGrid.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;script src="setup.js" type="text/javascript"&gt;&lt;/script&gt;

&lt;/head&gt;
&lt;body&gt;

&lt;table id="table"&gt;&lt;/table&gt;
&lt;div id="pager"&gt;&lt;/div&gt;

&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>Теперь сам код для создания таблицы, надеюсь слишком просто не будет, все пояснения в коде:</p>

<pre><code class="javascript">
jQuery(document).ready(function($){

	/**
	 * функция для построения select'a из моих данных
	 * тут даже тип ответа нельзя задать
	 * надо все парсить и клеить самому
	 * @param response ответ ajax'a
	 */
	function build(response) {
		// а вот и статья про шаблонизатор пригодилась
		// http://mabp.kiev.ua/2010/01/16/javascript-template-engine/
		var html = '', template = '&lt;option value="[value]"&gt;[name]&lt;/option&gt;';
		$(eval("("+response.responseText+")")).each(function(i, val){
			html += template.replace(/(\[([^\[\]]+)\])/g, function($0, $1, $2){
			   return val[$2] || "";
			});
		});
		return '&lt;select&gt;' + html + '&lt;/select&gt;';
	}

	/**
	 * Эта функция формирует пост запрос и для массивов сформирует
	 * переменную вида letters=a,b,c,d,e
	 * я переделываю это все в letters[]=a,letters[]=b итд
	 * заодно экранирую всякую ересь вроде &#038;<>
	 * @param data массив всех данных формы
	 */
	function serialize(data){
		var str = [];
		if(data.letters)
			data.letters = data.letters.split(",");
		for (var i in data) {
			if ($.isArray(data[i])) {
				for (var j in data[i]) {
					str.push(i + "[]=" + encodeURI(data[i][j]));
				}
			} else {
				str.push(i + "=" + encodeURI(data[i]));
			}
		}
		return str.join("&#038;");
	}


	$("#table").jqGrid({
		autowidth:true,
		colNames:["ID","Name","Latitude","Longitude","Capital","Letters"],
		colModel:[
			// запрещаю редактировать id и искать по нему, по остальным - можно
			{name:"id",			index:"id",				width:40,	editable:false, search:false},
			// тип поля ввода textarea/text/checkbox/select а так же все остальные типы поля input
			{name:"name",		index:"name",			width:100,	editable: true,	edittype:"textarea"},
			// можно так же указать maxlength для инпутов и multiple для селектов
			{name:"latitude",	index:"title",			width:100,	editable: true,	edittype:"text",editoptions:{maxlength:16}},
			{name:"longitude",	index:"description",	width:100,	editable: true,	edittype:"text",editoptions:{maxlength:16}},
			// значения для чекбоксов задаються вот так
			{name:"capital",	index:"capital",		width:100,	editable: true,	edittype:"checkbox",editoptions:{value:"Yes:No"}},
			{name:"letters",	index:"letters",		width:100,	editable: true,
				// для селектов тоже можно задать формат инлайн, но я выбрал кастомный формат
				edittype:"select", editoptions:{multiple:true,dataUrl:"/content/polygon/jqgrid.backend-dict.json",buildSelect:build},
				// и для поиска можно сделать селект
				stype:"select", searchoptions:{dataUrl:"/content/polygon/jqgrid.backend-dict.json", buildSelect:build}
				}
		],
		pager:"#pager",
		datatype:"json",
		viewrecords:true,
		url:"/content/polygon/jqgrid.backend-data.json",
		ajaxGridOptions:{ // <-- передаеться прямо в $.ajax
			type : "GET",
			dataType : "json" // <-- странно но эта опция не перекрывает параметр datatype
		},
		/**
		 * Если пользователь запросил страницу номер которой больше чем максимальное 
		 * количество страниц, или меньше чем 1, эта функция вернет его обратно 
		 * в позволенные рамки
		 */
		onPaging: function(pgButton) {
			var curPage = grid.jqGrid('getGridParam','page');
			var lastPage = grid.jqGrid('getGridParam','lastpage');
				if (curPage < 1) {
			this.p.page = 1;
			}
			if (curPage > lastPage) {
				this.p.page = lastPage;
			}
		}
	}).jqGrid("navGrid","#pager",
		{}, // показать/скрыть кнопки добавить/редактировать/удалить/поиск/обновить
		{  // опции для редактирования
			modal:true, // диалог модальный
			url:"/content/polygon/jqgrid.backend-save.json", // бэкэнд
			closeAfterEdit:true, // закрыть диплог после редактирования
			reloadAfterSubmit:false, // перезагрузить таблицу после добавления
			mtype:"GET", // тип запроса, перекрывает все предыдущие настройки
			/**
			 * с помощью этой функции можно показать ошибки заполнения формы
			 * а так же вставить новый ряд с id который пришел с сервера
			 * error - {"message":"Epic Fail!"}
			 * succes - {"message":""}
			 * @param response
			 */
			afterSubmit:function(response){
				var json = eval("("+response.responseText+")");
				return [!!!json.message,json.message];
			},
			/**
			 * тут я делаю магию: я не хочу что бы можно было редактировать поле letters
			 * поэтому я его дизейблю, но поскольку форма еще не есть частью документа
			 * нельзя сделать выборку $("select")
			 * @param form форма
			 */
			afterShowForm:function(form){
				$("select",form).attr({disabled:"disabled"})
			},
			serializeEditData : serialize // описание функции в начале
		},
		{  // опции для добавления, все так же как и прошлый раз
			modal:true,
			url:"/content/polygon/jqgrid.backend-save.json",
			closeAfterAdd:true, // закрыть диплог после добавления
			reloadAfterSubmit:false,
			mtype:"GET",
			afterSubmit:function(response){
				var json = eval("("+response.responseText+")");
				return [!!!json.message,json.message,json.id];
			},
			/**
			 * Отменяем магию которую сотворила эта же функция при редактировании
			 * @param form форма
			 */
			afterShowForm:function(form){
				$("select",form).removeAttr("disabled");
			},
			serializeEditData : serialize
		},
		{ // опции для удаления
			modal:true,
			url:"/content/polygon/jqgrid.backend-save.json",
			reloadAfterSubmit:false,
			mtype:"GET",
			afterSubmit:function(response){
				var json = eval("("+response.responseText+")");
				return [!!!json.message,json.message];
			},
			serializeDelData : serialize
		},
		{ // опции поиска
			sopt:['eq', 'ne', 'in', 'cn'], // ограничиваю критерии
			multipleSearch:true, // можно искать по нескольким полям
			closeAfterSearch:true // закрыть после поиска
		}
	);
});
</code></pre>

<p>Играемся!</p>

<table id="table2"></table>
<div id="pager2"></div>

<p>Кроме того что не правильно позиционируется диаложек с предупреждением при редактировании на выбранной строки я ошибок не видел. Если что обращайтесь с вопросам, помогу если смогу.</p>
]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2010/01/31/jqgrid/feed/</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>jMaps</title>
		<link>http://mabp.kiev.ua/2010/01/19/jmaps/</link>
		<comments>http://mabp.kiev.ua/2010/01/19/jmaps/#comments</comments>
		<pubDate>Tue, 19 Jan 2010 00:04:36 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1180</guid>
		<description><![CDATA[Я вообще стараюсь не писать о том что морально устарело или больше не поддерживается, но сегодня именно такой случай. Я хочу рассказать вам об одном очень хорошем плагине для работы с гуглокартами под названием jMaps. Его исходники доступны на жыдхабе, но к сожелению их никто не обновлял с 17 сентября 2009 года. Один из авторов [...]]]></description>
			<content:encoded><![CDATA[<p>Я вообще стараюсь не писать о том что морально устарело или больше не поддерживается, но сегодня именно такой случай. Я хочу рассказать вам об одном очень хорошем плагине для работы с гуглокартами под названием jMaps. Его исходники доступны на <a href="http://wiki.github.com/digitalspaghetti/jmaps" rel="nofollow external">жыдхабе</a>, но к сожелению их никто не обновлял с 17 сентября 2009 года. Один из авторов <a href="http://digitalspaghetti.me.uk/" rel="nofollow external">digitalspaghetti</a> тоже пропал с сентября. В общем жыдхаб это единственная живая ссылка на проект, не считая репозитория плагинов <a href="http://plugins.jquery.com/project/jmaps" rel="nofollow external">jquery</a> :( Если кто-то располагает хоть какими-то знаниями относительно более свежих версий очень прошу в каменты. А пока я буду рассказывать о том чего я накопал в коде.</p>
<span id="more-1180"></span>
<p>А накопал я много чего интересного. Оказывается, сам по себе плагин составляет три с половиной строки кода вокруг объекта Mapifies</p>
<pre><code class="javascript">
(function($){
	$.fn.jmap = function(method, options, callback) {
		return this.each(function(){
			if (method == 'init' &#038;& typeof options == 'undefined') {
				new Mapifies.Initialise(this, {}, null);
			} else if (method == 'init' &#038;& typeof options == 'object') {
				new Mapifies.Initialise(this, options, callback);
			} else if (method == 'init' &#038;& typeof options == 'function') {
				new Mapifies.Initialise(this, {}, options);
			} else if (typeof method == 'object' || method == null) {
				new Mapifies.Initialise(this, method, options);
			} else {
				try {
					new Mapifies[method](this, options, callback);
				} catch(err) {
					throw Error('Mapifies Function Does Not Exist');
				}
			}
		});
	}
})(jQuery);
</code></pre>
<p>Что сказать, хорошо сделанная обертка, кроме одной меленькой детали. Все методы объекта Mapifies за редким исключением получают три параметра element (карта), options (опции), callback (функция), а возвращает результат работы callback(map). И вот как вы думаете как вы получите результат работы хоть какой-нибудь функции если обертка не предполагает возврата данных?!</p>
<p>Уже только по этому я решил отказаться от использования обертки и дальше работал с Mapifies напрямую. Документация по плагину крайне бедная, но код очень хорошо документирован, поэтому не составляет труда в нем разобраться. Продемонстрирую несколько простейших функций:</p>
<p>Создание резиновой карты, растягивается за нижний правый угол. Осторожно если сильно потянуть вправо то потом ее обратно уже не сжать, зато в любой момент можно растянуть вниз на практически любую длину.</p>
<pre><code class="javascript">
jQuery(document).ready(function($){
    var $map = $("#map1"),
    GMap = Mapifies.Initialise(
         $map.get(0),  {
            // описание всех параметров смотрите в коде плагина
            // http://github.com/digitalspaghetti/jmaps/blob/master/dist/jquery.jmap.js#L100
            language: 'en',
            mapType: G_HYBRID_MAP,
            mapCenter: [50.440951,30.527181],
            mapZoom: 10,
            mapControl: 'large',
            mapEnableDragging: true,
            mapEnableScrollZoom: true,
            mapShowjMapsIcon: false
        }, function(thisMap){
            return thisMap;
    });

    $map.resizable({
        stop: function(event, ui) {
            Mapifies.CheckResize($map);
        }
    });
});
</code></pre>
<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAhZYAzYrXPwUDjxsHSuaBzRROkIKc1AeUaEtpre4S8pXwOdxbIxQ7FwgQCh5LqtMSHrBMiLBIe5m16A"></script>
<script type="text/javascript" src="/content/source/jquery.jmap.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($){
    var $map = $("#map1"),
    GMap = Mapifies.Initialise(
         $map.get(0),  {
            language: 'en',
            mapType: G_HYBRID_MAP,
            mapCenter: [50.440951,30.527181],
            mapZoom: 10,
            mapControl: 'large',
            mapEnableDragging: true,
            mapEnableScrollZoom: true,
            mapShowjMapsIcon: false
        }, function(thisMap){
            return thisMap;
    });

    $map.resizable({
        stop: function(event, ui) {
            Mapifies.CheckResize($map);
        }
    });
});
</script>
<div id="map1" style="height:400px;width:600px;"></div>
<p>Теперь покажу как добавить маркер</p>
<pre><code class="javascript">
jQuery(document).ready(function($){
    var $map = $("#map2");
    Mapifies.Initialise(
         $map.get(0),  {
            // описание всех параметров смотрите в коде плагина
            // http://github.com/digitalspaghetti/jmaps/blob/master/dist/jquery.jmap.js#L100
            language: 'en',
            mapType: G_HYBRID_MAP,
            mapCenter: [50.440951,30.527181],
            mapZoom: 10,
            mapControl: 'large',
            mapEnableDragging: true,
            mapEnableScrollZoom: true,
            mapShowjMapsIcon: false
    });
    Mapifies.CreateMarkerManager($map,{markerManager: 'MarkerManager'});
    Mapifies.AddMarker($map,{
        // описание всех параметров смотрите в коде плагина
        // http://github.com/digitalspaghetti/jmaps/blob/master/dist/jquery.jmap.js#L704
        pointTitle : 'Tooltip',
        pointLatLng: [50.440951,30.527181],
        pointHTML: '&lt;strong&gt;description&lt;/strong&gt;',
        pointOpenHTMLEvent: 'click',
        pointIsDraggable: true,
        pointMinZoom: 0,
        pointMaxZoom: 17,
        pointIcon:Mapifies.createIcon({
            // описание картинок смотрите в моей прошлой статье
            // http://mabp.kiev.ua/2010/01/12/google-map-markers/
            iconImage: 'http://maps.google.com/mapfiles/arrow.png',
            iconShadow: 'http://maps.google.com/mapfiles/arrowshadow.png',
            iconSize: new GSize(39,34),
            iconShadowSize: new GSize(39,34),
            iconPrintImage: 'http://maps.google.com/mapfiles/arrowie.png',
            iconMozPrintImage: 'http://maps.google.com/mapfiles/arrowff.png',
            iconTransparent: 'http://maps.google.com/mapfiles/arrowtransparent.png'
        })
    });
});
</code></pre>
<script type="text/javascript" src="http://gmaps-utility-library.googlecode.com/svn/trunk/markermanager/1.1/src/markermanager.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($){
    var $map = $("#map2");
    Mapifies.Initialise(
         $map.get(0),  {
            language: 'en',
            mapType: G_HYBRID_MAP,
            mapCenter: [50.440951,30.527181],
            mapZoom: 10,
            mapControl: 'large',
            mapEnableDragging: true,
            mapEnableScrollZoom: true,
            mapShowjMapsIcon: false
    });
    Mapifies.CreateMarkerManager($map,{markerManager: 'MarkerManager'});
    Mapifies.AddMarker($map,{
        pointTitle : 'Tooltip',
        pointLatLng: [50.440951,30.527181],
        pointHTML: '<strong>description</strong>',
        pointOpenHTMLEvent: 'click',
        pointIsDraggable: true,
        pointMinZoom: 0,
        pointMaxZoom: 17,
        pointIcon:Mapifies.createIcon({
            iconImage: 'http://maps.google.com/mapfiles/arrow.png',
            iconShadow: 'http://maps.google.com/mapfiles/arrowshadow.png',
            iconSize: new GSize(39,34),
            iconShadowSize: new GSize(39,34),
            iconPrintImage: 'http://maps.google.com/mapfiles/arrowie.png',
            iconMozPrintImage: 'http://maps.google.com/mapfiles/arrowff.png',
            iconTransparent: 'http://maps.google.com/mapfiles/arrowtransparent.png'
        })
    });
    $("#remove1").click(function(){
        var manager = Mapifies.MapObjects.Get($map).MarkerManager;
        Mapifies.RemoveMarker($map,manager.getMarker(50.440951,30.527181,1))
    });
    $("#remove2").click(function(){
        var manager =  Mapifies.MapObjects.Get($map).MarkerManager;
        manager.removeMarker(manager.getMarker(50.440951,30.527181,1));
    });
});
</script>
<div id="map2" style="height:400px;width:600px;"></div>
<p>С этим примером тоже почти все хорошо кроме двух вещей. </p>
<p>Первая это нельзя заменить крест под маркером (во время перетаскивания). Он немного не по размеру этому маркеру (чуть чуть левее чем надо), но чтобы заменить его нужно отказаться от метода createIcon и создать маркер вручную.</p>
<p>Вторая... Если посмотреть код страницы видно что я подключил скрипт markermanager.js, он помогает при работе с маркерами, довольно неплохая вещь. Плагин умеет его использовать и если он подключен создает маркеры с его помощью. Но создавать создает а удалять забыват ;( Тоесть чтобы удалить маркер нужно вместо </p>
<pre><code class="javascript">
    $("#remove1").click(function(){
        var manager = Mapifies.MapObjects.Get($map).MarkerManager;
        Mapifies.RemoveMarker($map,manager.getMarker(50.440951,30.527181,1))
    });
</code></pre>
<input type="button" id="remove1" value="удалить первым способом"/>
<p>Написать</p>
<pre><code class="javascript">
    $("#remove2").click(function(){
        var manager =  Mapifies.MapObjects.Get($map).MarkerManager;
        manager.removeMarker(manager.getMarker(50.440951,30.527181,1));
    });
</code></pre>
<input type="button" id="remove2" value="удалить вторым способом"/>
<p>Ага, вот сейчас вы нажали первую кнопку и подумали что я вас обманываю, нет попробуйте позумить, маркер никуда не пропал. Зато если нажать на вторую кнопку маркер пропадает начисто.</p>

<p>Я думаю на сегодня все. кто знает что-то об авторе, свежих версиях, документации и тд и тп пишите в каменты.</p>]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2010/01/19/jmaps/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Paint</title>
		<link>http://mabp.kiev.ua/2009/12/08/paint/</link>
		<comments>http://mabp.kiev.ua/2009/12/08/paint/#comments</comments>
		<pubDate>Tue, 08 Dec 2009 00:26:55 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[2d]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[Raphaël]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[UI plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1147</guid>
		<description><![CDATA[Научившись работать с Raphaël и пользуясь познаниями из прошлой статьи плагин нужно соединить с colorpicker'ом и datagrid'ом. Долго не гугля я выбрал два кажущиеся мне нормальные плагина и похоже не ошибся. Интеграция прошла быстро и безболезненно :) Задача: есть некая модель данных, которая должна отображаться в двух видах: в табличке в виде данных и на [...]]]></description>
			<content:encoded><![CDATA[<p>Научившись работать с <a href="http://mabp.kiev.ua/2009/12/08/raphael/">Raphaël</a> и пользуясь познаниями из <a href="http://mabp.kiev.ua/2009/12/08/advanced-theory-of-plugin-creation/">прошлой статьи</a> плагин нужно соединить с <a href="http://www.eyecon.ro/colorpicker/" rel="nofollow external">colorpicker'ом</a> и <a href="http://www.trirand.com/blog/" rel="nofollow external">datagrid'ом</a>. Долго не гугля я выбрал два кажущиеся мне нормальные плагина и похоже не ошибся. Интеграция прошла быстро и безболезненно :)</p>
<span id="more-1147"></span>
<p>Задача: есть некая модель данных, которая должна отображаться в двух видах: в табличке в виде данных и на картинке ввиде линии. Линии можно выделять при этом реагирует табличка подсвечивая выбранный ряд, соответственно и наоборот если выбрать ряд в табличке надо подсветить линию на рисунке, линии можно удалять, удаляя данные и из таблички, и добавлять добавляя пустой ряд в табличку. Так вроде все описал, на всякий случай еще приделал к этому цветовую палитру, еще наверное надо будет толщину линии выбирать разрешить, но это просто.</p>

<p>Модель данных взята от фонаря три шага налево...</p>

<pre><code class="javascript">
var model = [
{id : 0, amount : 100, units : "meters", value : 13, color : "#00f", path : "M 250 73 L 250 73"},
{id : 1, amount : 200, units : "meters", value : 22, color : "#0f0", path : "M 327 67 L 327 67"},
{id : 2, amount : 300, units : "meters", value : 36, color : "#f00", path : "M 185 172 L 185"},
{id : 3, amount : 400, units : "meters", value : 18, color : "#00f", path : "M 216 231 L 216"},
{id : 4, amount : 150, units : "meters", value : 23, color : "#f00", path : "M 274 171 L 274 171"},
{id : 5, amount : 360, units : "liters", value : 36, color : "#00f", path : "M 308 176 L 308 176"},
{id : 6, amount : 170, units : "liters", value : 12, color : "#0f0", path : "M 358 113 L 358 113"},
{id : 7, amount : 180, units : "liters", value : 24, color : "#f00", path : "M 201 109 L 201 109"},
{id : 8, amount : 292, units : "liters", value : 37, color : "#00f", path : "M 228 114 L 228 114"},
{id : 9, amount : 182, units : "kgrams", value : 43, color : "#0f0", path : "M 357 245 L 357 245"},
{id : 10,amount : 273, units : "kgrams", value : 53, color : "#f00", path : "M 346 172 L 346 172"},
{id : 11,amount : 164, units : "kgrams", value : 61, color : "#00f", path : "M 386 179 L 386 179"},
{id : 12,amount : 254, units : "kgrams", value : 27, color : "#0f0", path : "M 218 178 L 218 178"}
];
</code></pre>

<p>Загадочная надпись M 250 73 L 250 73 это начало линии в <a href="http://www.w3.org/TR/SVG/paths.html#PathData" rel="nofollow external">формате SVG</a>, дословно она означает Move To 250 px OX 73 px OY Line To 250 px OX 73 px OY, если в конце написать "z" то начало и конец линии замкнуться. В примере я укоротил запись в исходниках все по честному.</p>

<p>Теперь это все надо отрендерить, для этого я написал следующий код, который вы можете использовать как есть, но использовать пока не советую из-за крайней сырости, но за найденные баги буду очень благодарен</p>

<pre><code class="javascript">
(function($) {

    /**
     * author: CTAPbIu_MABP
     * email: ctapbiumabp@gmail.com
     * site: http://mabp.kiev.ua/
     * last update: 08.12.2009
     * version: 0.2
     */

    var Paint = function (node, options) {
        $.extend(this, options);
        this.init(node);
    };


    Paint.prototype = {

        // private
        raphael : null,
        isStarted : false,
        node : null,
        offset : null,

        // public
        color : "#f00",
        defaultColor : "#000",

        // callbacks
        onInit : function() {
        },
        onLineClick : function() {
        },
        onLineDblClick : function() {
        },
        onLineDelete : function() {
        },
        onStart : function() {
        },
        onStop : function() {
        },
        onMove : function() {
        },

        init : function (raw_img) {

            var img = $(raw_img).hide(),
                    self = this,
                    width = img.width(),
                    height = img.height();

            this.node = img.wrap("&lt;div/&gt;").parent().addClass("plot").css({width:width,height:height});
            this.raphael = new Raphael(this.node.get(0), width, height);
            this.raphael.image(img.attr("src"), 0, 0, width, height);

            this.unselectable(this.node.get(0));
            this.node.mouseover(function() {
                //this.style.cursor = 'pointer';
                //$(this).css({'cursor':'url("pencil.cur")'});
            }).mousedown(function(e) {
                self.start(e);
            }).mouseup(function(e) {
                self.stop(e);
            }).mousemove(function(e) {
                self.move(e);
            });

            this.node.bind("drawLine setColor deleteLine", function(e, param) {
                self[e.type](param);
            });

            var model = this.model;
            this.model = [];

            for (var i = 0,j = model.length; i &lt; j; i++) {
                this.setColor(model[i].color);
                this.drawLine(model[i].path);
            }

            this.onInit.apply(this, [raw_img]);
        },

        setColor : function(color) {
            this.color = /^#([a-f0-9]{3}){1,2}$/i.test(color) ? color : this.defaultColor;
        },

        start: function(e) {
            //console.log("start");
            e.preventDefault(); // prevent drag image
            if (this.isStarted)
                return;

            this.offset = this.node.offset();

            this.model.push({
                line : "M " + (e.pageX - this.offset.left) + " " + (e.pageY - this.offset.top),
                color : this.color,
                path : null
            });

            this.isStarted = true;
            this.onStart.apply(this, [e]);
        },

        move: function(e) {
            //console.log("move");
            if (!this.isStarted) return;

            var model = this.model.pop();
            model.path &#038;& model.path.remove();
            delete model.path,model;

            this.drawLine(model.line + " L " + (e.pageX - this.offset.left) + " " + (e.pageY - this.offset.top));
            this.onMove.apply(this, [e]);
        },

        stop: function(e) {
            //console.log("finish");
            this.isStarted = false;

            var model = this.model.pop();
            e.isClick = !model.path; // was it line or just click on image

            model.path &#038;& this.model.push(model) || delete model;

            this.onStop.apply(this, [e]);
        },

        drawLine : function(line) {
            var path = this.raphael.path(line)
                    .attr({stroke:this.color,'stroke-width':5,'stroke-linejoin':'round'});

            var self = this;

            $(path.node)
                .click((function(c) {return function() {self.onLineClick.apply(self, [c]);}})(self.model.length))
                .dblclick((function(c) {return function() {self.onLineDblClick.apply(self, [c]);}})(self.model.length))
                .mouseover(function() {this.style.cursor = 'crosshair';});

            this.model.push({line:line,path:path,color:this.color});
        },

        deleteLine : function(index) {
            var model = this.model[index];
            model.path &#038;& model.path.remove();
            delete model.path, model;

            this.onLineDelete.apply(this, [index])
        },

        unselectable: function(element) {
            // http://ajaxcookbook.org/disable-text-selection/
            element.onselectstart = function() {
                return false;
            };
            element.unselectable = "on";
            element.style.MozUserSelect = "none";
        }
    };


    $.fn.paint = function(options) {
        this.each(function() {
            new Paint(this, options);
        });
        return this;
    };

})(jQuery);
</code></pre>

<p>Простите, но комментариев к коду пока нет, будут в следующей версии, наверное, сильно не обольщайтесь. Да и скачать этот код можно <a href="http://mabp.kiev.ua/content/source/paint/jquery.paint.js">отсюда</a>.</p>

<p>Теперь пора показать как это все собрать в кучу, запускаю плагин.</p>

<pre><code class="javascript">
$("#plot").paint({
    model : model,
    // текущий подсвеченный элемент
    hightlited : null, // it may be zero
    hightlitedColor : "#fff",

    // обработчик двойного клика на линию
    // удаляет подсветку
    // удаляет линию с рисунка
    // удаляет ряд из таблицы
    onLineDblClick : function(index){
        this.hightlited = null;
        this.deleteLine(index);
        $("#grid").jqGrid('delRowData',index+1); 
    },

    // обработчик клика на линию
    onLineClick : function (index) {
        // снимает прошлуб подсветку если была
        if (this.hightlited != null){
            var prev = this.model[this.hightlited];
            prev.path.attr({stroke:prev.color});
        }

        // добавляет новую
        this.model[index].path.attr({stroke:this.hightlitedColor});
        // подсвечивает ряд в таблице
        $("#grid").jqGrid('setSelection',index+1);
        this.hightlited = index;
    },

    // call-back на событие остановки рисования линии
    onStop : function(e){
        // удаляет старую подсветку если была
        if (this.hightlited != null){
            var prev = this.model[this.hightlited];
            prev.path.attr({stroke:prev.color});
        }

        // если это не случайный клик
        if (!e.isClick){
            // подсвечивает свежесозданную линию
            var index = this.model.length-1;
            this.model[index].path.attr({stroke:"#fff"});
            this.hightlited = index;
            // добавляет ряд в таблицу
            $("#grid").jqGrid('addRowData',index+1,this.model[index]);
        }
    },

    // пока не забыл надо в самом начале определить 
    // событие которое будет вызвано когда в таблице
    // поменяется выделенная строка
    onInit : function(){
        var self = this;
        this.node.bind("hightliteLine", function(e, index) {
            // убирает старую подсветку
            if (self.hightlited != null){
                var prev = self.model[self.hightlited];
                prev.path.attr({stroke:prev.color});
            }

            // подсвечивает новую линию
            self.model[index].path.attr({stroke:self.hightlitedColor});
            self.hightlited = index;
        });
    }
});
</code></pre>

<p>Теперь надо привязать выбор цвета, для этого создается небольшая функция инициализирующая colorpicker, когда colorpicker меняет цвет срабатывает событие у рисовалки, и она меняет цвет линии.</p>

<pre><code class="javascript">
$('#colorpicker').ColorPicker({
    flat: true,
    // начальный цвет
    color: '#00ff00',
    // вся магия тут
    onChange: function(hsb, hex, rgb) {
        $("#plot").trigger("setColor","#"+hex);
    }
});
</code></pre>

<p>Еще немного магии для инициализации data grid'а :</p>

<pre><code class="javascript">
$("#grid").jqGrid({
    datatype: "local",
    height: 250,
    colNames:['No','Amount','Units','Value'],
    colModel:[
        {name:'id',index:'id',width:100, sorttype:"int"},
        {name:'amount',index:'amount', width:100, sorttype:"int", align:"right"},
        {name:'units',index:'units', width:100, sorttype:"float"},
        {name:'value',index:'value', width:100, sorttype:"int", align:"right"}
    ],
    caption: "Lines Data",
    onSelectRow : function (id){
        $("#plot").trigger("hightliteLine", id-1);
    }
});

for(var i=0, j=model.length;i<=j;i++)
    $("#grid").jqGrid('addRowData',i+1,model[i]);
</code></pre>

<p>Тут, помимо общих настроек и установки модели, есть чудо метод который вызовет событие подсветки линии когда измениться текущий ряд в таблице.</p>

    <link rel="stylesheet" href="/content/source/paint/colorpicker.css" type="text/css" />
    <link rel="stylesheet" type="text/css" media="screen" href="/content/source/jqgrid/css/ui.jqgrid.css" />
    <style type="text/css">
        .plot {
            cursor: url("/content/source/paint/pencil.gif"), url("/content/source/paint/pencil.cur"), auto;
        }
        #gbox_grid {
            line-height:100%;
        }
        #gview_grid table{
            margin : 0px;
        }
        #gview_grid .ui-jqgrid-hbox {
            padding : 0px !important;
        }
    </style>

    <script src="/content/source/paint/jquery.paint.js" type="text/javascript"></script>
    <script src="/content/source/paint/colorpicker.js" type="text/javascript"></script>

    <script src="/content/source/jqgrid/js/i18n/grid.locale-ru.js" type="text/javascript"></script>
    <script src="/content/source/jqgrid/js/jquery.jqGrid.min.js" type="text/javascript"></script>

    <script src="/content/source/raphael/raphael-1.5.2.js" type="text/javascript"></script>
    <script src="/content/source/paint/jquery.paint.example.js" type="text/javascript"></script>

<img id="plot" src="/content/source/face.png" width="400" height="400" alt="face"/>
<div id="colorpicker"></div>
<table id="grid"></table>

<p>Я думаю, я в ближайшее время не заброшу этот плагин так что ждите новую версию, возможно даже с документацией, пишите свои каменты, не забывайте читать мои бредни ;)</p>
]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2009/12/08/paint/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>jQuery UI: Customized datepicker</title>
		<link>http://mabp.kiev.ua/2009/08/11/jquery-ui-customized-datepicker/</link>
		<comments>http://mabp.kiev.ua/2009/08/11/jquery-ui-customized-datepicker/#comments</comments>
		<pubDate>Tue, 11 Aug 2009 19:49:35 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[calendar]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[jqueryUI]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.kiev.ua/?p=1104</guid>
		<description><![CDATA[Написать этот код меня подтолкнул комментарий, но идея сделать такую плюшку была давно. Смысл простой до нельзя - надо на стандартном календаре выделять определенные дни. Стандартными средствами этого естественно сделать нельзя. Хачить ui.datepicker.js тоже нельзя, при этом желательно еще передавать список дней прямо в конструктор календаря. Нужно выкрутится, ну в общем как всегда. Начинаем кое-как [...]]]></description>
			<content:encoded><![CDATA[<p>Написать этот код меня подтолкнул <a href="http://mabp.kiev.ua/2008/12/29/jquery-ui-datepicker/#comment-990">комментарий</a>, но идея сделать такую плюшку была давно.</p>

<p>Смысл простой до нельзя - надо на стандартном календаре выделять определенные дни. Стандартными средствами этого естественно сделать нельзя. Хачить ui.datepicker.js тоже нельзя, при этом желательно еще передавать список дней прямо в конструктор календаря. Нужно выкрутится, ну в общем как всегда.</p> 
<span id="more-1104"></span>

<p>Начинаем кое-как обживаемся в тех узких рамках в которые себя загнали - используем замыкания и делегацию и прочие малоиспользуемых плюшки типа функции apply и регулярных выражений.</p>

<pre><code class="javascript">
(function($) {

	/**
	 * autor: CTAPbIu_MABP
	 * email: ctapbiumabp@gmail.com
	 * site: http://mabp.kiev.ua/2009/08/11/customized-datepicker/
	 * license: MIT &#038; GPL
	 * last update: 11.08.2009
	 * version: 1.0
	 */
     
	// сохраняем старые функции
	var old_datepicker =  $.fn.datepicker;
	var old_generateHTML = $.datepicker._generateHTML;

	// и делигируем их новым
	$.datepicker._generateHTML = function(inst) {
		// получаем календарь ввиде raw-html
		var _generateHTML = old_generateHTML.apply(this, arguments),
		// выгребаем даты для этого календаря
		dates = inst.settings.hightlight.values;
		titles = inst.settings.hightlight.titles;
		
		// и начинаем расскрашивать
		for (var i in dates){
			if (dates[i].getFullYear() == inst.drawYear &#038;& dates[i].getMonth() == inst.drawMonth){
				_generateHTML = _generateHTML.replace(
				// магия регулярок
				new RegExp('&lt;a class="([^"]+)" href="#"&gt;' + dates[i].getDate() + '&lt;/a&gt;','i'),
				function(link, classes){
					// еще больше магии
					return link.replace(classes, classes + ' ui-state-custom' + 
						(titles[i] ? '" title="'+ titles[i] : ''));
				});
			}
		}
		return _generateHTML;
	};
	
	// делегируем конструктор
	$.fn.datepicker = function(options){
		// новые опции преобразовываем к объекту
		options.hightlight = $.extend(
			{format:$.datepicker._defaults.dateFormat, values:[], titles:[], settings:{}},
			options.hightlight
		);
		
		// сразу превращаем даты в объекты типа Date для того чтобы сохранить 
		options.hightlight.values = $.map(options.hightlight.values, function(value){
			return $.datepicker.parseDate(options.hightlight.format, value, options.hightlight.settings);
		});
		
		return old_datepicker.apply(this, [options]);
	};
})(jQuery);
</code></pre>

<p>Сначала очень долго думал как же для каждого инстанса календаря сделать свою функцию generateHTML с замкнутыми датами, но потом обнаружил что разработчики тоже сильно не парились и все совали в $.data и я тоже решил этим воспользоваться. Как никак уже готовое решение и переиспользование кода.</p>

<p>Пока писал, обнаружил еще одно забавное недокументированую (кажется) функцию, оказывается есть отдельный CSS класс для выходных, к которому нет реализации. Вот я ее в пример и добавил.</p> 

<script src="/content/source/jquery.calendar.js"></script>
<style>
.ui-datepicker-week-end .ui-state-default{
       border: 1px solid #ffa1a1;
}
.ui-state-custom {
       border: 1px solid #f0f !important;
}
</style>
<script>
jQuery().ready(function($){
$("#datepicker").datepicker({
	firstDay: 1 , 
	hightlight : {
		format:"dd/mm/yy",
		values:["1/08/2009","5/08/2009","15/08/2009"],
		titles:["Рас","Два","Три"],
		settings:{}
	}
});
})
</script>
<div id="datepicker"></div>

<p>Используется это все вот так.</p>
<pre><code class="css">
.ui-datepicker-week-end .ui-state-default{
       border: 1px solid #ffa1a1;
}
.ui-state-custom {
       border: 1px solid #f0f !important;
}
</code></pre> 
<pre><code class="javascript">
$("#datepicker").datepicker({
	firstDay: 1 , 
	hightlight : {
		format:"dd/mm/yy",
		values:["1/08/2009","5/08/2009","15/08/2009"],
		titles:["Рас","Два","Три"],
		settings:{}
	}
});
</code></pre>

<p>Проще не бывает, а скачать можно <a href="http://mabp.kiev.ua/content/source/jquery.calendar.js">тут</a></p> 

]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2009/08/11/jquery-ui-customized-datepicker/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Простой comet</title>
		<link>http://mabp.kiev.ua/2008/04/26/simple_comet/</link>
		<comments>http://mabp.kiev.ua/2008/04/26/simple_comet/#comments</comments>
		<pubDate>Sat, 26 Apr 2008 15:01:38 +0000</pubDate>
		<dc:creator>CTAPbIu_MABP</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[IE]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[Opera]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://mabp.localhost/?p=179</guid>
		<description><![CDATA[Всю неделю готовил эту статью, и вот наконец публикую. Что же первое приходит в голову, когда видишь слово "comet", правильно - чистящее средство, потом комета, а оно почему-то означает технологию (хотя это, наверное, громко сказано, скорее паттерн) постоянного соединения с сервером. Все же в толстых книжках читали, что после того как сервер получил запрос и [...]]]></description>
			<content:encoded><![CDATA[<p>Всю неделю готовил эту статью, и вот наконец публикую.</p>
<p>Что же первое приходит в голову, когда видишь слово "comet", правильно - чистящее средство, потом комета, а оно почему-то означает технологию (хотя это, наверное, громко сказано, скорее паттерн) постоянного соединения с сервером. Все же в толстых книжках читали, что после того как сервер получил запрос и отдал ответ браузеру, он забывает, что к нему вообще кто-то обращался. А тут поседели умные люди и стали седыми. Нет, посидели и придумали идею, как заставить передавать браузеру информацию, изменившуюся на сервере.</p>

<span id="more-179"></span>
<script type="text/javascript" src="/content/source/jquery.comet.js"></script>
<script type="text/javascript">
jQuery().ready(function($){
	$().comets({
		update:function(data){
			$("#clock").text(data);
		},
		connect:function(sid){
			$(this).attr({src:"/content/polygon/comet_clock.php?sid="+sid});
		},
		url:"/content/polygon/comet_clock.php?sid="
	});
	$().comets({
		update:function(data){
			$("#timer").text(data);
		},
		connect:function(sid){
			$(this).attr({src:"/content/polygon/comet_timer.php?sid="+sid});
		},
		url:"/content/polygon/comet_timer.php?sid="
	});
});
</script>

<style>
iframe {
	width:1px;
	height:1px;
	top:-2000px;
	position:absolute;
}
</style>

<p>Вот собственно об этом мы с вами сегодня и поговорим, а собственно я как обычно буду высказывать свои ламерские мысли, а вы, как обычно, будете комментировать...</p>
<img src="/content/img/comet.jpg" alt="comet" align="right" />
<p>И так с чего же начать, начать надо с того что выберем серверным языком php а клиентским как это не странно — javascript. Для того чтобы соединение не разрывалось, php скрипт должен все время работать, но тут мы встречаем два ограничения. Первое вполне логичное у нас нет бесконечного скрипта который бы все время что-то делал, да это и не надо, достаточно включить в код бесконечный цикл. А вот собственно второе ограничение уже куда более неприятное: по умолчанию php скрипт может выполняться только 30 секунд, после чего сервер выдает ошибку о зависании скрипта. С этим мы будем бороться, но об этом потом.</p>
<p>Теперь когда мы имеем серверную часть нужно придумать как она будет контактировать с клиентской. Для этого нужно написать еще какую-то функцию на javascript, которую бы мог вызвать сервер, причем это должно работать параллельным потоком относительно основной страницы. Для реализации параллельного запроса идеально подходит AJAX, но, к сожалению IE и Opera, не умеют корректно обрабатывать событие onreadystatechange==3 , поэтому для реализации параллельного потока в них используют iframe, а я буду использовать его и для остальных браузеров.</p>

<p>В общем пора переходить к делу. Для реализации порционного отдавания контента на php нужно использовать буферизацию, а то php будет хранить все у себя, аж пока не закончит работу скрипта, чего мы собственно пытаемся избежать. Дальше я приведу пример скрипта, который каждую секунду отдает браузеру команду обновить часы и текущее время.</p>

<pre><code class="php">
// устанавливаем неограниченное время выполнения скрипта
ini_set('max_execution_time', 0);

// начинаем буферизацию
ob_start();
// ставим флаг отдавать буфер каждый раз как он больше не нужен
ob_implicit_flush(true);

// отдаем заголовки, чтобы избежать кэширования
header('Expires: Sut, 01 Jan 2000 00:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

// выбрасываем в поток килобайт пробелов, 
// это нужно из-за того, что php первый раз меньше отдавать не хочет
echo str_repeat(chr(32), 1024);
ob_get_clean();   

// в бесконечном цикле описываем то передачу информации в браузер
while(true){
	echo ""
		."&lt;script type='text/javascript'&gt;\n"
		."parent.$(parent.document).trigger('comet".$_GET['sid']."',['".date("D, d M Y H:i:s O")."']);\n"
		."&lt;/script&gt;\n";
	ob_get_clean();
	// ждем 1 секунду до следующего обновления
	sleep(1);
}
</code></pre>

<p>А вот и пример работы этого скрипта находиться сразу под кодом.</p>

<div id="clock" style="width:300px;border: 2px dashed red; font-weight:bold; font-size:1.2em; margin:auto;text-align:center;">Hello from comet!</div>

<p>Теперь давайте разберем, клиентскую часть, она тоже весьма не большая. Как я уже говорил, ее задача в основном создать iframe и приаттачить нужные события. Событий может быть всего два: обновление с новыми данными и разрыв соединения с сервером. Оба события необязательные, потому что сервер и сам вполне может отдавать готовый javascript код, а разорванное соединение можно восстанавливать, а можно предположить, что все обновления с сервера уже пришли и новых больше не будет.</p>

<pre><code class="javascript">
(function($) {

	// конструктор объекта
	var comets = function(options){
		this.init(options);
	}
	
	// собственно сам объект
	comets.prototype = {
		comets : [], // итератор соединений
		options : { // массив функций по умолчанию
			connect:function(sid){
				// функция, выполняющаяся при разрыве соединения с сервером
			},
			update:function(data){
				// функция, вызываемая сервером с новыми данными
			},
			url:"", // адрес бэкэнда
		},
		init : function(options){
			var self = this, 
				comet = {},
				sid = self.comets.push([]); // i++

			// создаем реальный массив параметров
			$.extend(comet,self.options,options)
			
			// создаем iframe и соединение
			$("&lt;iframe/&gt;")
				.appendTo("body")
				.attr({src:comet.url+sid})
				.bind("load", function(){comet.connect.apply(this,[sid]);})
				
			// аттачим события, к ифрейму аттачить нельзя, так как он еще не загрузился
			$(document).bind("comet"+sid, function(event,data){comet.update.apply(this,[data])})
		}
	}

	$.fn.comets = function(options){
		// создаем новый comet
		new comets(options);
		
		// не нарушаем цепочку
		return this;
	}
})(jQuery);
</code></pre>

<p>Теперь надо передать в этот плагин (да я как всегда все делаю плагином под jQuery) наши функции.</p>

<pre><code class="javascript">
$().comets({
	// функция которая обновит такст часов
	update:function(data){
		$("#clock").text(data);
	},
	// функция которая востановит соединение с сервером
	connect:function(sid){
		$(this).attr({src:"clock.php?sid="+sid});
	},
	// адресс бэкэнда
	url:"clock.php?sid="
});
</code></pre>

<p>Как я говорил в начале я расскажу как бороться с временем выполнения скрипта если вам недоступна функция ini_set(); Для этого надо узнать сколько времени может выполняться скрипт и прерывать его работу незадолго до этого времени. Если передать браузеру команду восстановить соединение, то этого можно не указывать в параметрах  в javascript. Вносим правки в алгоритм работы скрипта:</p>


<pre><code class="php">
// узнаем время рабы и отнимаем 5 секунд запаса
$metime = ini_get('max_execution_time') - 5;

// узнаем время начала и конца работы скрипта
$start = $stop = array_sum(explode(" ",microtime()));

ob_start();
ob_implicit_flush(true);
header('Expires: Sut, 01 Jan 2000 00:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

echo str_repeat(chr(32), 1024);
ob_get_clean();   

// по условию, что время начала больше чем время конца минус время исполнения прерываем процедуру
while($start > $stop-$metime){
	echo ""
		."&lt;script type='text/javascript'&gt;\n"
		."parent.$(parent.document).trigger('comet".$_GET['sid']."',['".date("D, d M Y H:i:s O")."']);\n"
		."&lt;/script&gt;\n";
	ob_get_clean();
	$stop = array_sum(explode(" ",microtime()));
	sleep(1);
}

// echo "&lt;script type='text/javascript'&gt;window.location='".$_SERVER['PHP_SELF']."?sid=".$_GET['sid']."';&lt;/script&gt;n";
</code></pre>

<p>Вот пример работы этого кода, только для наглядности я поставил обрыв соединения каждые 10 секунд.</p>

<div id="timer" style="width:300px;border: 2px dashed red; font-weight:bold; font-size:1.2em; margin:auto; text-align:center;">Hello from comet!</div>

<p>Вот такие вот у меня получились простые комёты ;) , возможно, я их немного доделаю и опубликую, свежую версию, но это не сейчас.</p>
]]></content:encoded>
			<wfw:commentRss>http://mabp.kiev.ua/2008/04/26/simple_comet/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
	</channel>
</rss>

