То, о чём писал в твиттере, но не мог написать в блоге раньше. 

Была небольшая и, на первый взгляд, достаточно простая задача - показать на странице таблицу, с возможностью сортировки и автообновления. Ну ещё и поиск по ней. После некоторого времени, потраченного на поиск и попытки исправления существующих решений стало понятно, что написать с нуля будет быстрее и дешевле (тут имеется в виду также дальнейшая поддержка всего этого). Готовые реализации javasctipt-библиотек и плагинов к jQuery были или слишком уж навороченный для данной задачи или, мягко говоря, очень плохо справлялись с обновлением таблицы и последующей сортировкой. Результатом этого всего стало написание плагина для jQuery c нуля.

Коротко о плагине:

 

  • позволяет автоматически обновлять и сортировать таблицы;
  • построен на базе jQuery Template (в будущем планируется версия без этого);
  • сортировка работает исходя из данных что пришли от сервера в JSON, и не зависит от html-разметки;
  • маленький размер и минимум функциональности.

Более подробно с примерами и какой-то документацией о плагине можно почитать на http://ajaxtable.e0ne.info/. Также страница в официальном репозитории пакетов http://plugins.jquery.com/project/jQueryAjaxTable и исходники на GitHub: https://github.com/e0ne/jQuery-AjaxTable.

Комментарии, пожелания и замечания очень приветствуются.

 


 

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

Согласно спецификации ECMAScript 5th Edition, ECMA Script (для простоты понимания и удобства буду использовать термин JavaScript) у объекта Object должен быть метод freeze, который принимает объект и создает на его основе новый неизменяемый (inmutable) объект, у которого все свойства становятся read only и пропадает возможность удалить и/или изменять свойства объекта.

Синтаксис очень простой: Object.freeze(obj);

Для проверки, является ли объект замороженным, существует метод Object.isFrozen(obj).

Пример небольшого кода, чтобы убедится что все работает:

var obj =
   {
      prop: function () {},
      foo: "bar"
   };   
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
var o = Object.freeze(obj);
alert(o === obj);
alert(Object.isFrozen(obj) === true);
alert(obj.foo);
obj.foo = "hello";
alert(obj.foo);
delete obj.foo;
alert(obj.foo);
obj.func = "function";
alert(obj.function);

Более детальное описание и примеры можно найти на страницы разработчиков FireFox: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze

И всё было бы хорошо, если б не браузеры, а, точнее, их поддержка или неподдержка метода Object.freeze. Если верить таблице http://kangax.github.com/es5-compat-table/, то его поддерживают только последние версии Internet Explorer, FireFox, Google Chrome и всё. Такой код не будет работать в Safari и Opera, что практически уничтожает всю полезность данного подхода. 

Так что использовать такие “константы” или нет - решать нужно исход из специфики проекта, а именно - списка поддерживаемых браузеров. В моём случае, к сожалению, поддержка Safari и Internet Explorer 8 намного важнее, чем использование почти настоящих констант в JavaScript’е.

 


Выступать на публике с докладами и говорить в микрофон - совсем не то, что обсуждать что-то в курилке или за чашкой чая. Без подготовки сделать это, как показала практика, значительно сложнее. Я решил попрактиковаться в создании аудио-версии своего блога. Первый блин, как всегда, комом, но буду стараться и практиковаться дальне. Из первых впечатлений:

  • аааа! это мой голос так звучит?
  • блин, как я могу так разговаривать?
  • USB гарнитура Logitech H360 под Mac OS работает отвратительно.
  • веселая фоновая музыка
Первая запись получилась не очень, но я старался как можно меньше ее редактировать, только поубирал лишние звуки и некоторые, слишком большие, паузы.

О чем говорил:

Субъективные мысли вслух о JavaScript:

  • Недостатки JavaScript (куда же без них?)
  • Немного слов о RIA
  • Попытки избавиться от JavaScript 
  • Разработка под Android и iPhone
  • JavaScript - самый низкоуровневый язык программирования для веб
Titanium Appselerator - http://www.appcelerator.com/ 


Продолжение предыдущего поста: http://blog.e0ne.info/post/Modal-popup-with-HTML.aspx

Часть 2. Практика

Если есть проблема, то, обязательно, должно быть решение. Так как готового я не нашел (возможно, плохо искал), пришлось изобретать свое.  Решений, на самом деле, нашлось аж целых два, но так как я остановился лишь на одном, то это решение я рассмотрю более подробно и с примером.

Для начала рассмотрю тот вариант, который я не стал реализовывать.

Решение проблемы #1: манипуляции с свойством tabIndex.

Те, кто сталкивался с необходимостью навигацией по сайту с помощью клавиатуры непременно сталкивались с таким свойством элементов, как tabIndex. По умолчанию, для всех элементов <input /> (кроме <input type=”hidden” />) и <option /> tabIndex равен 0. Для других элементов свойство или не определено, или задано значение “-1” (минус еденица) В таком случае переход по элементам осуществляется в порядке их расположения в DOM-моделе. Если необходимо сделать так, чтоб при навигации по странице с помощью кнопки tab фокус на элемент не попадал, то значение tabIndex необходимо сделать -1.

Исходя из вышесказанного, можно сделать такой алгоритм создания модального окна:

  • реализовать диалоговое окно, как описано в предыдущем посте;
  • при вызове функции show() для всех элементов запоминаем текущее состояние tabIndex и ставим новое значение, равное “-1”;
  • в вызове функции hide() восстанавливаем прежние значения tabIndex.

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

Решение проблемы #2: манипуляции с DOM.

Мой диалог работает следующим образом: базовая часть отображения остается без изменений, все изменения касаются только функций show и hide и некоторыми манипуляциями в DOM-модели.

Работает это следующим образом:

  • все содержимое тега body обрамляем контейнером:
    $(document.body).wrapInner("<div id='body-container' />");
  • делаем метку, где находится наш диалог:
    $("<div id='dlg-marker'/>").insertBefore($("#dlg-content"));
  • перемещаем содержимое диалога в начало тега body:
    $(document.body).prepend($("#dlg-content"));
  • ставим атрибут disabled=”true” для всех элементов, которые не находятся в диалоге:
    $("#body-container").attr('disabled', true);
    // for webkit based browsers
    $("#body-container > input, option").each(function(){
        $(this).attr('disabled', true);
    });
  • показываем диалог.

Соответственно, в методе hide() необходимо все вернуть на своё место:

  • убираем атрибут disabled:
    $('#body-container').removeAttr('disabled');
  • ставим диалог на то место, где он находился изначально; для этого нам и нужна была метка:
    $("#dlg-content").insertAfter($("#dlg-marker"));
  • удаляем ранее созданную метку:
    $("#dlg-marker").detach();
  • удаляем элемент, в который мы поместили содерживое body:
    var html = $('#body-container').html();
    $('#body-container').detach();
    $(document.body).html(html);
  • прячем диалог.

Этот код является слишком неоптимизированным - от него можно избавится, если элемент <div id='body-container' /> будет находится на странице всегда.

Манипуляции с возвращением диалога на прежнее место, прежде всего, необходимы для корректной работы приложений, написанных с помощью ASP.NET. Если этого не сделать, то будут проблемы c UpdatePanel и кнопками, которые находятся вне формы.

В файле advanced-popup-with-input.html я привожу базовую реализацию такого способа. Для использования в production его необходимо немного доработать напильником. А именно:
оформить это всё в виде плагина для jQuery или виджета jQueryUi;
решить проблему с производительностью и обработчиками событий при возвращении элементов в начальное положение относительно DOM-модели.

Сейчас похожая реализация успешно работает в production коде, единственное отличие состоит в том, что элемент <div id='body-container' /> всегда находится на странице и нет необходимости его добавлять и удалять.

Все примеры доступны на GitHub: https://github.com/e0ne/BlogSamples/tree/master/ModalDialog


На мой взгляд, обработке ошибок на JavaScript уделяется незаслуженно мало внимания. Если при написании серверного кода, конструкцию try-catch можно встретить достаточно часто, то на стороне клиента такой код скорее исключение, чем правило.

Для начала немного теории.  Исключение (exception) - ошибка или нестандартное поведение программы во время её работы. Например, попытка открыть файл, которого не существует, или вызвать метод элемента DOM-модели, которого нет.

В JavaScript существует конструкция try-catch-finaly, которая работает так же, как и в других языках программирования.  Логично предположить, что если есть try-catch, то должен быть и оператор throw. И такой оператор в JavaScript действительно есть. Рассмотрим пример (файл error-0.html):

HTML-код:

<div id="someButton">
</div>

JavaScript-код:

var button = document.getElementById("someButton");
button.click();


При выполнении такого кода мы получаем исключение (ошибку) такого вида:

Uncaught TypeError: Object #<an HTMLDivElement> has no method 'click'

Сразу хочу отметить, что пример немного выдуманный, но в реальном коде подобное встретить можно, особенно в случаях использования Ajax: кода html-код нам приходит с сервера.

Как всегда, есть два способа решения данной проблемы и при выборе какой использовать необходимо отталкиваться от конкретной ситуации. Так как тема этого поста обработка ошибок, то я не буду показывать пример проверки на существования метода click.

Добавляем в наш код конструкцию try-catch (файл error-1.html):

var button = document.getElementById("someButton");
try {
    button.click();
}
catch (e) {
document.write(e.name + ':' + e.message);
}

Что тут происходит:

  • находим элемент с id=”someButton”;
  • у найденного элемента пытаемся вызвать метод click();
  • так как такого метода у div’а нет, мы попадаем в тело блока catch;


В блоке catch у нас есть переменная e с типом Error, которая содержит два интересующих нас свойства:

  • name - тип ошибки;
  • message - текст сообщения об ошибке.

На самом деле свойств больше, но сейчас нас они не интересуют.

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

Теперь, когда мы научились обрабатывать исключения, можно создавать свои с помощью конструкции throw. Для начала определимся, какие стандартные типы ошибок есть в JavaScript:

  • SyntaxError - одна из наиболее распространённых ошибок, возникающая при неверном синтаксисе JavaScript’а;
  • TypeError - возникает когда интерпритарор JavaScript’а ожидает получить объект другого типа;
  • EvalError - ошибка при исполнении функции eval();
  • RangeError - возникает при обращении к несуществующему элементу массива;
  • URIError - возникает при выполнении decode/encode URL.


Кроме стандартных типов ошибок(исключений) можно создавать свои. Для того, чтобы бросить исключение (rise exception) необходимо использовать одну из следующих конструкций (пример смотрите в файле error-2.html):

  • throw "Error!";
  • throw new Error("Some Error");
  • throw {name:"Custom Error", message:"Custom error message"};

Ещё одна важная заметка. Так как в JavaScript в отличии от многих других языков используется объект Error вместо Exception, то в тексте значение слов “исключение” и “ошибка” имеют одинаковое значение.

Ссылка на примеры: https://github.com/e0ne/BlogSamples/tree/master/JavaScriptErrors/


Рано или поздно при написании JavaScript’а возникает необходимость в том, чтобы он был автоматически запущен при загрузке страницы. Нужно это, как правило, для следующих действий: инициализация интерфейса (UI) и отложенная загрузка данных (lazy load).
Как часто бывает, для такой простой на первый взгляд задачи, есть несколько способов решения.

Задача. После загрузки страницы, нам нужно каким-то образом проинициализировать меню функцией initiPageMenu(), которыя находится в файле menu.js.

Способы решения:

1. Самый простой способ: в конце js-файла делаем вызов нужной функции.

menu.js:
// some javascript code
initiPageMenu();
// end of file

Самый простой способ имеет множество недостатков:

  • если скрипт подключается в теге <head>, у него нет доступа к DOM-модели;
  • вызов необходимой функции блокирует загрузку других скриптов;
  • необходимо учитывать, что к моменту загрузки скрипта, DOM-модель существует и все необходимые зависимости учтены (например, загружен другой скриат).
  • при просмотре файла menu.js не всегда очевидно, что будет выполнена функция initiPageMenu();
  • данный способ ничего не знает о UpdatePanel.

2. На серверной стороне в событии OnLoad или OnPreInit добавить следующий код:

ScriptManager.RegisterStartupScript(this,  this.GetType(), "initiPageMenu", “initiPageMenu();”, true);

Этот способ значительно лучше. Вызов функции initiPageMenu() будет помещен в конец тега <body>. Из недостатков отмечу следующее:

  • при изменении имени функции необходимо перекомпилировать серверный код со всеми вытекающими отсюда последствиями;
  • вызов клиентской (javascript) функции находится в серверном коде - ничего плохого в этом нет, но, imho, теряется красота кода на стороне сервере; хочу отметить, что во многих случаях этот вариант остаются единственным возможным (когда необходимо передать в клиентский код значение серверной переменной).

3. В файле menu.js добавить функцию page_load():

function page_load() {
    initiPageMenu();
}

После загрузки страницы, ScriptManager ищет все обработчики page_load() и выполняет их в порядке загрузки. К моменту начала выполнения этой функции страница уже полностью загруженна браузером и есть доступ к DOM-модели.

Недостатки:

  • неширокая распространённость данного подхода (а недостаток ли это?);
  • не работает без использования на странице ScriptManager (а такие страницы ещё остались!).

4. Последний способ, о котором я хочу рассказать - событие document.ready() при использовании jQuery:

$(document).ready(initiPageMenu());

Недостаки тут тоже есть (куда же без них?)

  • необходимо использовать библиотеку jQuery;
  • как и первый, данный способ также ничего не знает о UpdatePanel.


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


Часть 1. Теория


Сейчас использование модальных и не только диалогов на веб-сайтах является вполне нормальным явлением, в следствии чего нам доступна масса уже готовых контролов как для asp.net/.... (подставьте сюда ту технологию, которую вы используете в своих проектах), так и множество плагинов для популярных javascript-фреймворком, таких как jquery, moo tools, prototype и других. Для упрощения договоримся, что здесь под контролом я буду иметь в виду все что, что в итоге превращается в html-код и выглядит как привычное нам модальное диалоговое окно.

Реализация диалогового окна на html выглядит следующим образом:

  • в какой-то элемент (например, <div id=”container”>) помещается весь контент диалогового окна и ставится ему свойства display:none; z-index:100; position:absolute;
  • создается “фоновый” элемент для создания эффекта модальности с такими свойствами: background-color:gray; position: absolute; width: 100%; height: 100%;     z-index: 90; display:block; opacity:.32; top:0; left:0;display:none; (например, <div id=”background”>).  - этот пункт нужен только для создания модального диалога;
  • при отображении диалогового окна мы меняем у нашего контейнера и фона свойства display на значение block и видим эффект (модального) диалогового окна.

Пример простого pop-up’а находится в файле simple-popup.html

Все модальные окна, реализованные на HTML, которые встречал работали по описанному алгоритму. Изменения были лишь в удобстве работы с ними и набором дополнительных фич, таких как: drag-n-drop, поддержка тем, обработчики событий и др.

Я всегда использовал этот алгоритм до того момента, как мне не пришлось делать возможность навигации по кнопкам внутри модального диалога с помощью клавиатуры. Для примера, рассмотрим работу диалого, реализованного в файле simple-popup-with-input.html.

При нажатии на кнопку show показывается наш модальный диалог. При этом, мы не можем мышкой перейти на поле ввода вне диалога или кликнуть по кнопке show еще раз до закрытия текущего. С первого взгляда все кажется вполне работоспособным. Но кажется это только до нажатия “магической” (magic!) кнопки tab. И тут мы попадаем на наши поля ввода в таком порядке:

  • кнопка show;
  • поле ввода за кнопкой show;
  • поле ввода на диалоге
  • кнопка hide;
  • элементы управления браузера;
  • и так по кругу...

Здесь мы видим, что модальный диалог никакой не модальный, а только кажется таковым.

Как это работает?

Диалоги на HTML реализуются с помощью слоев отображения элементов. По умолчанию, у нас есть только один слой, т.е. свойство z-index у всех элементов одинаково и равно 0. Для создания нового слоя необходимо выставить свойство z-index в 1, 2, и т.д. В нашем примере, у элементов, не входящих в диалог, z-index равен 0, у фона диалога z-index равен 90, а его высота и ширина равняется 100%, что соответствует ширине и высоте окна браузера, из-за чего он визуально перекрывает элементы, у которых z-index < 90 и складывается впечатление модальности. Последним у нас идет элемент с содержимым диалога, у которого z-index равен 100 (максимальное, в нашем случае, значение), что позволяет нам отобразить его на “переднем” слоем.

Из-за слоев, мы не можешь попасть мышкой на кнопку show, но прекрасно можем добраться до нее с помощью клавиатуры (кнопка tab), а также вводить данные в текстовое поле вне диалога. Получается такой себе полу-модальный диалог с неправильной навигацией с клавиатуры.

В следующей части я расскажу как можно обойти данную проблему и покажу прототип модального диалога, в котором корректно работает tab order.

popup.zip (25,42 kb)


HTML Decode/Encode.

Если кто-то слышал об такой атаке на веб-сайты, как XSS Injection, то он(она) знает что для предотвращение такой атаки необходимо использовать функции HtmlEncode/HtmlDecode. XSS Injection (Cross-site injection) - один из способов атак веб-приложений, главной идеей которого является вставка(инъекция) чужого javascript-кода на атакуемый веб-сайт. Как минимум, это может привести к краже печенек cookies и получении прав администратора сайта. ...

1.1. ASP.NET

Это происходит, примерно, так:
Допустим, у нас есть форма для отправки комментарием, где пользователь вводит свой еmail и текст коммеентария. Самый простой обработчик кнопки добавить окмментарий будет выглядить так:

protected void OnAddCommentClick(object sender, EventArgs e)
{
 var pageId = this.GetPageId();
 var email = this.txtEmail.Text;
 var comment = this.txtComment.Text;
 CommentsService.AddComment(pageId, email, comment);
}

С первой точки зрения этот код не содержит никаких ошибок, компилируется и корректно сохраняет данные в БД. Но, если в тексте  комментария будет любой HTML или JavaScript-код, то при отображении этого комментария на страницу без предварительной обработки мы увидем, что HTML и JavaScript-код без особых проблем обрабатываются браузером.
Таким образом, мы сделали наш сайт уязвимым и нарушили одно из базовых правил проектирования ПО: “Не доверять данным, которые пришли извне нашей системы”. От себя еще добавлю: вдвойне недоверять данным, которые были введенны пользователем. Чтобы исправить это ошибку, необходимо всего-лишь вызвать метод HttpServerUtility.HtmlEncode(), перед сохранением данных в базу:


 var email = Server.HtmlEncode(this.txtEmail.Text);
 var comment = Server.HtmlEncode(this.txtComment.Text);

После чего, строка вида “<script>alert(‘hello!’);</script>” будет сохранена в базу в таком виде:

 &lt;script&gt;alert(‘hello!’);&lt;/script&gt;

Теперь все хорошо: любой введенный пользователем текст не ламает нашу верстку и не вызывает выполнения стороннего javascript (пример 2).
Но все не так просто, как кажется. Теперь, если такой код вставить в обработчике OnAddPage(), где администратор сайта будет добавлять страницы, то вместо красивого текста пользователи увидят набор непонятных символов. Поэтому, при отображении содержимого страницы мы вызываем метод HttpServerUtility.HtmlDecode(), который переведет сохраненный в базе данных текст в такой вид, чтобы браузер его корректно обрабатывал.

Таким образом, мы избавили наш сайт от XSS Injection дали возможность пользователям воодить разнообразные данные без вреда для сайта.

Примечание. Во всех примерах выше свойство странице EnableRequestValidation было установлено в false для возможноти ввода символов “<“ и “>”.
Примечание для ASP.NET 4.0/ASP.NET MVC 2.0. В ASP.NET 4.0/ASP.NET MVC 2.0 появилась конструкция <%: ... %>, которая делает за нас HTML Decode/Encode.

1.2. JavaScript

Если нам необходимо реализовать данную функциональность на клиенте с помощью javascript, то нам на помощь приходит jQuery:

function htmlEncode(html) {
 return $('<div/>').text(html).html();
}
function htmlDecode(text) {
 return $('<div/>').html(text).text();
}

1.3 Python/Django

В фреймворке Django HTML Decode работает по умолчанию, и для его отключение в шаблоне (template) добавить блок {% autoescape off %} и в нем поместить все элементы, для которых нам не нужен HTML Decode, после чего закрыть блок с помощью {% endautoescape %}.

2. URL Decode/Encode.

В отличии от HTML Encode/Decode незаэкранированный url больших проблем не вызывает, за исключением двух моментов: url становится не таким простым и понятным для пользователя и это не очень хорошо влияет на SEO.

2.1 ASP.NET

Рассмотрим такую ситуацию: у нас есть сайт с функцией поиска по нем. Стандартная форма поиска содержит поле для вводи и кнопу "Поиск". В простейшем случае при нажатии на кнопку “Поиск” у нас есть такой обработчик:


protected void OnSerachClick(object sender, EventArgs e)
{
 var searchText = this.txtSearch.Text;
 car url = String.Format(“{0}?q={1}”, VirtualPathUtility.ToAbsoluteUrl(“~/Search.aspx”), searchText);
 Response.Redirect(url);
}

Вполне рабочий код, за исключением того, что в некоторых случаях в итоге мы получаем не очень красивый url вида:

/Search.aspx?q=some%20keywords

Конечно, такой url правильно обрабатывается браузером, но не всем пользователям будет понятно что он значит. Происходит это из-за того, что браузер автоматически заменяет символ пробела и ряд других символов на  их шестнадцатиричное значение вида %XX, где XX - код символа в шестнадцатиричной системе.
Сделано это было давно, кода браузеры были маленкими еще не умели отображать нелатинские символы в строке ввода адреса и для правильного отображения разнообразных символов.
И тут нам на помощь приходят методы HttpServerUtility.UrlEncode() и HttpServerUtility.UrlDecode() которые предназначены для переобразования параметров строки запроса в понятный для пользователей вид. Отдельно отчему, что эти методы предназначены для обработки исключительно параметров url, а для переобразования адреса к необходимому ресурсу существует метод HttpServerUtility.UrlPathEncode(), работа которого больше похожа на работу метода HttpServerUtility.HtmlEncode(), но учитывает все особенности построения url. Этот метод не имеет метода для обратного переобразования, но при правильном подходе к проектированию приложенич это не понадобится.

2.2. JavaScript

Аналог URL Encode в javascript - функция encodeURI()


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

[Добавлено перед публикацией поста]
Все примеры достаточно просты, большинство (5 штук!) взяты из документации, но они активно используются мной и я решил сделать пост-заметку для себя. Может, еще кому-то будет полезно. Те, кто не использует jQuery либо другую библиотеку (еще остались такие?) поймут, что эти несколько строк кода в каждом примере иногда заменяют десятки строк кода на javascript’е без использования сторонних библиотек и своих велосипедов.
[/Добавлено перед публикацией поста]

  • Прочитать и/или изменить значение нужного атрибута в элемента DOM-модели:
    var value = $("#element").attr("custimAttribute");
    и
    $("#element").attr("custimAttribute")=”some text”;
  • Тоже самое, но для классов:
    методы .addClass() (http://api.jquery.com/addClass/) и .removeClass() (http://api.jquery.com/removeClass/
  • Сделать подсветку всех картинок на странице по заданному параметру (имя класса):
    $(".image").mouseover(function () {
        $(this).css("border-width", "2px");
        $(this).css("border-style", "solid");
        $(this).css("border-color", "#1b5790");
    });
  • Сделать какие-либо действия над коллекцией объектов:
    $.each([52, 97], function(index, value) {
        alert(index + : + value);
    });

    http://api.jquery.com/jQuery.each/
  • Присвоить tabIndex для всех элементов ввода на форме:
    $(function(){
        var tabindex = 1;
        $(input,select).each(function() {
             if (this.type != "hidden") {
                  var $input = $(this);
                  $input.attr("tabindex", tabindex);
                  tabindex++; 
             }
        });
     });

    http://greatwebguy.com/programming/dom/setting-your-tabindex-on-your-html-forms-automatically-with-jquery/
  • Отправить AJAX-запрос на сервер:
    как ни странно, но примеры из документации http://api.jquery.com/category/ajax/ достаточно работоспособны чтобы использовать их практически без изменений (ссылку на службу все-таки прийдется подставить свою).
  • Создать start-up скрипт на странице (без использования ScriptManager, входящего в состав ASP.NET):
    $(document).ready(function () {
         alert (hello);
    });

 


В связи с тем, что я повсеместно пытаюсь избавиться от ASP.NET AJAX Control Toolkit (ACT) и перейти на использование jQuery, то от использования Hover Extender от ACT пришлось отказаться. Сразу же было найдено большое количество разнообразных плагинов для jQuery, но большинство из них имели недостаточную реализацию, из-за чегоостановился на jQuery plugin EZPZ Tooltip.

Из основных достоинств данного плагина хочется выделить:

  • "Чистый" HTML - не нужно добавлять специальные атрибуты для элементов, плагин завязывается на отдельный HTML элемент.
  • Конфигурация через наименование (Convention over configuration) - поведение элементов зависит от из ID.
  • Гибкие настройки - с помощью CSS можно настроить как внешний вид контрола, так и его позиционирование.
  • Возможность замены стандартных эффектов путем перехвата соответствующих событий на jQuery.
  • Удобство и простота работы.
  • Маленький размер - 4.5K в сжатов виде.

Более подробно о всем этом можно почитать на странице проекта, а я хочу рассказать как можно его использовать в связке с ASP.NET на примере простого UserControl.

Для инициализации контрола нужно выполнить небольшой javascript:

$("#example-target-1").ezpz_tooltip();
или
$("#target").ezpz_tooltip({contentId:"content"}); - В случае если у наших HTML-элементов будут "нестандартные" для этого контрола ID.

Так как, ASP.NET сам генерирует нам ClientID элементов (я не учитываю специфику ASP.NET 4.0, где можно отключать автоматическую генерацию ClientID), то приходится использовать 2-й вариант таки образом:

 

При этом у нас:

  •  TargetControlId - ID элемента, при наведении на который будет отображаться наш hover (tooltip).
  • ContainerClientId - ID элемента который будет показываться в качестве hover (tooltip).

 Разметка нашего user control выглядит так:

 

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

HoverControl.zip (31.35 kb)