<?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>Разработка &#187; javascript</title>
	<atom:link href="http://www.job-blog.bullgare.ru/tag/javascript/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.job-blog.bullgare.ru</link>
	<description>о программировании и работе</description>
	<lastBuildDate>Fri, 03 Feb 2012 09:42:30 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.4</generator>
		<item>
		<title>Сравнение 12 javascript MVC-фреймворков</title>
		<link>http://www.job-blog.bullgare.ru/2012/01/%d1%81%d1%80%d0%b0%d0%b2%d0%bd%d0%b5%d0%bd%d0%b8%d0%b5-12-javascript-mvc-%d1%84%d1%80%d0%b5%d0%b9%d0%bc%d0%b2%d0%be%d1%80%d0%ba%d0%be%d0%b2/</link>
		<comments>http://www.job-blog.bullgare.ru/2012/01/%d1%81%d1%80%d0%b0%d0%b2%d0%bd%d0%b5%d0%bd%d0%b8%d0%b5-12-javascript-mvc-%d1%84%d1%80%d0%b5%d0%b9%d0%bc%d0%b2%d0%be%d1%80%d0%ba%d0%be%d0%b2/#comments</comments>
		<pubDate>Thu, 26 Jan 2012 08:05:14 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[проектирование]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=1394</guid>
		<description><![CDATA[The Top 10 Javascript MVC Frameworks Reviewed Лучшим признан Ember.js. Надо поковырять) UPD: поковырял. В принципе, более стройная архитектура, чем у того же backbone, у которого часть логики контроллера во вью. Но из коробки нет router и не так просто с pushState, так что не стал использовать серьёзно. Возможно, через полгода-год этот фреймворк будет интереснее.]]></description>
			<content:encoded><![CDATA[<p><a href="http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/">The Top 10 Javascript MVC Frameworks Reviewed</a><br />
Лучшим признан <a href="http://emberjs.com/">Ember.js</a>. Надо поковырять)</p>
<p>UPD: поковырял. В принципе, более стройная архитектура, чем у того же backbone, у которого часть логики контроллера во вью. Но из коробки нет router и не так просто с pushState, так что не стал использовать серьёзно. Возможно, через полгода-год этот фреймворк будет интереснее.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2012/01/%d1%81%d1%80%d0%b0%d0%b2%d0%bd%d0%b5%d0%bd%d0%b8%d0%b5-12-javascript-mvc-%d1%84%d1%80%d0%b5%d0%b9%d0%bc%d0%b2%d0%be%d1%80%d0%ba%d0%be%d0%b2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FireBug console.table</title>
		<link>http://www.job-blog.bullgare.ru/2010/11/firebug-console-table/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/11/firebug-console-table/#comments</comments>
		<pubDate>Tue, 30 Nov 2010 05:39:16 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[ссылка]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=853</guid>
		<description><![CDATA[В FireBug1.6 появился новый способ вывода &#8211; console.table]]></description>
			<content:encoded><![CDATA[<p>В FireBug1.6 появился новый способ вывода &#8211; <a href="http://www.softwareishard.com/blog/firebug/tabular-logs-in-firebug/">console.table</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/11/firebug-console-table/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Пример jquery-ui виджета</title>
		<link>http://www.job-blog.bullgare.ru/2010/11/%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-jquery-ui-%d0%b2%d0%b8%d0%b4%d0%b6%d0%b5%d1%82%d0%b0/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/11/%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-jquery-ui-%d0%b2%d0%b8%d0%b4%d0%b6%d0%b5%d1%82%d0%b0/#comments</comments>
		<pubDate>Tue, 16 Nov 2010 15:09:21 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=844</guid>
		<description><![CDATA[Виджет добавляет инпуту кнопки, упрощающие генерацию маски, и строку для показа сгенерированного по маске значения. Виджет выложен исключительно для примера. Использовать напрямую нельзя (только с моего письменного разрешения). К сожалению так, прошу извинить. Для работы нужен jquery.caret (function( $ ) { $.widget( "ui.numerationMaskGenerator", { options: { "buttonsData": { "Day": { "value": "%D", "isPressed": false, "example": [...]]]></description>
			<content:encoded><![CDATA[<p>Виджет добавляет инпуту кнопки, упрощающие генерацию маски, и строку для показа сгенерированного по маске значения.<br />
Виджет выложен исключительно для примера.<br />
Использовать напрямую нельзя (только с моего письменного разрешения). К сожалению так, прошу извинить.<br />
<span id="more-844"></span><br />
Для работы нужен jquery.caret</p>
<pre class="code">
(function( $ ) {
	$.widget( "ui.numerationMaskGenerator", {
		options:
		{
			"buttonsData":
			{
				"Day":
				{
					"value": "%D",
					"isPressed": false,
					"example": "01"
				},
				"Month":
				{
					"value": "%M",
					"isPressed": false,
					"example": "01"
				},
				"Year":
				{
					"value": "%Y",
					"isPressed": false,
					"example": "1970"
				},
				"Number":
				{
					"value": "%N",
					"isPressed": false,
					"example": "1",
					"required": true
				}
			}
		},
	//{{{ _init
	/**
	 * Инициализация виджета
	 */
		_init: function()
		{
			var self = this;
		// обкладывающий див
			this.buttonsParent = $( "&lt;div&gt;&lt;/div&gt;" )
				.addClass( "numeration-mask-parent" )
				.addClass( "ui-helper-clearfix" );
			this.buttons = [];
			this.valuesExamples = [];
			this.required = [];
		// инитим все кнопки
			for ( var buttonName in this.options.buttonsData ) {
				this._initiateButton( buttonName, this.options.buttonsData[buttonName] )
					.appendTo( this.buttonsParent );
			}
			this.element.before( this.buttonsParent )
				.addClass( "numeration-mask" );
		// вывод примера сгенерированного значения
			this.exampleElement = $( "&lt;div&gt;&lt;/div&gt;" )
				.addClass( "numeration-mask-example" );
			this.element.after( this.exampleElement );
			this.element.bind( "keyup", function(){ self._generateExample(); } );
			this._generateExample();
		},
	/**
	 * Инициализация кнопки
	 */
		_initiateButton: function( ButtonName, ButtonData )
		{
			var self = this,
				button;
			button = this.buttons[ButtonName] = $( "&lt;div&gt;&lt;/div&gt;" );
			button
			// запрещаем выделение текста
				.disableSelection()
				.addClass( "numeration-mask-button " + ButtonName )
				.text( ButtonName )
				.bind( "click", function( Event ) {
					self.buttonClick( self.buttons[ButtonName] );
				})
				.data( "name", ButtonName )
				.data( "value", ButtonData["value"] )
				.data( "isPressed", this.element.val().indexOf( ButtonData["value"] ) &gt; -1 ? true : ButtonData["isPressed"] )
				.data( "example", ButtonData["example"] )
			// если уже введён текст - делаем кнопку нажатой
				.toggleClass( "pressed", button.data( "isPressed" ) );
			this.valuesExamples[ButtonData["value"]] = ButtonData["example"];
		// сюда складываем названия обязательных кнопок
			if ( "required" in ButtonData &#038;&#038; ButtonData.required ) {
				this.required.push( ButtonName );
			}
			return this.buttons[ButtonName];
		},
	/**
	 * Нажатие на кнопку
	 */
		buttonClick: function( Button )
		{
			var button = Button;
			button
				.data( "isPressed", ! button.data( "isPressed" ) )
				.toggleClass( "pressed", button.data( "isPressed" ) );
		// при нажатии на кнопку вставляем значение кнопки в позицию курсора
			if ( button.data( "isPressed" ) ) {
				$( this.element ).insertAtCaret( Button.data( "value" ) );
			}
		// иначе - удаляем все вхождения значения кнопки
			else {
				this._clearButtonValue( Button.data( "value" ) );
			}
		// перегенерируем пример
			this._generateExample();
			$( this.element ).focus();
		},
	/**
	 * Меняем текст в строке
	 */
		_replace: function( String, Value, Replace )
		{
			var re = new RegExp( Value, "g" );
			return String.replace( re, Replace );
		},
	/**
	 * Убираем из значения инпута все вхождения значения кнопки
	 */
		_clearButtonValue: function( Value )
		{
			$( this.element ).val( this._replace( $( this.element ).val(), Value, "" ) );
		},
	/**
	 * Генерация примера
	 */
		_generateExample: function()
		{
			var maskString = $( this.element ).val();
			maskString = this._appendRequiredParamsToExampleString( maskString );
			for ( var val in this.valuesExamples ) {
				maskString = this._replace( maskString, val, this.valuesExamples[val] );
			}
			$( this.exampleElement ).text( maskString );
		},

	/**
	 * Добавляем значения требуемых кнопок
	 */
		_appendRequiredParamsToExampleString: function( String )
		{
			if ( this.required.length )
			{
				for ( var i =0; i &lt; this.required.length; i++ )
				{
					var name = this.required[i];
					var value = this.buttons[name].data( "value" );
					if ( String.indexOf( value ) == -1 ) {
						String += value;
					}
				}
			}
			return String;
		}
	});

}( jQuery ));

$.fn.extend({
	insertAtCaret: function( Value )
	{
		var $element =  $( this );
		var caretPosition = $element.caret();
		var value = $element.val();
		$element.val( value.substr( 0, caretPosition.start ) + Value + value.substr( caretPosition.end, value.length ) );
		var pos = caretPosition.start + Value.length;
		$element.caret( pos, pos );
	}
});
</pre>
<p>Ещё пример, попроще:</p>
<pre class="code">

	/**
	 * Виджет редактирования текста на странице (использует уже существующий DOM для формы редактирования)
	 */
		jQuery.widget( "ui.simpleInlineEditor",
		{
			options:
			{
				selectorForm: null,
				selectorText: null,
				selectorSwitchToForm: null,
				selectorSave: null,
				selectorCancel: null,
				messageSuccess: 'Данные сохранены',
				messageError: 'Не удалось сохранить данные. Попробуйте ещё раз.',
				messagePositionTop: 30,
				eventsNs: 'simpleInlineEditor'
			},
			_init: function()
			{
				var $ = jQuery,
					me = this;
				var $el = me.element;
				if ( ! me.options.selectorForm || ! me.options.selectorText || ! me.options.selectorSwitchToForm || ! me.options.selectorSave || ! me.options.selectorCancel ) {
					throw new Error( 'not enough params' );
				}
				$( me.options.selectorSwitchToForm, $el ).bind( 'click.' + me.options.eventsNs, function() {
					me._toggle( true );
				} );
				$( me.options.selectorCancel, $el ).bind( 'click.' + me.options.eventsNs, function( Event ) {
					Event.stopPropagation();
					Event.preventDefault();
					me._toggle( false );
				} );
				$( me.options.selectorSave, $el ).bind( 'click.' + me.options.eventsNs, function( Event ) {
					Event.stopPropagation();
					Event.preventDefault();
					me._submit();
				} );
			},
		//переключатель текст/форма
			_toggle: function( ShowForm )
			{
				var $ = jQuery,
					me = this;
				var $el = me.element;
				$( me.options.selectorForm, $el ).toggle( ShowForm );
				$( me.options.selectorText, $el ).toggle( ! ShowForm );
			},
		// сохранение данных формы
			_submit: function()
			{
				var me = this,
					$ = jQuery;
				var $el = me.element;
				var $form = $( me.options.selectorForm, me.element );
				$.post( $form.attr( 'action' ), $form.serialize(), function( Answer )
					{
						if ( Answer.done )
						{
							me._showMessage( me.options.messageSuccess );
							me._toggle( false );
							if ( 'text' in Answer ) {
								$( me.options.selectorText, $el ).text( Answer.text );
							}
						}
						else {
							me._showMessage( me.options.messageError );
						}
					}, 'json');
			},
		// показ сообщения об ошибке или о сохранении
			_showMessage: function( Text )
			{
				var me = this;
				showPopupMessage( me.element, Text, { top: me.options.messagePositionTop } );
			}
		} );

// вызов:
			$( '.js-collection-name' ).simpleInlineEditor( {
				selectorForm: 'form',
				selectorText: '#collection-name',
				selectorSwitchToForm: '#collection-name',
				selectorSave: 'input[type="submit"]',
				selectorCancel: 'input[type="reset"]'
			} );
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/11/%d0%bf%d1%80%d0%b8%d0%bc%d0%b5%d1%80-jquery-ui-%d0%b2%d0%b8%d0%b4%d0%b6%d0%b5%d1%82%d0%b0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Отдача json из Django view</title>
		<link>http://www.job-blog.bullgare.ru/2010/11/%d0%be%d1%82%d0%b4%d0%b0%d1%87%d0%b0-json-%d0%b8%d0%b7-django-view/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/11/%d0%be%d1%82%d0%b4%d0%b0%d1%87%d0%b0-json-%d0%b8%d0%b7-django-view/#comments</comments>
		<pubDate>Sun, 14 Nov 2010 15:31:26 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[json]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=841</guid>
		<description><![CDATA[from django.http import HttpResponse import simplejson as json ... def my_view( request ): jsonDict = { "status": "success", "message": "everything's fine" } return HttpResponse( json.dumps( jsonDict ), mimetype="application/json" ) Подробнее о simplejson &#8211; в документации]]></description>
			<content:encoded><![CDATA[<pre class="code">
from django.http import HttpResponse
import simplejson as json
...
def my_view( request ):
   jsonDict = { "status": "success", "message": "everything's fine" }
   return HttpResponse( json.dumps( jsonDict ), mimetype="application/json" )
</pre>
<p>Подробнее о simplejson &#8211; <a href="http://simplejson.googlecode.com/svn/tags/simplejson-2.1.2/docs/index.html">в документации</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/11/%d0%be%d1%82%d0%b4%d0%b0%d1%87%d0%b0-json-%d0%b8%d0%b7-django-view/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Динамическое добавление/удаление полей форм в Django</title>
		<link>http://www.job-blog.bullgare.ru/2010/11/%d0%b4%d0%b8%d0%bd%d0%b0%d0%bc%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%be%d0%b5-%d0%b4%d0%be%d0%b1%d0%b0%d0%b2%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%bf%d0%be/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/11/%d0%b4%d0%b8%d0%bd%d0%b0%d0%bc%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%be%d0%b5-%d0%b4%d0%be%d0%b1%d0%b0%d0%b2%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%bf%d0%be/#comments</comments>
		<pubDate>Tue, 09 Nov 2010 21:34:09 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=824</guid>
		<description><![CDATA[Задача стояла следующая: есть список сущностей (к примеру, упражнений), каждая из которых в форме выводится в виде нескольких полей ввода (к примеру, сеты и название упражнения), хочется иметь возможность яваскриптом добавлять/удалять упражнения, менять их положение. Для этого были использованы формсеты (django.forms.formsets) и jquery на клиенте. В теории всё так: в шаблон передаём формы из формсета [...]]]></description>
			<content:encoded><![CDATA[<p>Задача стояла следующая:<br />
есть список сущностей (к примеру, упражнений), каждая из которых в форме выводится в виде нескольких полей ввода (к примеру, сеты и название упражнения), хочется иметь возможность яваскриптом добавлять/удалять упражнения, менять их положение.<br />
Для этого были использованы <a href="http://docs.djangoproject.com/en/dev/topics/forms/formsets/">формсеты</a> (django.forms.formsets) и jquery на клиенте.</p>
<p>В теории всё так: в шаблон передаём формы из формсета (подводный камень тут &#8211; для правильной валидации нужно в шаблоне внутри формы нужно не забыть вписать скрытые инпуты, отвечающие за количество форм формсета на странице, для этого пишем в шаблоне <strong>{{ exercisesFormset.management_form }}</strong>).<br />
В шаблоне у каждого упражнения присутствуют контролы для удаления/добавления и перемещения упражнений, после загрузки страницы ненужные контролы у каждого упражнения скрываются, а после, к примеру, добавления нового упражнения контролы перерисовываются.<br />
Для удаления все поля ввода упражнения очищаются, после чего скрываются.</p>
<p>Это была теория, теперь практика. Ниже гольный код с комментариями.<br />
<span id="more-824"></span><br />
Во вьюхе нужно добавить следующее:</p>
<pre class="code">
from django.forms.formsets import formset_factory
...
class NameForm( forms.ModelForm ):
	name = forms.CharField( max_length = 250 )
	description = forms.CharField( widget = forms.Textarea, required = False )

class ExerciseNameForm( forms.Form ):
	name = forms.ModelChoiceField( Exercise.objects.all() )
	sets = forms.IntegerField( min_value = 1, max_value = 200 )
...
@login_required
def add( request, id ):
	user = request.user
	other = Other()
	if request.method == "POST":
		nameForm = NameForm( request.POST, instance = other )
		exercisesFormsetClass = formset_factory( ExerciseNameForm )
		exercisesFormset = exercisesFormsetClass( request.POST, prefix = "exercises" )
		if nameForm.is_valid() and exercisesFormset.is_valid():
			nameData = nameForm.cleaned_data
			other.name = nameData["name"]
			other.description = nameData["description"]
			other.save()
			for position, exerciseForm in enumerate( exercisesFormset.forms, start = 1 ):
				exerciseData = exerciseForm.cleaned_data
				exercise = Exercise()
				exercise.exercise = exerciseData["name"]
				exercise.other = other
				exercise.sets = exerciseData["sets"]
				exercise.position = position
				exercise.save()
			return HttpResponseRedirect( "/other/%s/" % other.id )
		hide_exercises = request.POST["hide_exercises"]
	else:
		exercisesFormsetClass = formset_factory( ExerciseNameForm, extra = 1 )
		nameForm = NameForm( instance = other )
		exercisesFormset = exercisesFormsetClass( prefix = "exercises" )
		hide_exercises = ""
	return render_to_response( "add.html", { "nameForm": nameForm, "exercisesFormset": exercisesFormset, "hide_exercises": hide_exercises }, context_instance = RequestContext( request ) )
</pre>
<p>В шаблоне следующий код:</p>
<pre class="code">
&lt;script&gt;
$(document).ready(function() {
	fixTotalFormsNumber();
	initiateControls();
	hideDeletedExercises();
});
&lt;/script&gt;
{% block content %}
	&lt;form action="" method="POST"&gt;
		&lt;div class="parent"&gt;
			{% csrf_token %}
			&lt;ul&gt;
				{{ nameForm.as_ul }}
				{{ exercisesFormset.management_form }}
			&lt;/ul&gt;
			{% for exerciseForm in exercisesFormset.forms %}
				&lt;ul class="exercise-form-data"&gt;
					{{ exerciseForm.as_ul }}
					&lt;li class="controls"&gt;
						&lt;span class="up clickable"&gt;вверх&lt;/span&gt;
						&lt;span class="down clickable"&gt;вниз&lt;/span&gt;
						&lt;span class="delete clickable"&gt;удалить&lt;/span&gt;
						&lt;span class="add clickable"&gt;добавить&lt;/span&gt;
					&lt;/li&gt;
				&lt;/ul&gt;
			{% endfor %}
		&lt;/div&gt;
		&lt;input type="hidden" name="hide_exercises" value="{{ hide_exercises }}" /&gt;
		&lt;input type="submit" name="Добавить" /&gt;
	&lt;/form&gt;
{% endblock %}
</pre>
<p>В шаблоне также грузится яваскрипт:</p>
<pre class="code">
/**
* При обновлении страницы лишние элементы со страницы пропадут, а количество форм для обработки на стороне сервера останется (фф запомнит)
*/
function fixTotalFormsNumber()
{
	var inputNamePrefix = "exercises-",
		$parent = $( ".parent" );
	var count = $parent.find( "ul.exercise-form-data" ).length;
	$parent.find( "input[name=" + inputNamePrefix + "TOTAL_FORMS]" ).val( count );
}

/**
* Инициируем контролы
*/
function initiateControls()
{
	var $parent = $( ".parent" );
	$parent.find( ".controls .add" ).live( "click", function( Event ) { controlClicked( Event, "add" ) } );
	$parent.find( ".controls .delete" ).live( "click", function( Event ) { controlClicked( Event, "delete" ) } );
	$parent.find( ".controls .up" ).live( "click", function( Event ) { controlClicked( Event, "up" ) } );
	$parent.find( ".controls .down" ).live( "click", function( Event ) { controlClicked( Event, "down" ) } );
	updateControlsVisibility();
}

/**
* Скрывает "удалённые" упражнения при перезагрузке страницы
*/
function hideDeletedExercises()
{
	var $hideExercisesInput = $( "input[name=hide_exercises]" );
	var hideByIndexes = $hideExercisesInput.val().split( "," );
	if ( hideByIndexes.length )
	{
		var $rows = $( ".exercise-form-data" );
		for ( var rowNum = 0, count = $rows.length; rowNum < count; rowNum ++ )
		{
			var $row = $( $rows[rowNum] );
			if ( $.inArray( rowNum + "", hideByIndexes ) > -1 ) {
				$row.hide();
			}
		}
	}
}

function updateControlsVisibility()
{
	var $rows = $( ".exercise-form-data:visible" );
	for ( var rowNum = 0, count = $rows.length; rowNum < count; rowNum ++ )
	{
		var $row = $( $rows[rowNum] );
		if ( rowNum == 0 ) {
			$row.find( ".up" ).hide();
		}
		else {
			$row.find( ".up" ).show();
		}
		if ( rowNum == ( count - 1 ) )
		{
			$row.find( ".down" ).hide();
			$row.find( ".add" ).show();
		}
		else
		{
			$row.find( ".down" ).show();
			$row.find( ".add" ).hide();
		}
		if ( count == 1 ) {
			$row.find( ".delete" ).hide();
		}
		else {
			$row.find( ".delete" ).show();
		}
	}
}

/**
* При клике на контрол
* @param Event
* @param string ControlType
*/
function controlClicked( Event, ControlType )
{
	if ( ControlType == "add" )
	{
		addNewExerciseInputs();
		updateControlsVisibility();
	}
	else
	{
		var $el = $( Event.target ),
			inputSelectors = [ "select[name$=name]", "input[name$=sets]" ],
			rowSelector = ".exercise-form-data";
		var $currentRow = $el.parents( rowSelector );
		if ( ControlType == "delete" )
		{
			deleteExercise( $currentRow, inputSelectors );
			updateControlsVisibility();
		}
		else
		{
			rowSelector += ":visible"
			var $changeToRow;
			if ( ControlType == "up" ) {
				$changeToRow = $currentRow.prevAll( rowSelector );
			}
			else {
				$changeToRow = $currentRow.nextAll( rowSelector )
			}
			if ( $changeToRow.length ) {
				$changeToRow = $changeToRow.eq( 0 );
			}
			changeExerciseInputValues( $currentRow, $changeToRow, inputSelectors );
		}
	}

/**
 * Добавляем поля ввода для ещё одного упражнения
 */
	function addNewExerciseInputs()
	{
		var inputNamePrefix = "exercises-",
			$parent = $( ".parent" ),
		// в качестве шаболна используем последний ul для ввода упражнения
			$sourceExercise = $parent.find( "ul.exercise-form-data:last" );
	// если нашли ul и он правильный
		if ( $sourceExercise.length &#038;&#038; $sourceExercise.find( "input[name^=" + inputNamePrefix + "]" ).length )
		{
		// для вычисления номера в шаблоне
			var re1 = new RegExp( inputNamePrefix + "(\\d*)-" );
		// для замены номеров
			var re2 = new RegExp( inputNamePrefix + "\\d*(-[^'\"]*)", "g" );
		// копируем html шаблона
			var newExerciseHtml = $sourceExercise.html();
		// получаем номер
			var sourceRowNum = parseInt( re1.exec( newExerciseHtml )[1] );
			var newRowNum = sourceRowNum + 1;
		// в html нового ul вписываем правильный номер
			newExerciseHtml = newExerciseHtml.replace( re2, inputNamePrefix + newRowNum + "$1" );
			$parent.append( "&lt;ul class='exercise-form-data'&gt;" + newExerciseHtml + "&lt;/ul&gt;" );

		// увеличиваем количество форм для обработки на стороне сервера
			var $managerNums = $parent.find( "input[name=" + inputNamePrefix + "TOTAL_FORMS]" );
			$managerNums.val( parseInt( $managerNums.val() ) + 1 );
		}
	}

/**
 * "Удаляем" упражнение
 */
	function deleteExercise( $Row, InputSelectors )
	{
		if ( $Row.length )
		{
		// с пустым параметром просто сбросит все значения
			exerciseData( $Row, InputSelectors, "set" );
			$Row.hide();
			// для скрывания этого упражнения при перезагрузке страницы
				var $hideExercisesInput = $( "input[name=hide_exercises]" );
				$hideExercisesInput.val( $hideExercisesInput.val() + $Row.index( "ul.exercise-form-data" ) + "," );

		}
	}

	/**
	 * Меняем два упражнения местами
	 * @param jquery $Row1
	 * @param jquery $Row2
	 * @param Array InputSelectors
	 */
	function changeExerciseInputValues( $Row1, $Row2, InputSelectors )
	{
		if ( $Row1.length &#038;&#038; $Row2.length )
		{
			var row1Values = exerciseData( $Row1, InputSelectors, "get" ),
				row2Values = exerciseData( $Row2, InputSelectors, "get" );
			 exerciseData( $Row1, InputSelectors, "set", row2Values );
			 exerciseData( $Row2, InputSelectors, "set", row1Values );
		}
	}

/**
 * Геттер/сеттер всех инпутов упражнения (так сложно - чтобы сеттер всегда понимал формат геттера)
 * @param jquery $Row
 * @param Array InputSelectors
 * @param string Type - get/set
 * @param Array Values - для сеттера массив значений
 */
	function exerciseData( $Row, InputSelectors, Type, Values )
	{
		Type = Type == "set" ? "set" : "get";
		Values = Values ? Values : [];
	// Пробегаемся по всем нужным элементам ввода
	// если элемент элемент типа select, то получаем/заполняем выбранный option, для инпутов - то же самое с value
		for ( var i = 0, count = InputSelectors.length; i < count; i ++ )
		{
			var inputSelector = InputSelectors[i];
			var $input = $Row.find( inputSelector );
			if ( $input.is( "select" ) )
			{
				if ( Type == "get" ) {
					Values[i] = $input[0].selectedIndex;
				}
				else {
					$input[0].selectedIndex = i in Values ? Values[i] : 0;
				}
			}
			else
			{
				if ( Type == "get" ) {
					Values[i] = $input.val();
				}
				else {
					$input.val( i in Values ? Values[i] : "" );
				}
			}
		}
		return Values;
	}
}
</pre>
<p>Альтернативы:<br />
<a href="http://stackoverflow.com/questions/801354/django-equivalent-of-phps-form-value-array-associative-array">http://stackoverflow.com/questions/801354/django-equivalent-of-phps-form-value-array-associative-array</a>.<br />
<a href="http://eikke.com/django-generic-ajax-form-validation/">http://eikke.com/django-generic-ajax-form-validation/</a> - для валидации формы ajax'ом.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/11/%d0%b4%d0%b8%d0%bd%d0%b0%d0%bc%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%be%d0%b5-%d0%b4%d0%be%d0%b1%d0%b0%d0%b2%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5%d1%83%d0%b4%d0%b0%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5-%d0%bf%d0%be/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Выборка DOM-элементов по селекторам</title>
		<link>http://www.job-blog.bullgare.ru/2010/10/%d0%b2%d1%8b%d0%b1%d0%be%d1%80%d0%ba%d0%b0-dom-%d1%8d%d0%bb%d0%b5%d0%bc%d0%b5%d0%bd%d1%82%d0%be%d0%b2-%d0%bf%d0%be-%d1%81%d0%b5%d0%bb%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%b0%d0%bc/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/10/%d0%b2%d1%8b%d0%b1%d0%be%d1%80%d0%ba%d0%b0-dom-%d1%8d%d0%bb%d0%b5%d0%bc%d0%b5%d0%bd%d1%82%d0%be%d0%b2-%d0%bf%d0%be-%d1%81%d0%b5%d0%bb%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%b0%d0%bc/#comments</comments>
		<pubDate>Fri, 22 Oct 2010 09:58:08 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=811</guid>
		<description><![CDATA[Постоянно забываю запись всех атрибут селекторов для jquery. Памятка с примерами использования: 1. Элемент с атрибутом [A] Выбирает все элементы с атрибутом. Пример: $( "checkbox[checked]" ) Выбрать все чекбоксы с атрибутом &#171;checked&#187;. 2. Элемент с атрибутом A, равным B [A=B] Пример: $( "input[name='my-name']" ) Выбрать все инпуты с атрибутом &#171;name&#187;, равным &#171;my-name&#187;. 3. Элемент с [...]]]></description>
			<content:encoded><![CDATA[<p>Постоянно забываю запись всех атрибут селекторов для jquery. Памятка с примерами использования:<br />
1. <strong>Элемент с атрибутом [A]</strong><br />
Выбирает все элементы с атрибутом.<br />
Пример:</p>
<pre class="code">$( "checkbox[checked]" )</pre>
<p>Выбрать все чекбоксы с атрибутом &laquo;checked&raquo;.<br />
2. <strong>Элемент с атрибутом A, равным B [A=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name='my-name']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, равным &laquo;my-name&raquo;.<br />
3. <strong>Элемент с атрибутом A, не равным B [A!=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name!='my-name']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, не равным &laquo;my-name&raquo;.<br />
4. <strong>Элемент с атрибутом A, начинающимся с B [A^=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name^='my-nam']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, начинающимся с &laquo;my-nam&raquo;.<br />
5. <strong>Элемент с атрибутом A, заканчивающимся на B [A$=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name$='-name']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, заканчивающимся на &laquo;-name&raquo;.<br />
6. <strong>Элемент с атрибутом A, содержащим B [A*=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name*='y-n']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, содержащим &laquo;y-n&raquo;.<br />
7. <strong>Элемент с атрибутом A, содержащим префикс B [A|=B]</strong><br />
Выбирает все элементы, у которых атрибут A равен B или начинается с B и дефиса.<br />
Пример:</p>
<pre class="code">$( "input[name|=my]" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, равным &laquo;my&raquo; или начинающимся с &laquo;my&raquo;.<br />
8. <strong>Элемент с атрибутом A, содержащим B, ограниченный пробельными символами [A~=B]</strong><br />
Пример:</p>
<pre class="code">$( "input[name~='my-name']" )</pre>
<p>Выбрать все инпуты с атрибутом &laquo;name&raquo;, равным &laquo;my-name&raquo; или, к примеру &laquo;my-name your-name&raquo;.</p>
<p><a href="http://api.jquery.com/category/selectors/">http://api.jquery.com/category/selectors/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/10/%d0%b2%d1%8b%d0%b1%d0%be%d1%80%d0%ba%d0%b0-dom-%d1%8d%d0%bb%d0%b5%d0%bc%d0%b5%d0%bd%d1%82%d0%be%d0%b2-%d0%bf%d0%be-%d1%81%d0%b5%d0%bb%d0%b5%d0%ba%d1%82%d0%be%d1%80%d0%b0%d0%bc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Проверка идентичности двух плоских массивов в jQuery</title>
		<link>http://www.job-blog.bullgare.ru/2010/10/%d0%bf%d1%80%d0%be%d0%b2%d0%b5%d1%80%d0%ba%d0%b0-%d0%b8%d0%b4%d0%b5%d0%bd%d1%82%d0%b8%d1%87%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%b4%d0%b2%d1%83%d1%85-%d0%bf%d0%bb%d0%be%d1%81%d0%ba%d0%b8%d1%85-%d0%bc/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/10/%d0%bf%d1%80%d0%be%d0%b2%d0%b5%d1%80%d0%ba%d0%b0-%d0%b8%d0%b4%d0%b5%d0%bd%d1%82%d0%b8%d1%87%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%b4%d0%b2%d1%83%d1%85-%d0%bf%d0%bb%d0%be%d1%81%d0%ba%d0%b8%d1%85-%d0%bc/#comments</comments>
		<pubDate>Wed, 06 Oct 2010 10:24:05 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=797</guid>
		<description><![CDATA[arraysAreEqual: function( Arr1, Arr2 ) { var equal = ( Arr1.length == Arr2.length ); if ( equal ) { $.each( Arr1, function ( key, val ) { if ( $.inArray( val, Arr2 ) == -1 ) { equal = false; return false; } } ); } return equal; },]]></description>
			<content:encoded><![CDATA[<pre class="code">
	arraysAreEqual: function( Arr1, Arr2 )
	{
		var equal = ( Arr1.length == Arr2.length );
		if ( equal )
		{
			$.each( Arr1, function ( key, val ) {
				if ( $.inArray( val, Arr2 ) == -1 )
				{
					equal = false;
					return false;
				}
			} );
		}
		return equal;
	},
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/10/%d0%bf%d1%80%d0%be%d0%b2%d0%b5%d1%80%d0%ba%d0%b0-%d0%b8%d0%b4%d0%b5%d0%bd%d1%82%d0%b8%d1%87%d0%bd%d0%be%d1%81%d1%82%d0%b8-%d0%b4%d0%b2%d1%83%d1%85-%d0%bf%d0%bb%d0%be%d1%81%d0%ba%d0%b8%d1%85-%d0%bc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Greasemonkey-скрипт для Redmine</title>
		<link>http://www.job-blog.bullgare.ru/2010/06/greasemonkey-%d1%81%d0%ba%d1%80%d0%b8%d0%bf%d1%82-%d0%b4%d0%bb%d1%8f-redmine/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/06/greasemonkey-%d1%81%d0%ba%d1%80%d0%b8%d0%bf%d1%82-%d0%b4%d0%bb%d1%8f-redmine/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 14:38:40 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[greasemonkey]]></category>
		<category><![CDATA[prototype]]></category>
		<category><![CDATA[redmine]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=744</guid>
		<description><![CDATA[Redmine, конечно, настраиваемый и гибкий, но иногда очень не удобный. Захотелось сделать кнопочку &#171;Взять в работу&#187;, которая появлялась бы на странице тикета. Делать плагин долго, просто подключить яваскрипт в шаблон &#8211; плохо, проблемы при обновлении Redmine. В итоге решил написать Greasemonkey-скрипт: // ==UserScript== // @name Redmine Ticket // @namespace hc // @include [урл к Redmine]/issues/* [...]]]></description>
			<content:encoded><![CDATA[<p>Redmine, конечно, настраиваемый и гибкий, но иногда очень не удобный.<br />
Захотелось сделать кнопочку &laquo;Взять в работу&raquo;, которая появлялась бы на странице тикета.<br />
Делать плагин долго, просто подключить яваскрипт в шаблон &#8211; плохо, проблемы при обновлении Redmine.<br />
В итоге решил написать Greasemonkey-скрипт:</p>
<pre class="code">
// ==UserScript==
// @name           Redmine Ticket
// @namespace      hc
// @include        [урл к Redmine]/issues/*
// ==/UserScript==
var tt = document.createElement('DIV');
tt.innerHTML = "&lt;script&gt;\
var linkInWork = '&lt;a href=\"javascript:void(0);\" onclick=\"setInWork(); return false;\" class=\"icon\"&gt;In work&lt;/a&gt;';\
$A( $$( '#content .contextual a.icon-del' ) ).each( function( Elem ) {\
	Elem.insert( {'after': linkInWork} );\
});\
function setInWork()\
{\
	var t = $('loggedas').innerHTML;\
	var userId = /\\/users\\/([0-9]*)/.exec(t);\
	if ( userId !== null ) {\
		userId = userId[1];\
	}\
	var statusValue = 2;\
	setSelectOptionSelected( 'issue_assigned_to_id', userId );\
	setSelectOptionSelected( 'issue_status_id', statusValue );\
	$('issue-form').submit();\
}\
function setSelectOptionSelected( SelId, Value )\
{\
	var sel = $( SelId );\
	$A( sel.options ).find( function( option, index ) {\
	   if( option.value == Value ) {\
			sel.selectedIndex = index;\
	   }\
	});\
}\
&lt;/script&gt;";

document.getElementById('content').appendChild(tt);
</pre>
<p>Добавляет ссылки &laquo;In work&raquo; в список ссылок вверху и внизу страницы.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/06/greasemonkey-%d1%81%d0%ba%d1%80%d0%b8%d0%bf%d1%82-%d0%b4%d0%bb%d1%8f-redmine/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Как написать плагин для jQuery</title>
		<link>http://www.job-blog.bullgare.ru/2010/05/%d0%ba%d0%b0%d0%ba-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d1%82%d1%8c-%d0%bf%d0%bb%d0%b0%d0%b3%d0%b8%d0%bd-%d0%b4%d0%bb%d1%8f-jquery/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/05/%d0%ba%d0%b0%d0%ba-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d1%82%d1%8c-%d0%bf%d0%bb%d0%b0%d0%b3%d0%b8%d0%bd-%d0%b4%d0%bb%d1%8f-jquery/#comments</comments>
		<pubDate>Sun, 23 May 2010 19:13:56 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[плагин]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=737</guid>
		<description><![CDATA[Перепечатка статьи, ссылка на оригинал в конце статьи. За последнее время было много просьб рассказать о том, как написать свой собственный плагин к jQuery. Идя навстречу пожеланиям трудящихся бескрайних полей Интернета – выполняю эти просьбы… Для того, чтобы лучше понять принципы, поставим себе какую-нибудь несложную задачу, имеющую хотя бы минимальную практическую пользу. Давайте вместе напишем [...]]]></description>
			<content:encoded><![CDATA[<p>Перепечатка статьи, ссылка на оригинал в конце статьи.</p>
<p>За последнее время было много просьб рассказать о том, как написать свой собственный <strong>плагин к jQuery</strong>. Идя навстречу пожеланиям трудящихся бескрайних полей Интернета – выполняю эти просьбы…</p>
<p>Для того, чтобы лучше понять принципы, поставим себе какую-нибудь несложную задачу, имеющую хотя бы минимальную практическую пользу. Давайте вместе напишем <strong>плагин к jQuery</strong>, который будет раскрашивать в разный цвет четные и нечетные строки любой имеющейся таблицы. Причем мы должны иметь возможность передавать нашему плагину в качестве параметров значение цвета для четных и нечетных строк таблицы, а также значение цвета шрифта в четных и нечетных строках. Еще неплохо сделать, чтобы при перемещении указателя мыши над строкой, цвет фона строки и цвет шрифта тоже изменялся. Задача определена – приступаем к ее выполнению.<span id="more-67"></span></p>
<p>Для начала усвоим правила, которым необходимо следовать при написании плагина для jQuery:</p>
<ul>
<li>имя файла плагина должно формироваться следующим образом: jquery.[имя_плагина].js;</li>
<li>все новые методы присоединяются к объекту jQuery.fn, все функции к объекту jQuery;</li>
<li>в методах, this – это ссылка на текущий объект jQuery;</li>
<li>любые методы или функции, которые Вы присоединяете, должны иметь точку с запятой в конце – иначе при сжатии код станет некорректным;</li>
<li>метод должен возвратить объект jQuery, если не предусмотрено иное;</li>
<li>вы должны использовать this.each, чтобы выполнить итерации по текущему набору элементов – таким путем Вы создадите чистый и совместимый код;</li>
<li>всегда используйте jQuery вместо $ в коде Вашего плагина – это позволит пользователям изменять псевдоним для jQuery в единственном месте;</li>
</ul>
<p>Следуя первому правилу, назовем файл нашего плагина <strong>jquery.zebra.js</strong> и первое, что мы напишем в этом файле, будет определение плагина:</p>
<pre class="code">jQuery.fn.zebra = function(){
  // тут мы разместим код плагина
};</pre>
<p>И хотя наш плагин еще не содержит вообще никакого кода, его уже можно вызвать следующим образом:</p>
<pre class="code">$("selector").zebra();</pre>
<p>Но мы договорились, что наш плагин должен уметь принимать некоторые параметры, поэтому давайте немного дополним наш код:</p>
<pre class="code">jQuery.fn.zebra = function(options){
  // тут мы разместим код плагина
};</pre>
<p>где <strong>options</strong> – это объект, который может содержит пользовательские настройки. А может и не содержать…. Поэтому первое, что мы сделаем – определим настройки по умолчанию, воспользовавшись для этого jQuery.extend(object).</p>
<pre class="code">jQuery.fn.zebra = function(options){
  // настройки по умолчанию
  var options = jQuery.extend({
    bgEven: '#FFC080', // бэкграунд для четных строк
    bgOdd: '#FFDFBF', // бэкграунд для нечетных строк
    fontEven: '#AA7239', // цвет шрифта четных строк
    fontOdd: '#AA7239', // цвет шрифта нечетных строк
    bgHover: '#FF8000', // бэкграунд при hover
    fontHover: '#55391C' // цвет шрифта при hover
  },options);
  // тут мы разместим код
};</pre>
<p>Переменная <strong>options</strong> – это объект, состоящий из пар ключ/значение в которых передаются значения соответствующих опций. Если нашему плагину не будут переданы пользовательские настройки – мы используем настройки по умолчанию. Если же опции, все или некоторые, будут переданы, тогда будут использованы именно они.</p>
<p>Двигаемся дальше, и сейчас самое время вспомнить одно из правил написания плагинов к jQuery – вы должны использовать <strong>this.each</strong>, чтобы выполнить итерации по текущему набору элементов. Заодно вспомним и это правило тоже – метод должен <strong>возвратить</strong> объект jQuery, если не предусмотрено иное. Проделаем это:</p>
<pre class="code">jQuery.fn.zebra = function(options){
  // настройки по умолчанию
  var options = jQuery.extend({
    bgEven: '#FFC080', // бэкграунд для четных строк
    bgOdd: '#FFDFBF', // бэкграунд для нечетных строк
    fontEven: '#AA7239', // цвет шрифта четных строк
    fontOdd: '#AA7239', // цвет шрифта нечетных строк
    bgHover: '#FF8000', // бэкграунд при hover
    fontHover: '#55391C' // цвет шрифта при hover
  },options);
  return this.each(function() {
    // тут мы разместим код
  });
};</pre>
<p>Вспомним еще одно правило (или скорее памятку) – <strong>this</strong> – это ссылка на текущий объект jQuery. Действительно, в нашем случае <strong>this</strong> содержит ссылку на объект, который определит пользователь нашего плагина в jQuery-селекторе, например:</p>
<pre class="code">$("table.example").zebra();</pre>
<p>здесь объект jQuery будет содержать таблицу (или таблицы) с классом example.</p>
<p>Теперь напишем собственно код, который должен реализовать задуманную нами функциональность. Займемся для начала четными строками – отыщем их:</p>
<pre class="code">jQuery(this).find('tr:even');</pre>
<p>Тут все ясно – <strong>this</strong> содержит ссылку на текущую таблицу, и в этой таблице мы отбираем только четные строки. Но надо еще установить цвет фона и цвет шрифта для этого набора строк. Воспользуемся для этого css-свойствами <strong>background-color</strong> и <strong>color</strong>:</p>
<pre class="code">jQuery(this).find('tr:even')
            .css('background-color', options.bgEven)
            .css('color', options.fontEven);</pre>
<p>Для всех строк, присутствующих в текущем наборе (сейчас это четные строки текущей таблицы) мы устанавливаем css-свойства <strong>background-color</strong> и <strong>color</strong>, получая их значения из объекта <strong>options</strong>.</p>
<p>Но и этого будет маловато… Нужно обработать прохождение указателя мыши над строкой. Допишем наш код еще немного:</p>
<pre class="code">jQuery(this).find('tr:even')
    .css('background-color', options.bgEven)
    .css('color', options.fontEven)
    .hover(
      function () {
	jQuery(this)
              .css('background-color', options.bgHover)
              .css('color', options.fontHover);
      },
      function () {
        jQuery(this)
              .css('background-color', options.bgEven)
              .css('color', options.fontEven);
      }
);</pre>
<p>Мы воспользовались методом <strong>hover()</strong> библиотеки jQuery, передав ему две функции. Первая определит css-свойства в момент, когда указатель мыши находится над строкой, а вторая – вернет старые значения, когда он выйдет за пределы строки.</p>
<p>Точно такой же код нужно написать и для нечетных строк текущей таблицы и в итоге получится следующее:</p>
<pre class="code">jQuery.fn.zebra = function(options){
// настройки по умолчанию
var options = jQuery.extend({
  bgEven: '#FFC080', // бэкграунд для четных строк
  bgOdd: '#FFDFBF', // бэкграунд для нечетных строк
  fontEven: '#AA7239', // цвет шрифта четных строк
  fontOdd: '#AA7239', // цвет шрифта нечетных строк
  bgHover: '#FF8000', // бэкграунд при hover
  fontHover: '#55391C' // цвет шрифта при hover
},options);
return this.each(function() {
  // четные строки
  jQuery(this).find('tr:even')
    .css('background-color', options.bgEven)
    .css('color', options.fontEven)
    .hover(
      function () {
        jQuery(this)
              .css('background-color', options.bgHover)
	      .css('color', options.fontHover);
      },
      function () {
        jQuery(this)
               .css('background-color', options.bgEven)
               .css('color', options.fontEven);
      }
    );
  // нечетные строки
  jQuery(this).find('tr:odd')
    .css('background-color', options.bgOdd)
    .css('color', options.fontOdd)
    .hover(
      function () {
        jQuery(this)
               .css('background-color', options.bgHover)
               .css('color', options.fontHover);
      },
      function () {
        jQuery(this)
               .css('background-color', options.bgOdd)
               .css('color', options.fontOdd);
      }
    );

});
};</pre>
<p>Вот такой небольшой, но вполне рабочий плагин у нас получился. А теперь займемся пожалуй даже более важным, чем собственно написание плагина – написанием документации к нему. И для начала сообщим пользователям, а для чего оно вообще надо. Затем опишем этапы установки плагина, приведем список доступных опций и примеры использования. Естественно дадим ссылку на работающий пример.</p>
<h3>О плагине jquery.zebra.js</h3>
<p>Плагин <strong>jquery.zebra.js</strong> позволяет легко изменять цвет фона и шрифта для четных и нечетных строк таблицы, выделяет цветом строку над которой находится указатель мыши. Совместим с IE 6.0+, FF 2+, Safari 2.0+, Opera 9.0+.</p>
<h3>Установка плагина <strong>jquery.zebra.js</strong></h3>
<p>Подключите к странице требующиеся для работы файлы – библиотеку jQuery и файл плагина:</p>
<pre class="code">&lt;script type="text/javascript" src="js/jquery.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="js/jquery.zebra.js"&gt;&lt;/script&gt;</pre>
<h3>Использование плагина <strong>jquery.zebra.js</strong></h3>
<p>1. Использование плагина с настройками по умолчанию:</p>
<pre class="code">&lt;script type="text/javascript"&gt;
$(document).ready(function(){
    $("table.bClass").zebra();
});
&lt;/script&gt;</pre>
<p>В этом примере плагин работает с таблицей (или таблицами), которая имеет класс bClass. К четным и нечетным строкам таблицы будут применены css-правила, которые зададут для них разный цвет фона и разный цвет шрифта. В примере будут использованы настройки по умолчанию.</p>
<p>2. Использование плагина с пользовательскими настройками:</p>
<pre class="code">&lt;script type="text/javascript"&gt;
$(document).ready(function(){
    $("table.cClass").zebra({
      bgEven: "#CC66CC",
      bgOdd: "#E6ACE6",
      fontEven: "#662266",
      fontOdd: "#662266",
      bgHover: "#331133",
      fontHover: "#FFFFFF"
    });
});
&lt;/script&gt;</pre>
<p>В этом примере мы передаем плагину объект, содержащий пользовательские настройки, которые и определяют цвет фона и цвет шрифта в строках таблицы.</p>
<h3>Доступные опции.</h3>
<p><strong>bgEven</strong> – строка: цвет фона для четных строк.<br />
<strong>bgOdd</strong> – строка: цвет фона для нечетных строк.<br />
<strong>fontEven</strong> – строка: цвет шрифта четных строк.<br />
<strong>fontOdd</strong> – строка: цвет шрифта нечетных строк.<br />
<strong>bgHover</strong> – строка: цвет фона при прохождении указателя мыши над строкой.<br />
<strong>fontHover</strong> – строка: цвет шрифта при прохождении указателя мыши над строкой.</p>
<h3>Демонстрация работы.</h3>
<p>Здесь можно <a href="http://www.linkexchanger.su/example_jquery/zebra.html" target="_blank">посмотреть демо</a></p>
<p>Вот и все. Осталось разве что привести пару  ссылок по теме:</p>
<p><a href="http://docs.jquery.com/Plugins/Authoring" target="_blank">http://docs.jquery.com/Plugins/Authoring</a><br />
<a href="http://www.learningjquery.com/2007/10/a-plugin-development-pattern" target="_blank">http://www.learningjquery.com/2007/10/a-plugin-development-pattern</a></p>
<p>и теперь Вы знаете как написать свой плагин для библиотеки jQuery. Удачи!</p>
<p><a href="http://www.linkexchanger.su/2008/67.html">Оригинал статьи</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/05/%d0%ba%d0%b0%d0%ba-%d0%bd%d0%b0%d0%bf%d0%b8%d1%81%d0%b0%d1%82%d1%8c-%d0%bf%d0%bb%d0%b0%d0%b3%d0%b8%d0%bd-%d0%b4%d0%bb%d1%8f-jquery/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Найти ближайшего по иерархии родителя с заданным классом в jQuery</title>
		<link>http://www.job-blog.bullgare.ru/2010/01/%d0%bd%d0%b0%d0%b9%d1%82%d0%b8-%d0%b1%d0%bb%d0%b8%d0%b6%d0%b0%d0%b9%d1%88%d0%b5%d0%b3%d0%be-%d0%bf%d0%be-%d0%b8%d0%b5%d1%80%d0%b0%d1%80%d1%85%d0%b8%d0%b8-%d1%80%d0%be%d0%b4%d0%b8%d1%82%d0%b5%d0%bb/</link>
		<comments>http://www.job-blog.bullgare.ru/2010/01/%d0%bd%d0%b0%d0%b9%d1%82%d0%b8-%d0%b1%d0%bb%d0%b8%d0%b6%d0%b0%d0%b9%d1%88%d0%b5%d0%b3%d0%be-%d0%bf%d0%be-%d0%b8%d0%b5%d1%80%d0%b0%d1%80%d1%85%d0%b8%d0%b8-%d1%80%d0%be%d0%b4%d0%b8%d1%82%d0%b5%d0%bb/#comments</comments>
		<pubDate>Thu, 28 Jan 2010 12:27:47 +0000</pubDate>
		<dc:creator>bullgare</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[DOM]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.job-blog.bullgare.ru/?p=621</guid>
		<description><![CDATA[$parentElem = $( '#myComment' ).parents( '.specialClass' );]]></description>
			<content:encoded><![CDATA[<pre class="code">
$parentElem = $( '#myComment' ).parents( '.specialClass' );
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.job-blog.bullgare.ru/2010/01/%d0%bd%d0%b0%d0%b9%d1%82%d0%b8-%d0%b1%d0%bb%d0%b8%d0%b6%d0%b0%d0%b9%d1%88%d0%b5%d0%b3%d0%be-%d0%bf%d0%be-%d0%b8%d0%b5%d1%80%d0%b0%d1%80%d1%85%d0%b8%d0%b8-%d1%80%d0%be%d0%b4%d0%b8%d1%82%d0%b5%d0%bb/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

