Создание одностраничного ajax-приложения с поддержкой History API (и без нее)

    Судя по комментариям в этой статье, создание веб-приложений с возможностью аякс-навигации является интересной для сообщества темой и пока еще немногие сталкивались с подобной задачей. Я расскажу о ее решении с помощью небольшой библиотеки под названием jQuery-Pjax (либо моего форка ее).

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

    Основные особенности

    • навигация по сайту и обработка форм без полной перезагрузки страниц
    • чистые url, доступные для прямого доступа
    • поддержка #!/hash для устаревших браузеров (добавлено в моей версии)
    • работа с кнопками «назад» и вперед» для современных браузеров
    • а теперь и для старых — благодаря benalman.com/projects/jquery-hashchange-plugin
    • похоже, есть проблемы с ИЕ7 (спасибо Nc_Soft) (тем более, большое спасибо за участие Nc_Soft) и, возможно, opera 11.5 (пока не могу подтвердить, но нахожу крайне удивительным из-за dev.opera.com/articles/view/introducing-the-html5-history-api) — сообщение artishok — проверено и работает на сборке 1074 (not_ice)
    • imsamurai (https://github.com/imsamurai) предложил улучшения библиотеки (и я радостью слил изменения): встроенная функция для отправки форм, улучшения работы с хешами и более развитая система триггеров. (у imsamurai, к сожалению, нет аккаунта на хабре — будем рады помощи)

    Ссылки


    Принцип работы Pjax


    Основа — раздельный вывод и кеширование обычных запросов и запросов со специфическими заголовками. То есть, в контроллере перед выводом шаблона страницы, необходима проверка наличия заголовка X-PJAX, например, так:

    PHP:
    if (!isset($_SERVER['HTTP_X_PJAX'])
    { // here is regular-kind load }
    else
    {    // here you don't print page layout — just the page }


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

    $('#pjaxcontainer a:not(.logout-link,.login-link,.login_link,[id*="login_link"],[href*="#"],[target="_blank"],'
          +'[href$="mp3"],[href$="jpg"],[href$="jpeg"],[href$="gif"],[href$="png"],[href$="doc"],[href$="pdf"])')
       .pjax('#pjaxcontainer', {
          timeout: 0
       });


    Для обработки форм мы используем:

    $('#pjaxcontainer form').live('submit',function(a){
    
         // display loading message
          $('#loading-shade').show();
    
          if( !$(a.target).attr('action'))
             a.target = $(a.target).closest('form');
    
          data = $(a.target).serialize();
    
          cont = $('#pjaxcontainer');
    
          $.ajax({
             type: "POST",
             url: $(a.target).attr('action'),
             data: data,
             beforeSend : function(xhr) {
                return xhr.setRequestHeader('X-PJAX','true'); // IMPORTANT
             },
             success: function(msg){
                cont.html(msg);
                $('#loading-shade').hide();
             },
             error: function(a,b,c) {
                $('#loading-shade').hide();
             }
          });
    
          a.preventDefault();
          return false;
       });


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

    Также есть два полезные события:
      $('body').bind('start.pjax',function() {
          setTimeout("$('#loading-shade').hide();",2000); // to be sure that loading message hides
          $('#loading-shade').show();
       });
       $('body').bind('end.pjax',function() {
          $('#loading-shade').hide();
       });


    Более подробную информацию о Pjax вы найдете в README.md на github.

    Допиливание хеш-навигации


    К создателю библиотеки несколько раз обращались с просьбой добавить поддержку хешей, но его позиция оказалась принципиальной ( github.com/defunkt/jquery-pjax/issues/3#issuecomment-986233, github.com/defunkt/jquery-pjax/issues/3#issuecomment-1353555, github.com/defunkt/jquery-pjax/issues/3#issuecomment-1354589). Потому я слегка модифицировал ее, добавив автоматический переход на #!/хеши и свободную конвертацию адресов двух типов.

    Однако, в моей модификации необходимо указать два параметра:
    $.siteurl = 'http://yousite.com';
    $.container = '#pjaxcontainer';


    Заключение


    Таким образом мы в несколько строк кода реализуем одностраничное веб-приложение. Буду благодарен за комментарии и любые багтреки.

    Feel free to fork and roll your own © defunkt

    Спасибо Terion за наводку.

    image => image

    Планы


    Работа над этим проектом оказалась крайне познавательной — потому есть, что рассказать еще. В следующей статье я напишу о создании многофункционального аудио-видео плеера с плейлистом на основе jPlayer (http://jplayer.org/).



    Важные комментарии


    Devgru, 12 июля 2011, 20:43 #

    Восклицательный знак — специальный сигнал для поисковиков, что этот контент может быть доступен и другими путями. Без него хеш-навигация для поисковика действительно превращается в чёрный ящик.

    Devgru, 12 июля 2011, 23:29 #

    Не совсем так.
    Используется дополнительный урл для обработки, см спеку и изначатьльную инициативу.

    И да, вроде бы пока это только гугл.
    Я не использовал этот функционал, только читал.

    Share post

    Similar posts

    Comments 63

      +2
      давно пользуюсь этой техникой, автору больше спасибо за подробное описание
        0
        Я с ней познакомился ровно когда пришлось использовать :)
          +3
          Автор, главное, допилил ее, чтоб в IE работало)
          +3
          Спасибо за статью.
          Небольшое замечание: как многократно писали, не стоит пытаться «помочь» указанием чего-то вроде div#pjaxcontainer. Потому как в этом случае сначала выберутся все div-ы, и лишь потом будет выполнен поиск по id. А поиск по id сам по себе гораздо быстрее (самый быстрый из всех поисков по DOM).
            0
            Верно, чего это я. Сейчас поправлю
            0
            Ниче не понял а где mp3 плеер? о_О
              +1
              Зависит от того, что вы хотели спросить. Если вы о сайте, так он в верхнем правом углу должен быть, если о статье — совершенно верно, его там нет!

              Если вам интересно, могу отдельную статью посвятить созданию mp3-плеера (или видео-плеера) с плейлистом и дополнительными функциями на основе jPlayer (http://jplayer.org/)
                0
                Оооо просим-просим, очень интересно у меня с ним долгой дружбы не вышло… особено интересно если рассмотрите варинат мулти-инстанса, когда кучка объектов-плееров jPlayer на одной странице.
                  0
                  Сам видео-ролик на странице-примере — чумовой :)
                    0
                    Кстати под FF 3.6.18 то не работает что-то…
                      0
                      Я думаю, что нет смысла держать на компьютере с доступом в интернет Firefox версией ниже 4-й. Впрочем, попробуйте зайти на мой сайт — библиотеку мы с imsamurai модифицировали для работы с браузерами, не поддерживающими pushState. Так все должно работать.

                      Просто, у меня сейчас нету сервера, чтобы выложить там пример. Но я займусь этим
                        0
                        Речь ни о нас с вами, а о пользователях, которые и IE любят, и обновлются не так шустро как бы нам хотелось. FF 4-ка зарелизилась то не так давно.
                          0
                          Тот пример, что вы смотрите — оригинальная библиотека defunkt'а. А он принципиально отказывается поддерживать не-pushState браузеры. Мы с imsamurai эту поддержку добавили — в виде hash-навигации.
                            0
                            А, тогда ясно, лучше б тогда полноценный пример куда нить выложили потыкать в браузерах.
                              0
                              Ох… Посмотрите третий пункт в разделе «ссылки». Там предпоследняя версия, без улучшений от imsamurai
                              0
                              Да, вы правы извините, не доглядел, ваш то пример с Amsterdam Music работает.
                                0
                                Знаете, я уже заметил, что это распространенная ошибка :)
                                  0
                                  Жара, видать мозги плавятся, три из четырех ссылок почему пропарсились головой как ссылки на реп. и только последняя на пример, тут чистая невнимательность.
                          0
                          Или скачайте с github последнюю версию — я еще не обновлял исходники сайта.
                        0
                        Просто плеер не имеет отношения к pjax. Он просто помещен вне обновляемой зоны и навигация по сайту никак не затрагивает его работу.
                          0
                          Ага теперь понял :)
                          Спасибо!
                        0
                        Хм, а как быть с индексированием в поисковиках?
                          0
                          Все урл доступны для поисковиков. Вы можете посмотреть: у каждой ссылки есть href и, при входе на него напрямую, браузер не отправляет заголовок X-PJAX и получает страницу целиком.

                          Все индексируется лучше некуда.
                          +1
                          Мне не нравится одна вещь с хеш-навигацией.
                          Вот пример: кто-то с pjax навигацией дает ссылку кому-то с ие7
                          amsterdamusic.com.ua/profile/85
                          Этот кто-то с ие7 заходит по ссылке и его хеш начинает меняться вот так
                          amsterdamusic.com.ua/profile/85#!/profile/85
                          Можно сразу редиректить если хеш нужен и его нет с amsterdamusic.com.ua/profile/85 на amsterdamusic.com.ua/#!/profile/85, но тогда такая ссылка будет для поисковика совсем не тем, чем хочется.
                            +2
                            Восклицательный знак — специальный сигнал для поисковиков, что этот контент может быть доступен и другими путями. Без него хеш-навигация для поисковика действительно превращается в чёрный ящик.
                              0
                              site.ru/#!hash для поисковиков это site.ru/hash?
                              Или только для гугла?
                                +2
                                Не совсем так.
                                Используется дополнительный урл для обработки, см спеку и изначатьльную инициативу.

                                И да, вроде бы пока это только гугл.
                                Я не использовал этот функционал, только читал.
                                0
                                Не подскажете, а я ерунду не сделал, что слеш ставлю после восклицательного знака? :)
                                  +1
                                  Насколько я этот механизм понимаю ­— не глупость. Ставить можно что угодно, оно будет передано через _escaped_fragment, см. ссылки которые я запостил выше.
                                    0
                                    Большое спасибо. Выходит, нужно добавить в контроллере запросов вида /_escaped_fragment_=… и превращать их в /…
                                      0
                                      Не совсем. Не «превращать их» а отдавать поисковику контент, который увидит юзер, перешедший по аякс-запросу.
                                      Т.е. схема такая:
                                      — поисковик видит ссылку, в которой есть #!
                                      — поисковик идёт по escaped_fragment-урлу и запрашивает страницу (в контектсте: «а что увидит юзер с таким аякс-запросом?»)
                                      — ваш сервер должен выдать ему соответствующий контент (на сколько я понимаю, стоит использовать минимальное html-обрамление)
                                      — поисковик связывает выданный контент с ajax-запросом и может показать его в выдаче
                                        0
                                        Ну, я о том же :)
                                0
                                Вы совершенно правы, сейчас что-нибудь придумаю
                                  0
                                  Думаю, теперь я разобрался с проблемой — как считаете? К слову, пока ковырял, узнал, сколько ошибок можно поместить в 3 строки :)
                                    +1
                                    Набрал в ие7 amsterdamusic.com.ua/profile/85 получил бесконечные редиректы
                                      0
                                      Оу, а в восьмом работало. Спасибо, буду разбираться
                                        0
                                        Не могу воспроизвести. Может, у вас закешировался скрипт?
                                          0
                                          Мы установили, что, таки-да, кеш виноват.
                                    0
                                    При переходе по ссылке amsterdamusic.com.ua/#!/profile/88 я вижу профиль музыканта, а при обновлении страницы уже вижу топ5. Да и вообще находясь на любой странице, после обновления я вижу топ5.
                                      0
                                      Скажите, пожалуйста, что у вас за браузер — обязательно проверю
                                        0
                                        Opera Next
                                        Версия: 11.50 alpha
                                        Сборка: 1009
                                          +1
                                          Opera 11.50, сборка 1074 — все ок.
                                            0
                                            Спасибо. Видимо, билд неудачный
                                      0
                                      imsamurai (https://github.com/imsamurai) предложил улучшения библиотеки (и я радостью слил изменения): встроенная функция для отправки форм, улучшения работы с хешами и более развитая система триггеров. (у imsamurai, к сожалению, нет аккаунта на хабре — будем рады помощи)
                                        +1
                                        добавьте плиз в README вариант использования в Rails

                                        class ApplicationController < ActionController::Base
                                        layout Proc.new { |controller| request.headers['X-PJAX'] ? false : 'application' }
                                        end
                                          0
                                          Добавил. Сойдет?
                                          0
                                          ага, <зануда>в следующий коммит, добавьте пару пробелов перед layout </зануда>
                                            0
                                            Прикольный плагин. Правда развиваться ему еще есть куда.
                                              0
                                              С радостью выслушаю ваши предложения.
                                                0
                                                Ну, в вашем yii варианте расширения не предусмотрен переход по анкорной ссылке на нужную страницу (когда страница открывается в первый раз).
                                                  0
                                                  Вы про это: amsterdamusic.com.ua/#!/artists?
                                                    0
                                                    Ага, при первом открытии страницы нужен редирект на нужную.
                                                      0
                                                      Проверил эту ссылку в Хроме и ИЕ8 — работает. И никто до сих пор не жаловался. Что у вас за браузер?
                                              0
                                              1) Она работает, но, дважды подгружается контент страницы. Первый раз — главная страница. А потом уже по аякс — та что нужна. Не лучше ли при первом открытии сайта в самом начале выдачи главной страницы делать редирект на нужную?
                                              P.S.: я код смотрел бегло, но, этого не заметил там.
                                                0
                                                Это и происходит при инициализации pjax. Просто он инициализируется уже после загрузки контента. Это сделано для того, чтобы статично сайт работал и без яваскрипта. Вы видите, как это обойти?
                                                0
                                                Вижу, поищите в инете, решений уйма есть. Просто пока острая нехватка времени.
                                                  0
                                                  Ладно, попробую слету ответить:

                                                  //… тут title и остальное


                                                  А вообще для подобного hash евания страницы есть jquery bbq, он помощнее вроде + он уже включен в yii.
                                                    0
                                                    Блин, забыл про хабрапарсер, вот вариант:
                                                    
                                                    <html>
                                                    <head>
                                                    <script>if(location.hash!=='')location.href=location.hash.replace('!/','');</script>
                                                    //.. Тут тайтл и так далее
                                                    </head>
                                                    //.. Тут все остальное и сам плагин тоже может быть.
                                                    </html>
                                                    
                                                      0
                                                      За BBQ спасибо, надо будет изучить. Но вы ошиблись, я совершенно не знаком с Yii. Я фанат codeigniter и Kohana :)

                                                      Возможно, это оправдано. Проблема только в том, что у пользователей не-pushState браузеров постоянно будет меняться формат url — с хешем и без хеша. Спасибо за замечание, поупражняюсь над этим.
                                                        0
                                                        Понятно, тогда извиняюсь, просто в соседней форуме тоже эта тема поднялась.
                                                  0
                                                  плохо что демки нет
                                                    0
                                                    Реализация фактически не отличается от оригинальной – pjax.heroku.com/
                                                    0
                                                    Демка не только даёт убедиться что технология работает, но и помогает понять почему не работает у меня. А вобще спасибо.

                                                    Only users with full accounts can post comments. Log in, please.