Django и jQuery Template

Published 9/21/2011 by e0ne in Python | Web Development
Tags: ,

 

По отдельность Django и плагин jQuery Template у меня работали хорошо. А вот вместе возникли небольшие проблемы. Вот только не знаю: это все из-за моей невнимательности или данная фича/бага плагина тоже сыграла свою роль.

Вначале просто  data binding работал отлично и никаких проблем не предиделось. Но стоило только появиться необходимости использовать тег {{if}} из jQuery Template, встретились первые неожиданности.

Неожиданность номер раз:

Не совсем, конечно, неожиданность, а, скорее, первая меленькая проблемка. Конструкция “{{“ - совпадает с синтаксисом шаблонов в Django, от чего мы получаем ошибку что у нас неправильный темплейт. Пришлось открыть доки django и найти там что такое template tag и как им пользоваться.

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

{% templatetag openvariable %} if highlight {% templatetag closevariable %}
  <div style="background-color: ${color}">
{% templatetag openvariable %} else {% templatetag closevariable %}
  <div>
{% templatetag openvariable %} /if {% templatetag closevariable %}
  ${name}
</div>

Читабельность всего этого упала в разы, но стало работать. Не совсем правильно, но работать...

Неожиданность номер два:

Вместо ожидаемого результата на странице я получал нечто похожее на:

{{ if highlight }}
  <div style="background-color: red">
{{ else }}

Первая мысль - в разметке страницы заэкранированы символы “{{“ и “}}”. Но, как часто это бывает - первая идея оказалась неправильной. Снова открыл доки Django и jQuery Template. Далеко не сразу заметил что в примерах jQuery Template нет пробела между “{{“ и “if”... Неожиданно, но удаление примеров помогло и все сразу заработало. 

Вывод один - читать документацию (любую!) по диагонали нельзя!

Пример, уже традиционно, лежит на GitHub: https://github.com/e0ne/BlogSamples/tree/master/DjangoAndJqueryTemplate

 


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

Была небольшая и, на первый взгляд, достаточно простая задача - показать на странице таблицу, с возможностью сортировки и автообновления. Ну ещё и поиск по ней. После некоторого времени, потраченного на поиск и попытки исправления существующих решений стало понятно, что написать с нуля будет быстрее и дешевле (тут имеется в виду также дальнейшая поддержка всего этого). Готовые реализации 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.

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

 


jQuery - достаточно мощный и удобный javascript framework. Последняя версия 1.4.4 имеет размер 179KB для разработчиков и всего-лишь 26KB для использования в production. 26 килобайт кода не много, но в таком сжатом формате разобраться практически не возможно даже с помощью дебаггера. В неупакованный версии разобраться проще, в дебаггере всё выглядит хорошо, но всё-равно все тонкости работы и устройства jQuery проходят мимо нас. В 7180-ти строках javascript-кода даже с помощью средств IDE и не менее мощной комбинации Ctrl+F  найти нужный кусок кода не так уж и просто.

Что же делать тем, кто хочет разобраться к том, как работает jQuery? Ответ достаточно простой: скачать исходники. Звучит странно и непонятно. В голове возникает вопрос: какие могут быть исходники у библиотеки, написанной на javascript? Под фразой “исходники jQuery” я подразумеваю то, что лежит у них в репозитории http://github.com/jquery/jquery.

 


Сразу уточню, что описываю я бранч версии 1.4.4. В текуoем master branch (trunk), в котором идёт работа над версией 1.5, переработан модуль ajax, из-за чего все, что к нему относится, вынесено в каталог /src/ajax. Кроме того, разработчики настоятельно не рекомендуют использовать последнюю версию из репозитария для production использования. На момент написания этого поста последняя версия - 1.5pre.

Итак, вернёмся к нашим баранам исходникам.  В корне у нас есть следующие 4 каталога, файлы лицензий и файлы для build-систем (make и ant):

  • build - файлы, необходимые для сборки;
  • speed - тесты на производительность (benchmarks): необходимы для того, чтобы измерить производительность текущей версии, по сравнению со собранной из исходников; если вы не вносили никаких изменений - время выполнения обоих скриптов будет одинаковым;
  • test - unit tests (модульные тесты), написанные с помощью qunit;
  • src - сами исходники jQuery.

Для сборки необходимы Java 1.6.0 и выше и NodeJS, если вы будете собирать из master branch. Сборка выполняется одним из двух способов: с помощью ant, которому нужно передать в качестве параметра файл build.xml, либо с помощью выполнения команды make, которая доступна во всех *nix-системах. После сборки файлы jquery.js и jquery.min.js будут находится в папке /dist.

Какие же есть преимущества у использования такой версии jQuery? На мой взгляд, стоит выделить такие:

  1. В исходниках значительно проще разобраться, чем в одном собранном файле. Здесь каждый модуль расположен в отдельном файле (css.js, event.js и т.д.), что даёт возможность быстро и легко узнать как это всё работает. Этот способ и натолкнул меня на  этот подход к использованию jQuery и написанию этого поста.
  2. Есть возможность отключить ненужные/не используемые модули для уменьшения размера итогового файла. Этот подход применяется при скачивании http://jqueryui.com/download с нужными компонентами.
  3. Можно вносить необходимые для проекта изменения и с помощью тестов убедиться что всё работает. Эти изменения также можно будет предлагать разработчикам jQuery в виде патчей.
  4. При использовании последней версии вы становитесь бета-тестерами и вносите свой, пусть и небольшой, но всё же вклад в развитие проектов :).


По своему опыту могу сказать, что даже для тестовых приложений использование самых последних исходников приносит достаточно много проблем: иногда это просто не собирается (http://forum.jquery.com/topic/can-t-build-latest-jquery-from-the-git-repository), могут не работать многие плагины, особенно с учетом переработанного в версии 1.5pre модуля ajax. Но при использовании бранча с последнего стабильного релиза, inho, плюсов значительно больше.


Продолжение предыдущего поста: 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’а возникает необходимость в том, чтобы он был автоматически запущен при загрузке страницы. Нужно это, как правило, для следующих действий: инициализация интерфейса (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)


Те, кто с помощью 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)


Разработчики JavaScript-фреймворка JQuery не перестают нас удивлять. После недавнего релиза JQuery 1.4.1 нам представили инструмент для unit-тестирования кода, написанного на JavaScript - QUnit

В данный момент поддерживаются такие функции как expect(), equals(), ok(), same(), log() и асинхронные тесты. Вместе с фреймворком распространяется css-файл, с помощью которого можно быстро и красиво выводить результаты тестов. Также разработчики активно используют его для тестирования самого JQuery, поэтому можно посмотреть уже готовые приверы от авторов фреймворка.

P.S. Что-то подсказывает мне что не загорами плагин для FireBug, по крайней мере, мне бы этого хотелось...


jQuery 1.4 Alfa 1

Published 12/7/2009 by e0ne in Web Development

Несколько дней назад вышла новая версия популярного JavaScript framework'а jQuery, пока только альфа, которую сами разработчики не рекомендуют использовать в production. Официальная информация на блоге http://blog.jquery.com/2009/12/04/jquery-14-alpha-1-released, а я расскажу о своих первых впечатлениях по сравнению с последней стабильной версией 1.3.2.

Для начала сравним размеры файлов:

jquery-1.3.2.js jquery-1.3.2.min.js jquery-1.4a1.js jquery-1.4a1.min.js
117 KB 55 KB 144 KB

87 KB

Видем что размер вырос, примерно, на 30 килобайт. Много это или мало - судите сами.

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