Пользователь
0,0
рейтинг
23 мая 2008 в 07:14

Разработка → Unobtrusive JavaScript

Цель статьи — показать, что из себя представляет «ненавязчивый JavaScript», для чего он нужен, и чем он лучше «навязчивого» JavaScript. В рунете я подобных статей не встречал (может они и есть, но мне на глаза не попадались и немного погуглив, я тоже ничего не нашел), а как показывает практика — очень многие не знают, что это такое и как этим пользоваться.



Что такое Unobtrusive JavaScript



Unobtrusive JavaScript — это техника программирования на языке JavaScript, которая состоит из следующих принципов:
  • разделения структуры (HTML) / оформления (CSS) и поведения (JavaScript)
  • использование JavaScript для повышения удобства использования уже рабочего приложения
  • применения техники Graceful degradation — если браузер не поддерживает те или иные функции, которые мы добавляем в приложение с помощью JavaScript — приложение всё равно остается рабочим


Зачем?



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

Как?



Легче всего показать это на примере. За ним далеко идти не нужно — возьмем всеми любимый Хабрахабр:
<div class="text_comments"> 

  <div class="comment_item" style="margin-left: 0px;">

    <div class="service_text_comments_holder">
      <a href="http://fxposter.habrahabr.ru/" class="comments_nickname">fxposter</a>
      ...
    </div>

    <div class="comment_text">...</div>

    <div class="comments_reply">
      <div class="reply_word_holder" id="reply_link866650">(<a href="javascript:saw(866650);">ответить</a>)</div>

        <div style="display: none" id="reply866650">
        <!-- форма отправки комментария -->
      </div>

    </div>
  </div>
</div>


Это код комментариев, которые показываются на страничке о посте. Для наглядности ненужные фрагменты были убраны.

Что плохо в этом фрагменте кода?
  1. JavaScript идет вперемешку с HTML (ответить)
  2. У людей с отключенным JavaScript'ом ответить на комментарий не получится в принципе


Как его можно улучшить?
  1. Вынести «навешивание» событий в отдельный файл
  2. Сделать так, чтобы при отключенном JavaScript'е пользователь перебрасывался на отдельную страницу, где бы он мог ответить на выбранный комментарий


Сказано — сделано. Преобразуем HTML к следующему виду:
<div class="text_comments"> 

  <div class="comment_item" style="margin-left: 0px;">

    <div class="service_text_comments_holder">
      <a href="http://fxposter.habrahabr.ru/" class="comments_nickname">fxposter</a>
      ...
    </div>

    <div class="comment_text">...</div>

    <div class="comments_reply">
      <div class="reply_word_holder" id="reply_link866650">(<a href="reply.php?comment_id=866650" class="show_reply_form" id="show_reply_form_866650">ответить</a>)</div>

        <div style="display: none" id="reply866650">
        <!-- форма отправки комментария -->
      </div>

    </div>
  </div>
</div>


Как видите — я изменил тег a (присвоил ему «нормальный» href, добавил id и class). Теперь при нажатии на ссылку «ответить» пользователя будет перебрасывать на страницу ответа на выбранный вопрос. Этим я выполнил второй пункт в списке улучшений. Теперь давайте взглянем на первый пункт: для того, чтобы у пользователей, у которых включен JavaScript вместо редиректа выполнялось открытие формы под самим комментарием мне нужно выбрать все элементы с классом "show_reply_form" и каждому из них назначить на событие onclick функцию, которая бы «открывала» соответствующую форму.

Напишем соответствующую функцию:
function showForm(event) {
  var id = parseInt(this.id.replace('show_reply_form_', ''));
  saw(id);
  return false;
}


Она берет this.id (т.е. id текущего обьекта), убирает из него «фразу» "show_reply_form_", тем самым получая номер элемента, который нам нужно открыть и вызывает функцию saw, которая присутствовала изначально в HTML-коде. Для того, чтобы не произошел редирект после клика на ссылку — функция возвращает false.

Осталось только связать эту функцию с нашими ссылками.

В jQuery это делается так:
$('.show_reply_form').click(showForm);

В PrototypeJS — так:
$$('.show_reply_form').invoke('observe', 'click', showForm);


После присвоения нашей функции элементу — this.id станет относится к id этого элемента (да, это «магия JavaScript» :) ).

Весь JavaScript-код теперь можно вынести в отдельный файл:
function showForm(event) {
  var id = parseInt(this.id.replace('show_reply_form_', ''));
  saw(id);
  return false;
}

document.observe("load", function(event) {
  $$('.show_reply_form').invoke('observe', 'click', showForm);
});


Здесь мы вызываем «связывание» наших ссылок с функцией показа формы при событии window.onload (при загрузке страницы) с помощью функции observe, которую добавляет обьекту document PrototypeJS. observe добавляет к событиям, которые уже могли были быть связаны с событием window.onload, указанное нами событие. Старайтесь пользоваться функциями addEventListener, observe (PrototypeJS) или $(window).load, чтобы предотвратить замещение уже «навешенных» на элемент функций.

Таким образом я выполнил и первый пункт в списке улучшений.

Выводы



На мой взгляд, такое использование JavaScript, а именно — вынос всех функций на JS в отдельный файл и связывание этих функций с элементами страницы с помощью различных событий (здесь мы видели события window.onload и element.onclick) — это на данный момент — единственно правильное использование JavaScript.

Дерзайте, господа. :)

PS. Я прекрасно знаю, что можно использовать событие не window.onload, а DOMContentLoaded. Но я считаю, что для примера понятнее будет всё же использование window.onload.

PPS. Кросспост отсюда.
Павел Форкерт @fxposter
карма
49,9
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (137)

  • 0
    А быстродействие обоих методов Вы сравнивали?
    Ну т.е. сколько уйдет времени при большом количестве комментаривев.
    • +7
      Не сравнивал. И не собираюсь. Я не пишу кривой код ради мизерных выигрышей в производительности. По логике скорость будет примерно одинаковая.
      • 0
        Но вы же пишете код для удолбства пользователей. Если вы не сравнивали, откуда вам знать что выигрыш мизерный?
        • 0
          Я пишу код для себя, либо для того, кто возможно будет доделывать приложение после меня. Для пользователей я пишу сайты/приложения, но никак не код.

          Про выигрыш - протестируйте, потом даже результаты можете сюда выложить. Я не вижу в коде мест, которые были бы "тормозящими".
      • 0
        Все зависит от количества привязок к событиям. Если привязок ооочень много (например web application), то конечно лучше делать инлайновые привязки ИМХО.
  • 0
    http://habrahabr.ru/blog/webdev/24651.ht…
    Ненавязчивое (unobtrusive) программирование
    • 0
      Этот пост я видел. Про unobtrusive js - один параграф, который тему ну никак не раскрывает.
      • 0
        согласен.
        где то я видел у того же автора чтото подробнее видел. вообще очень интерсная тема
    • 0
      черт, опять меня цитируют. Ужас просто :)

      Насчет ненавязчивости — я сам озадачился отсутствием в Рунете материалов, пока не нашел некоторый перевод здесь.
      http://aboutweb2.spb.ru/nenavyazchivy-ja…
      в скором времени вся серия будет переопубликована на http://webo.in/
      • 0
        ну как же- одного из моих любимых авторов не процитировать)
  • +1
    в головы программистам гигантским обухом вбивать эту штуку надо

    я бы добавил, что ненавязчивость можно (лучше) делать поэтапно. например, js может быть включен, но не xmlhttpobject сотоварищи, и навязывать его исходя только из того, что влючены скрипты — зло )
    • 0
      можно немного раскрыть мысль про "навязывать xmlhttpobject сотоварищи — зло"?
      • 0
        js может быть включен, а все эти штуки для асинхронной подзагрузки — отключены. поэтому навязывать их тоже нельзя так сразу
        • 0
          А подскажите пожалуста, как же это можно отключить XMLHTTP не отключая JS?
          IE в расчет не берем, ибо количество леммингов, умеющих отключать JS, а тем более XMLHTTP в IE, ничтожно мало. А те что умудрились это сдлеать — это их дело, и они сами прекрасно понимают, что делают и каковы последствия.
          • 0
            чёрт, неточно выразился. существует по меньшей мере один браузер, который поддерживает (более-менее) яваскрипт, но не подозревает о существовании хмлхттп и пр.
            • 0
              :) Какой? И каков процент его использования? Вы сами им пользуетесь на регулярной основе? Имеет ли смысл его учитывать?
              • 0
                многие мобильные браузеры, например ) и у оперы мини с этим (редко, правда) проблемы. процент? гигантский. пользуюсь? да. имеет ли смысл учитывать? всё имеет смысл учитывать
                • 0
                  Хм. Да, мобильные браузеры не учел, спасибо за замечание )
            • 0
              Для такого браузера можно
              1. оставить рабочими обычные ссылки (reply.php?comment_id=866650)
              2. использовать альтернативные методы доставки, те же самые обычные формы.
              ЗЫ И что-то я в статье не заметил упоминание про XHR
              ЗЫ2 XMLHttpRequest (XHR), а не xmlhttpobject
    • 0
      >в головы программистам гигантским обухом вбивать эту штуку надо
      Плюсанул бы если б мог =)

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

      Кстати для аякса очень удобно делать, что-то типа:

      <a href="makesompeaction.php?foo=bar" class="ajax" title="Do It">Do It</a>

      $$('a.ajax').each(function(element) {
      element.onclick = new Ajax.Request(el.href, {
      method: 'get',
      parameters: {
      format: 'json'
      },
      onSuccess: function(transport){
      //Do something
      }
      });
      return false;
      }
      • 0
        кстати, такие ссылки (выполняющие что-то по параметрам из get) плохи тем, что если нажать f5, действие выполнится снова %) к этому надо аккуратней подходить
        • 0
          Да, есть такое, но за частую ничего "плохо" это не несет, а если несет, то тут уже все на ответсвенности разработчиков, они должны это предусмотреть. Пост тоже по ф5 нормально повторяется =)
          • 0
            про пост многие браузеры предупреждают :)
            • 0
              Redirect через сервер может ? И get подойдет и браузеры не обязаны будут ни о чем предупреждать !
        • 0
          Поэтому нормальные программисты делают:
          1. после того как отработает такой запрос (и тут не важно GET у вас или POST) делают редирект на другую страницу (достаточно отдать браузеру новый урл начинающийся с http(s), то есть полный URI)
          2. добавляют маркеры и прочие механизмы проверяющие, а не делалось ли тоже самое совсем недавно.
          И мне вот неясно причем тут ссылки, GET, F5 и XHR запрос?
      • 0
        Для этих целей хорош Behavior из LowPro.
      • 0
        На всякий случай, хочу добавить что не стоит на GET вешать какие-либо действия, кроме простого получения информации. XSS, знаете ли...
        • 0
          Причем тут XSS и GET простите?
          • +1
            Вот вам раз: http://habrahabr.ru/blog/webdev/41714.ht…
            и вот вам два: http://en.wikipedia.org/wiki/Cross-site_…

            Грубый пример (поправьте, пожалуста, если не прав):
            Браузер: FF/Опера/другой_браузер_с_табами
            В одном табе ваш сайт, админка, в ней ссылка, допустим: httр://sitе.ru/аdmin/delete_content.php?id=1 , AJAX'овая. По нажатию на ссылку в админке вы что-то там удаляете.
            В другом табе (замечу — окно браузера то же, при чем вы в этот момент залогинены в админке, либо просто в куках сессия сидит), мой сайт, на нем ссылка: <a href="http://site.ru/admin/delete_content.php?id=1">FRЕЕ РRОN!!!1111</a>
            Угадайте, что произойдет, когда вы нажмете на ссылку на моём сайте?
            • 0

              <form id="xss_form" style="position: absolute; top: -100000px;visibility: hidden" action="http://site.ru/admin/delete_content.php?id=1" method="post"></form>
              ...
              <a href="..." onclick="document.forms['xss_form'].submit();return false">FREE PRON!!!!1111</a>

              Сильно поможет запрет GET'а?
              • –1
                а если он вам пришлет картинку с адресом http://site.ru/admin/delete_content.php?… ? а пост запрос в картинку не вставить
                • 0
                  Живой пример, открытый текст, никаких форм (ссылка безопасна)
                  зацените картинку: http://cherrylime.net/trash/habr14/free_pron.jpg
              • 0
                ах чёрт, комментарий выше предназначался вам
        • 0
          к тому же это запрещено стандартом HTTP, так что ни "не стоит", а "нельзя"
    • 0
      Много лишней работы (организация доп. страницы и обработка формы) ради малого числа людей (отключающих скрипты). На сайте промышленных масштабов такое обязательно, а в своем проекте я не стал заниматься дублированием.
      • 0
        можно один раз аккуратно спроектировать и не мучиться )
        • 0
          Спроектированное все равно придется реализовывать. Вот и дополнительная работа.
      • 0
        Вы дублированием занимаетесь, когда проставляете одни и те же вызовы функций напрямую в HTML. Не согласны? :)
        И не нужно мне рассказывать, что это всё генерируется серверным языком программирования. Это фигня - повторения есть? Есть! Вот мы их как раз и убираем.
        • 0
          Вот! И я про то же. Не надо убирать, надо просто _не реализовывать_.
          • 0
            Ага, пнятно - убрать весь JavaScript? :)
            • 0
              Наоборот :) Не реализовывать то, что уже сделано яваскриптом.
              • 0
                А зачем тогда CSS выносить в отдельный файл? Может мы тоже есть в style прописывать будем? В общем, не хотите - не используйте. Я показал, как нужно, по моему и не только по моему мнению, делать правильно.
                • 0
                  Ты высказал свое мнение, я - свое. Обсужать тут больше нечего.

                  CSS тут вообще не при чем.
                    • 0
                      А, я понял. Ты решил, что смысл статьи - в вынесении яваскрипта в отдельные файлы. А автор говорил немного про другое - что функциональность, сделанную с помощью яваскрипта, надо обязательно дублировать простым cgi, чтобы не страдали те, у кого нету яваскрипта. Я с этой позицией и спорил. Насчет вынесения в отдельные файлы - ничего против не имею.
                      • 0
                        Во-первых, автор - это я. Во вторых - я говорил о том, что JavaScript имеет смысл применять для УЖЕ РАБОТАЮЩЕГО приложения БЕЗ JAVASCRIPT. Так что - не дублирование без JS, а дополнение с помощью JS для удобства использования.
                        • 0
                          Тьфу, блин, не посмотрел :)
                          • 0
                            Ничего, бывает. :)
                      • 0
                        В общем, позиция понятна - делать unobtrusive без "зацикливания на пользователях без JS". Не поддерживаю, но и прекрасно понимаю твоё нежелание делать так. :) У меня в последнем проекте в некоторых местах так и сделано... Но к запуску, я думаю, это устраниться.
                        • 0
                          Не поддерживаю потому, что это не "unobtrusive". :)

                          При должном проектировании таких ситуаций вообще быть не должно. У меня это так получилось вследствии изменения требований заказчика и необходимости показать проект по-быстрому.
                          • 0
                            У меня немного другая аргументация. Есть ограниченное время и выбор - сделать скажем две функциональности на js или одну на cgi+js - я выберу две на js. А ты одну на cgi+js, да?
                            • 0
                              В данном конкретном случае - нужно только сделать один дополнительный view, обработка его будет абсолютно стандартной. Так что - да, я сделаю одну обычную, а затем добавлю вторую на JS.

                              Либо, если время действительно поджимает, JS-реализация готова, а обычная нет (хотя опять-же, такого вообще быть не должно) - я могу забить на обычную, добавив её уже после (например, после публичного выпуска проекта). Но добавлю я её точно. :)
                  • 0
                    <div style="margin-left: 0;"">
                    <a href="javascript:saw(1)">
                    Одно и то же. И использование и того и другого во внешних файлах дает преимущества как в возможностях, проектировании самих связей и прочего, так и в code reuse.

                    Удачи.
                    • 0
                      это не просто reuse, а сокрашение размера страниц, сокрашение обращений к серверу (js и css кэшируется), и значит- ускорение загрузки сайта
                      • 0
                        С одной стороны я рад, что sunnybear положительно влияет на мысли пользователей о важности JS, но с другой стороны - крупно не согласен с вот такой вот постановкой вопроса об оптимизации.

                        Я не считаю, что на этом этапе нужно вообще хоть сколько-то думать об оптимизации как таковой. Правильно спроектированное приложение всегда [довольно] легко оптимизировать. Но не нужно об этом думать с самого начала.

                        "Преждевременная оптимизация - источник всех бед".
    • 0
      а что такое «обух»?
      • +1
        тупая сторона топора или ножа
  • 0
    Вы говорите window.onload (или DOMContentLoaded, почти без разницы). Это значит что до наступления этого события все ссылки (ответить) буду вести на отдельную страницу.

    Фанатизм в разделении сущностей — большая глупость.
    • 0
      И пускай себе ведут, ведь они ведут в правильном направлении ;) Когда подключится JS, это добавит только удобство.
      • 0
        Нет, не пускай.
        • 0
          Можете аргументировать? Что именно вас не устраивает?
          • 0
            Дак я же написал уже. А идиальный вариант, я считаю дал estarter, имено так я всегда и делаю.
            Единственное «но», что в случае аякса, нужно спроектировать так, чтобы ответ был по тому-же адресу «reply.php», тода передавать нужно не id, а self, что-бы получить сразу href и приписав параметр («&ajax=1», например).
        • 0
          нет, пускай
    • НЛО прилетело и опубликовало эту надпись здесь
      • –1
        А где я что-то ущемлял? Матчасть почитайте, а потом уже говорите.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            DOMContentLoaded вместо onload.
    • +2
      я знаю что вы сделали прошлым летом...:
      задавали стили через атрибут style
      • 0
        Я вам написал по этому поводу чуть ниже.
    • 0
      Вы знаете, что такое DOMContentLoaded? И когда он вызывается? Сразу же, как только полностью загрузится HTML. Это самый что ни на есть хороший способ вешать события на элементы, не ждя, пока загрузится всё остальное.
      • 0
        Я знаю что такое DOMContentLoaded. Он вызывается когда браузер построил все DOM дерево до конца. На хабре в больших постах я успеваю прочитать пару-тройку комментариев до того, как ползунок прокрутки перестанет уменьшаться.
  • 0
    а мне больше нравятся ссылки вида:

    <a href="reply.php?comment_id=866650" onclick="return saw(866650);">ответить</a>

    по моему, подход, описанный в статье, делает html-код более сложным для разработчика.
    • 0
      чего уж там сложного в доступе к элементу через класс или id? программисту вообще не нужно будет лесть в вёрстку, если верстальщик грамотно огранизует доступ к элементам
    • 0
      отчасти согласен и тоже больше нравится вид onclick="return saw(866650);"
      Не нужно назначать сотни ID и CLASS только ради добавления события к этим элементам и главное не перепутать их друг с другом.
      Мне кажется действительно удобнее для программиста делать onclick в теле элемента
      • +1
        Для программиста правильно не так, как удобно... «правой ногой за ухом», а так, как умные головы придумали. Запутывание логики, данных и представления до добра никогда не доводило.
        Имхо, ради сиюминутной быстроты не стоит уходить от правильных принципов, даже если они кажутся в данном варианте излишеством и тут «и так сойдет». Потому, что сейчас onclick накидал в html-е быстренько, потом style накидал, потом забыл, проект задвигался, что-то другое повесили и покатилась грязь багов...
        • 0
          Давайте чтобы не спорить сойдёмся на том, что если вёрсткой и программированием занимается один человек, то ничего плохого в onclick нет. Если же это разные люди, то да - удобней разделять.
          Что касается style - я иногда употребляю и его при условии если позиция всего одна. Если их становится больше - выношу в css
          • 0
            Сегодня он один и накатал миллион строк кода, потом заманался и появился напарник или ушел и передал проект другому или... вариантов можно придумать вагон и тележку, впрочем если нет желание расти и переходить на серьезные проекты и устраивает ситуация «наколенной» сборки сайтика из десятка строчек JS за ночь, то и onclick «покатит» и style и tabl-ы. Нахерачил, получил бабки и забыл как страшный сон ;)
            • 0
              что же по вашему habrahabr, vkontakte или rbc несерьёзные проекты?
              • 0
                Ну зачем переходить на примеры?
                Ну хорошо, если уж начали, то разве все вышеперечисленные проекты абсолютно безглючные?
                На волне популярности любой веб проект может взлететь до небес и только программисты будут знать какой еб...ей заканчиваются попытки дальнейшего развития...
              • 0
                Чем опасны примеры, так это тем, что в основном не знаешь насколько трудно (или наоборот легко) даются изменения в них
        • 0
          Что можно другово навесить на ссылку? У вас одна ссылка 2 действия делает обычно? Без примеров сложно ваши аргументы воспринимать.
          • 0
            И два действия и десять и... да что угодно может в последствии на нее навешаться. Моя позиция и мои аргументы сводятся к тому, что сегодня ты помнишь о том, что смешал логику и представление, а завтра уже нет.
            • 0
              почему то Вы слишком категоричны.
              не думаю, что вешанье на onclick это такое уж смешение логики и представления.
              точнее для меня это вообще нифига не смешение, а нормально.. вот что нет href для людей без js - это упущение.
              хотя конечно все зависит от проекта.
              • 0
                Видать потому и категоричен, что на грабли эти уже наступал. ;) Впрочем, уже бесполезный спор получается. Вы так привыкли и делаете и небо вам судья и если вспомнить шутку, то для мужика главное ни не наступать на грабли, а не наступать на детские грабли.
      • 0
        стили тоже удобнее в теле задавать. удообней верстать таблицей и не задавать заголовки статьям.
        • –3
          Вы говорите глупости, намекая что onclick такая-же глупость, при этом не доказывая этого. Не всем это так очевидно, и мне тоже.
          • –2
            да бросьте! какие глупости? разве таблицей не удобней верстать?
            • –2
              Я думал вы более адекватны :(
    • 0
      Да нет, я про такую штуку вычитал в одной из книжек по JavaScript, переведенную кстати на русском. Сам пробовал так делать, не это не сложно. Привыкаешь, это скорее вопрос религии, пользовать или нет.
      Когда много полей в форме, и знаешь какие id каким полям дал, удобно события назначать в одном месте java-скрипта, не лазить по всей форме, особенно, когда она не очень компактна.
    • +2
      Мухи тодельно, макароны отдельно - это современны хороший тон.
      Это примерно тоже что прописывать инлайн стили каждому элементу.

      Кроме того это более гибкий способ. И, вообще, чем больше разделение тем лучше. Особенно когда команда состоит не из одного человека.
  • 0
    В PrototypeJS - на самом деле лутше так :)

    $$('.show_reply_form').invoke('observe', 'click',showForm);
    • 0
      Возможно. Я сам jQuery использую просто. А на прототипе привел код лишь потому, что он "ближе" к стандартным event'ам JavaScript'а.
      • 0
        в любом случе делать присвоение element.onclick = 123; не стоит вообще.
        • 0
          123 туда точно присваивать не стоит. :) А вот function вполне можно, если вы уверены, что никаких действий там больше не будет.
          • 0
            123 я написал для примера, не передергивайте. А уверенным в том, что никто туда борльше событий вешать не будет, уверенным быть нельзя. Особенно когда на сайте много скриптов. Кто-нить кого-нить перезапишет и долго будете мучаться в поисках бага.
            • 0
              Мне что, переписать статью с использованием addEventListener?
              • –1
                Дело Ваше, но я бы дописал.
                Но зачем addEventListener? в Prototype есть observe, в jQuery просто click(). Вообще я бы написал: что прямое присвоение событий — зло. Бага с перезаписью window.onload — вообще распространненая проблема, когда сторонние библиотеки его перезаписывают. Так что новичков сразу можно предупредить, чтобы не наступали на грабли.
                • 0
                  Сделал.
  • 0
    Ссылки такого вида уже существуют, в почтовых уведомлениях ссылки "ответить" с окончанием
    .html?replyto=%Comment_ID%#comment%Comment_ID%
  • 0
    спасибо за отличную и полезную статью!
  • 0
    JQuery сейчас "рулит", по функционалу и популярности.
    • 0
      и по производительности тоже
      http://webo.in/articles/habrahabr/35-css…
      • 0
        Давайте не будем холиварить? :) Я сам jQuery использую, но уважаю и другие JS-фреймворки.
  • 0
    за что люблю подобные посты - в них много продуктивных комментариев :)
  • 0
    Мне кажется вы переборщили в статье.
    Я не стану делать два варианта интерфейса, чтобы пользователям которые хз почему отключили JS было удобно. Отключили JS, значит сами сделали этот выбор.

    Некоторые моменты можно учитывать, но тратить большое количество времени из-за небольшого количества пользователей нерационально.

    За это время можно написать кучу новых функций и привлечь больше посетителей, чем жалкий процент без JS, мало того, новые функции улучшат работу постоянным посетителям

    PS: "В рунете я подобных статей не встречал" - такие статьи были на хабле, а если воспользоваться поисковиком, то их будет огромное количество! И про использование Да и про jQuery с Protoтайпом информации навалом.
    • 0
      1) Юзабильность без JS
      2) стиль написания (вместо атрибуты или эвенты).

      Кстати насчет пунка 1, иногда еще нужно давать возможность открыть ссылку в новом окне. Этот вариант тоже полностью подходит под статью.
  • 0
    Я такому научился из книжки "DOM Scripting. Web Design with JavaScript and the Document Object Model" от Jeremy Keith. Всем советую, отличная книжка.
  • +3
    Замечательно, что стали развивать тему ненавязчивого JavaScript-программирования! И пример подобран удачный.
    Однако, небольшое замечание насчет назначения обработчика события (onload или DOMContentLoaded - не важно).
    Таким вот определением window.onload = function() {} вы просто затираете все обработчики, которые висели на событии раньше. При правильно подходе нужно использовать addEventListener и attachEvent (или библиотечный вариант). Лично я использую функцию addHandler.
    Возможно упрощение сделано в целях повышения "читабельности" примера, но данный код может уйти в массы и породить неприятные ошибки.

    А в целом +1, никаких href="javascript:..." и даже onclick="..." в коде быть не должно. Разделяй и властвуй! JavaScript-программист должен работать с js-файлами, а не копаться в HTML-шаблонах. Даже если совмещает свою должность с версткой.
    • 0
      Да, с window можно было поработать через addEventListener, но... Это, как ты правильно заметил, всего лишь пример. Последний код я вообще привел лишь с той целью, чтобы показать - как это ВООБЩЕ можно подключить через внешний файл.
  • +1
    Можно (и нужно) пойти дальше.

    Во-первых, раз уж разделяете структуру и оформление, то из этой строчки
        <div style="display: none" id="reply866650">

    нужно убрать инлайн-стиль и в обработчике нажатия добавлять блоку класс, который делает его видимым через css.

    Во-вторых, в опущенном вами для лучшего понимания фрагменте кода есть форма, которая вместе с содержащим блоком весит 554 байта:
        <div style="display: none;" id="reply867362">
    <form action="http://habrahabr.ru/blog/javascript/42876.html?replyingto=867362#comment867362" method="post" id="form867362" onsubmit="return SubmitComment(867362,this);">
    <textarea class="input_comments_reply" name="comment" id="reply867362ta"></textarea>
    <input id="cpb867362" onclick="PreviewComment(867362);" value="предпросмотр" type="button">
    <input id="csb867362" name="send" value="добавить" type="submit">
    <div id="preview867362" class="comment_preview"></div>
    </form>
    </div>

    Сейчас в этом посте 62 комментария, то есть около 33 КБ одних этих форм (сжатие пока не учитываем). А вес форм на страницах с комментариями за сотню сравним с весом хорошо оптимизированного сайта.

    Так почему бы не создавать форму при нажатии на ссылку? В таком случае не потребуется даже класс для управления видимостью блока. А скрипт в онлоаде полегчавшей страницы быстрее навешает обработчики нажатий.
    • 0
      Простите за порванную страницу.
    • –1
      И вся идея inobtrusive пойдет лесом :)
      • 0
        Наоборот, из страницы уберется код, написанный специально для использования в скрипте, и она остается такой, как была бы, не используй мы JavaScript вовсе.
        • –1
          А как вы собираетесь "создавать форму при нажатии на ссылку" без применения JS? Открывать новую страницу?
          • 0
            Прочитайте внимательнее пост.
    • 0
      > нужно убрать инлайн-стиль и в обработчике нажатия добавлять блоку класс, который делает его видимым через css.
      Вот как раз про CSS и ненужность вот таких инлайн-стилей говорилось и писалось уже кучу раз. К моей теме это прямого отношения не имеет, согласись? :)


      > Так почему бы не создавать форму при нажатии на ссылку? В таком случае не потребуется даже класс для управления видимостью блока. А скрипт в онлоаде полегчавшей страницы быстрее навешает обработчики нажатий.
      Цель была не в оптимизации. Если бы я оптимизировал - я бы сразу создал одну форму для всех комментариев, а потом бы переносил её в нужное место с помощью JS.
      • 0
        А в чем была цель? Если для работы скрипта нужны килобайты html-кода, да еще вперемешку с css, это уже навязчивый JavaScript :)
  • +1
    Введение в ненавязчивый javascript на примере jQuery-ролловеров http://in.jetstyle.ru/ru/81518
  • +1
    Я конечно понимаю, что статья не ставила задачу лучшего решения, однако сделаю пару замечаний.
    Во-первых, пока не загрузится вся страница уже загруженные ссылки будут работать как без JS, то есть уводить на страницу с формой. Мелочь, но не приятно. Во-вторых слишком много действий, вы перелопачиваете весь DOM только для того чтобы повесить десяток другой обработчиков. К тому же зачем вешать столько обработчиков? В-третьих, если вы там же JS поменяете DOM, то есть добавите например новые комментарии (как это происходит на хабре), то новые ссылки не будут работать. Или же придется обходить новые узлы и навешивать обработчики. Согласитесь несколько геморойно.
    Собственно лучшим решением было бы сделать нечто подобное. Добавляем скрипт в HEAD (либо ссылку на него ли сам скрипт ибо он не большой).

    document.onclick = function(e){
    var event = e || window.event;
    var sender = event.target || event.srcElement;
    var m;
    // проверяем что нажали по ссылке, и нужного типа
    if (sender.tagName == 'A' && (m = (sender.href || '').match(/reply\.php\?comment_id=(\d+)/)))
    {
    // действие
    saw(m[1]);
    // отменяем поведение по умолчанию (переход по ссылке)
    if (event.preventDefault)
    event.preventDefault();
    else
    event.returnValue = false;
    }
    }

    Код кроссбраузерный, потому немного лишнего кода присутствует. Лучше вместо onload = function(){} использовать attach/addEventListener во избежание конфликтов, однако это доп. код - для простоты не стал включать.
    В чем преимущество:
    1. Код начинает работать сразу - не нужно дожидаться окончания загрузки страницы.
    2. Всего один обработчик, одна функция.
    3. Если меняется структура документа (добавляются новые ссылки), все по прежнему работает, никаких дополнительных действий не нужно.
    4. Не нужно обходить все узлы документа, чтобы что-то с ними сделать (в нашем случае добавить обработчик). Да вы это напрямую не делаете когда используете jQuery/prototype etc - однако это делают за вас :)
    5. Не нужно больших усилий чтобы добавить обработчики для других элементов или типов ссылок (добавляем в эту функцию дополнительные if или case). К тому же если вы будете навешивать другие обработчики на другие узлы (тип узлов), будет осуществляться еще один проход по дереву документа каждый раз для каждого типа элементов.
    6. Не требуется назначать класс (если конечно, вы не хотите добавить стиль в CSS для таких ссылок) и id, ведь в href уже есть то, что нам нужно.
    7. Если вы используете фреймворк/библиотеку, то данный код будет еще проще и короче, пример на prototype:

    $$(document).invoke('observe', 'click', function(e){
    var sender = Event.element(e);
    var m;
    if (sender.tagName == 'A' && (m = (sender.href || '').match(/reply\.php\?comment_id=(\d+)/)))
    {
    saw(m[1]);
    Event.stop(e);
    }
    });

    Сам на prototype не пишу, так что мог ошибиться и возможно код можно еще больше сократить - но должно быть где-то так :)
    • 0
      C document.onclick несогласен абсолютно. Зачем мне обрабатывать клики по документу? К тому же этот подход неуниверсален ну ни капельки:
      if (sender.tagName == 'A' && (m = (sender.href || '').match(/reply\.php\?comment_id=(\d+)/)))
      Это условие вполне может быть true на других элементах, которым я бы не хотел менять стандартную функцию onclick. Так что так как написали вы я бы делать не стал ни при каких обстоятельствах.


      > 1. Код начинает работать сразу - не нужно дожидаться окончания загрузки страницы.
      DOMContentLoaded

      > 2. Всего один обработчик, одна функция.
      У меня тоже одна вообще-то ;)

      > 3. Если меняется структура документа (добавляются новые ссылки), все по прежнему работает, никаких дополнительных действий не нужно.
      А если меняется href у ссылок? :) Да и опять же - я хочу использовать только КОНКРЕТНО УКАЗАННЫЕ элементы. Если действительно эти элементы выбирать здесь - получиться ужасающий код (проверка - подходит ли текущий тег A под указанной мной селектор).

      > 4. Не нужно обходить все узлы документа, чтобы что-то с ними сделать (в нашем случае добавить обработчик). Да вы это напрямую не делаете когда используете
      См. предыдущий ответ.

      > 5. Не нужно больших усилий чтобы добавить обработчики для других элементов или типов ссылок (добавляем в эту функцию дополнительные if или case).
      Ох уж мне эти ваши if'ы. Зачем мне это делать, если я могу просто указать - каким элементам что нужно делать?

      > 6. Не требуется назначать класс (если конечно, вы не хотите добавить стиль в CSS для таких ссылок) и id, ведь в href уже есть то, что нам нужно.
      Я href мог бы использовать и у себя. href или id - это непринципиально.
      • +1
        Кхм - видимо вы чего-то не допонимаете.

        C document.onclick несогласен абсолютно. Зачем мне обрабатывать клики по документу?

        Это вовсе не накладно и проще. Событие 'click' все равно всплывает к document хотите вы этого или нет, помоему проще ловить событие именно тут нежели на каждом элементе отдельно.

        Это условие вполне может быть true на других элементах, которым я бы не хотел менять стандартную функцию onclick.

        Данный код и не меняет "стандартную функцию onclick", он всего лишь делает свою работу, если условие true - при этом навешанные на элементы onclick так же будут работать. Если вы боитесь за "другие элементы", сделайте более точным условие. Ваше $('.show_reply_form').click(showForm) так же может зацепить не то что нужно.
        1. DOMContentLoaded срабатывает тогда когда загружена вся страница целиком, то есть весь HTML файл. Современные браузеры не дожидаясь полной загрузки HTML начинают его "отрисовывать" и пользователь может взаимодействовать со страницей. При этом ваш код работать не будет пока браузер не загрузит страницу целиком.
        2. Функция объявлена один раз - это верно. Однако создается множество ее экземпляров - для каждого элемента своя копия функции или обертка на нее (в лучшем случае делается ссылка).
        3. Если меняется href, в чем проблема? Проверяется элемент, только тогда когда по нему кликнули. Я не совсем понял про вторую часть. Функция срабатывает по клику, при этом проверяется по какому элементу кликнули, если подходит под требуемое описание - выполняется действие. И все. Не будет клика - не будет действия. Никаких выборок не производится. Селекторы? Какие еще селекторы? Зачем они тут?
        4. Угу. Потрасируйте вашу строчку - посмотрите какие действия делает jQuery/prototype.
        5. Ваш фреймворк видимо интуитивно понимает что это нужные элементы? ;) Тот несчастный if проверяет соответствие заданым правилам более эффективнее (читай быстрее) без всяких переборов. Проверка могла быть и такой:

        if (sender.className.match(/\bshow_reply_form\b/))
        ...

        И для этого не требуется разбор выражения ('.show_reply_form'), обход всего документа и т.п. В итоге ваш фреймворк получит тот же if обернутый в функцию, и будет проверять его на всех элементах документа.
        6. Верно, что-то вроде этого:

        $('[href^=reply.php?comment_id]')

        только сколько лишней работы по выборке, а потом еще обрабатывать href чтобы выбрать id.

        ЗЫ откройте для приличия код jQuery или prototype, почитайте как это работает, потрасируйте вашу строчку тем же firebug'ом, посмотрите сколько кода выполняется для достижения результата. Или же воспользуйтесь профайлером firebug'а (поставьте в начало кода console.profile(), а в конец console.profileEnd() - посмотрите результат в консоли, сколько всего вызывается). А ведь пользователь может ни разу не нажать ссылку -> вы делаете множество ненужной работы.
        • 0
          > 1. DOMContentLoaded срабатывает тогда когда загружена вся страница целиком, то есть весь HTML файл. Современные браузеры не дожидаясь полной загрузки HTML начинают его "отрисовывать" и пользователь может взаимодействовать со страницей. При этом ваш код работать не будет пока браузер не загрузит страницу целиком.
          Ну я даже не знаю, что ответить... Вы успеваете что-то сделать, пока браузер что-то показывает, но еще не догрузил html? Я - нет...

          > 2. Функция объявлена один раз - это верно. Однако создается множество ее экземпляров - для каждого элемента своя копия функции или обертка на нее (в лучшем случае делается ссылка).
          Функция одна, она однажды объявлена и никаких множеств экземпляров нет и не будет.

          > 3. Если меняется href, в чем проблема? Проверяется элемент, только тогда когда по нему кликнули. Я не совсем понял про вторую часть. Функция срабатывает по клику, при этом проверяется по какому элементу кликнули, если подходит под требуемое описание - выполняется действие. И все. Не будет клика - не будет действия. Никаких выборок не производится. Селекторы? Какие еще селекторы? Зачем они тут?
          Я выбираю элементы ПО СЕЛЕКТОРАМ а не по href'ам (если вы делаете по другому - ваше право, я выбираю ПО СЕЛЕКТОРАМ, меня href'ы ВООБЩЕ не интересуют в данном случае). Для полноценной проверки на принадлежность элемента селектору... Ну, в общем, я выше писал.

          > 4. Угу. Потрасируйте вашу строчку - посмотрите какие действия делает jQuery/prototype.
          вообще не понял ничего.

          > 5. Ваш фреймворк видимо интуитивно понимает что это нужные элементы? ;) Тот несчастный if проверяет соответствие заданым правилам более эффективнее (читай быстрее) без всяких переборов. Проверка могла быть и такой:

          В данном случае да. А если там будет не просто класс, а что-то типа "#comments .comment_item .show_reply_form". Вот проверьте - соответствует элемент этому селектору. А теперь прикиньте, что проверка будет НА КАЖДОМ КЛИКЕ. В отличии от моего варианта, где она выполняется ОДИН раз.
          • +1
            1. Представьте себе не у всех инет летает. К тому же у меня такое случается что сеть лагает, и подгружается только часть страницы, потом остальная. Так что среагировать получается, уж поверьте.
            2. Вы уверены? Посмотрите как работает .click() - она вызывает для каждого элемента .bind(), которая вызывается для каждого элемента .event.add(). При этом элементы расширяются (добиваются свойствами и методами), делается обертка для обработки события в которой вызываются привязанные функции, делается замыкание для вашей функции и много еще чего. Посмотрите на код последний двух функций - они весьма не скромные по размеру.
            3. o_O - что по вашему селекторы? Как они работают? Никогда не интересовались? ;) В моей реализации вообще ничего не выбирается, если так что. Ваши селекторы, к примеру:
            '.some_class' превращается в function(element){ if (element.className.math(/\bsome_class\b/)) return true }. Затем для каждого элемента вызывается эта функция. Вот и весь ваш селектор. В чем соль? В том что проще написать $('.some_class') нежели один if?
            4. Не знаком термин "трасировка"? Пошаговое выполнение если в двух словах. Firebug'ом пользовались? Ставите breakpoint на вашей строчке, а потом методично жмете F11 и смотрите что делает ваш фреймворк, чтобы ваша строчка работала.
            5. Хех, в данном случае одним if'ом не обойдешься, нужно перебрать все элементы от sender до document. Не думаю что их будет более 10-30, задача не сложная. Да, немного больше кода. Но это проверка цепочки на каждый клик - будет весьма быстро. А теперь представьте что этот ваш селектор выполняется для КАЖДОГО элемента страницы.
            Вот сейчас набрал в консоли

            document.getElementsByTagName('*').length

            ответ 3198, вот всего чуть больше трех тысяч проверок, тех же самых что делается каждый раз при клике :) фигня при современных то скоростях - да? Чтобы потратить столько же вычислительной мощности, вам потребуется всего то ~3000 раз кликнуть.
            И еще "она выполняется ОДИН раз" - да, но для всех элементов на странице. Их (элементов) больше всегда больше чем число кликов которое, сделает на странице пользователь.
            ЗЫ не все что проще написать работает быстро. Особено когда вы используете фреймворк, который предоставляет УНИВЕРСАЛЬНЫЙ подход к решению задач. Универсальное не может быть быстрым по определению.
            • 0
              1. Ок. Вопрос закрыт.
              2. В варианте jQuery на 100% не уверен. Сейчас в исходники лезть неохота.
              3. Я выше привел пример - для всех элементов, выбранных по селектору "#comments .comment_item .show_reply_form" нужно сделать то, о чем мы, собственно и говорим:
              $('#comments .comment_item .show_reply_form').click(showForm);
              Если сможете реализовать полноценный аналог в своём document.click - честь вам и хвала.

              > Затем для каждого элемента вызывается эта функция.
              В случае с селектором "#comments .comment_item .show_reply_form" - не для каждого мы выбираем элементы по селектору "#comments", потом выбираем уже из тех, которые были выбраны и т.д. Там всё отнюдь не так просто, как вам кажется.

              4. Не понял того, что вы написали. Я строчку "потрассировать" не могу - она строчка. Теперь хоть понял, что вы имеете ввиду. А вообще - какая разница, что там фреймворк делает? Для того, чтобы реализовать сравнимый функционал (проверки - подходит ли выбранный элемент по селектору) вам прийдется реализовать примерно то же самое. :)

              5. Хехе. Я могу несколько десятков миллисекунд подождать пока выполнится этот "пробег". Если вы считаете, что это "неосмотрительная трата времени" - что ж, считайте так дальше.

              > Универсальное не может быть быстрым по определению.
              А кто вам это сказал? :) Это всё чушь полнейшая. Универсальное ОЧЕНЬ ДАЖЕ может быть быстрым. Реально быстрым.
              Другой вопрос, что заточенная под одну функцию программа будет быстрее (не факт, но в абсолютном большинстве случаев это так). Но это отнюдь не говорит о том, что универсальная будет МЕДЛЕННОЙ.
              • 0
                2. я как раз про jQuery и говорю
                3. коммент ниже - насчет jQuery не знаю, какая там функция но думаю она есть.
                Насчет как это делается (выборка) знаю не по наслышке, потому как писал собственную реализацию, знаю чего это "стоит" браузеру. Реализация кстати местами (на части тестах) обгоняла prototype и практически всегда jQuery, однако base2 самая быстра либа в этом плане - как ни крути.
                4. Да, только решение под задачу будет компактнее, не будет зависеть от фреймворка да и работать быстрее тоже :)
                5. Вы просто не писали больших приложений ;)
        • 0
          Да, кстати, насчет кучи ненужной работы. Эта работа достаточно быстрая, если вы сами посмотрите на результаты slickspeed-тестов. Это раз. И два - скоро эта работа будет во всех новых браузерах выполняться в ~100 раз быстрее за счет нативного querySelectorAll.

          На этом спор предлагаю закончить.
          • 0
            Мда уж, мы все равно будем делать по своему? ;)
            кстати если уж вам так понадобились селекторы , то делайте это рационально. измените тот самый if на

            if ($(element).match('.some_class'))
            или
            if ($(element).match('#comments .comment_item .show_reply_form'))

            это для prototype. для jQuery, я думаю есть что-то подобное.
  • +1
    Статья неплохая - уверен многим она пригодится.

    По делу.
    Можно долго спорить о том что делать с людьми которые выключают JS и кто они, я лишь напишу свою имху - эти люди извращенцы, отключенный JS просто понт. Можно также гордиться тем что ты никогда не включаешь CSS, отключил или загрузку картинок. Помоему это чушь. Сёрфить по инету с отключенным яваскрипт всё равно что заниматься сексом используя деревянный презеверватив(не пробовал, просто в голову пришло почемуто =)). Неудобно обеим сторонам, удовольствия от процесса никакого.

    Единственный плюс ненавязчивого JS - это чистота HTML кода, в котором весь яваскрипт вынесен в файлы. Тут я всеми руками за. Это на мой взгляд реально круто. Так что если руководствоваться этими соображениями - честь вам и хвала.
    • 0
      На самом деле плюсы есть и еще - смотрим jQuery и её обработчики событий, например, - в этой библиотеке можно не устраивать проверок типа:

      function(event) {
      event = event || window.event;
      }

      Так как всё за нас сделает библиотека.

      Т.е. появляется возможность ввода и использования своих абстракций над тем, что предлагает нам браузер + усреднение функциональности JS в различных браузерах. Причем это не для какой-то конкретной процедуры - а практически "глобально".
  • 0
    пишите что хотите, джава скрипт отключен. Вот только для коммента включать приходится :-(

    зачем вообще на таком сайте как хабр джава скрипт? рекламу показывать?

    нужен только текст.

    для тех кто против - включите себе скрипт чтобы за курсором мышки буквы "вася пупкин" тянулись и еще бегущюю строку в тайтл и в статус бар.
    • 0
      помоему проше включить и выключать только для сайтов ломаюших статус бар (а лучше не посешать таких вообше), чем выключить и включать только для хабра, если вы об этом
  • 0
    Статья пригодится многим. Но она навязывает использование ненавязчивого джаваскрипта. Вы выбрали не правильный пример для описания. Сто тысяч раз говорилось о том, почему нельзя использовать в принципе. Зачем описывать как разработчику лечить собственные ошибки, лучше их не допускать. К томуже некоторые тутже кинутся менять на что-то типа , а это совершенно не нужно.
    Предлагаю Вам дополнить статью более практичными примерами использования ненавязчивого джаваскрипта, такими как этот
    http://www.frequency-decoder.com/demo/date-picker-v2/
    как впрочем и любым из этих
    http://www.frequency-decoder.com/the-language-in-the-lab/
    • 0
      Чёрт, хабр отрезал все теги
      "Сто тысяч раз говорилось о том, почему нельзя использовать a href="javascript:" в принципе."
      "К томуже некоторые тутже кинутся менять td onclick="myFunc(this)" на что-то типа td class="show_myFunc" id="show_myFunc_object", а это совершенно не нужно."
      • 0
        Просто решил показать на примере. Может, и немного неудачно. :)
        Показывать что-то типа

        $('input.date').blur(function(){
        // проверка на корректную дату
        });

        Этот пример я видел во многих местах, и, к сожалению, есть случаи, когда этот пример применяют, а всё остальное делают - obtrusive'ным.
  • –3
    Ммм… «Я выключил компьютер, потому что он излучает и у меня перестал отображаться сайт!». Горбатого сами знаете, что исправит. Применять «ненавязчивый JavaScript» имеет смысл там, где планируются посещения с мобильных устройств или там, где можно написать «C», за то же время и с той же функциональностью, что и «Без».
  • 0
    вот вам пример "как делать нельзя"... наткнулся - плакалЪ...
    http://www.germetik-plus.ru/
    • 0
      Спасибо, показательно.

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