О чем это я?
Так уж сложилось, что я часто смотрю сериалы или различные анимешки и смотрю их много. Но вот со всем этим смотрением возникала одна проблема, это на чем же я остановился. Данная проблема возникала почти ежедневно и иногда приходилось перекачивать серию повторно чтобы убедится, что я это видел. В дальнейшем сию проблему как-то удалось решить с помощью отмечания того, что видел на специализирующихся сайтах. Да вот беда, появилась вторая проблема, не забыл ли я отметить серию и не качаю ли ее повторно. К тому же заходить постоянно отмечать, потихоньку начинало доставать.
Так и была рождена идея нового автоматического сервиса по отмечанию сериалов, аниме и фильмов. Да, сейчас конечно кто-то скажет, что таких сервисов уже туча, что есть и автоматические отмечалки, установи только их плеер, смотри в оригинале чтобы определило, по определенному назови файл и т.д… Скажу сразу, все сервисы были перепробованы или можете считать что просто хотелка захотела ;) Вместе с этим пришло понимание, что это должна быть не только отмечалка серий, а еще полноценный каталог аниме, тв, фильмов с трейлерами, листом того что просмотрел, скриншотами серий, что смотреть дальше, рейтингами и так далее, так что без перфекционизма не обошлось. И одним из ключевых решений было делать сервис полностью с использованием ajax’a. О чём и пойдет речь далее.
Цель
— Загрузка каждого элемента должна происходить блоками не вызывая перезагрузку страницы. То-есть грузить только то что нужно
— Переход назад также недолжен делать перезагрузку страницы
— Загрузка полного урла должна собирать все блоки в единый html без ajax для поисковиков
— Любой ajax запрос меняет url (HTML5)
Вроде бы прописные истинны, да и что его там делать. Да вот не все так просто. Попробуем разобраться.
Структура сайта
Нельзя просто взять и сделать сайт с блочной загрузкой. Если вы решили делать ajax везде, то и структура скриптов должна ему соответствовать.
Как работает среднестатистический сайт.
- пользователь посещает страницу
- достаются все необходимые данные, делается авторизация пользователя, распихивается все по нужным переменным
- делается echo всей этой кухни что мы наварили
В ajax же сайте, каждый блок что планируется подгружаться в дальнейшем должен быть полностью самостоятелен.
Получается:
- пользователь заходит на страницу
- делается авторизация и распихивается по нужным переменным
- делается echo хидера где эти переменные используются
- достаются необходимые данные для следующего блока (скажем список категорий)
- выводится список категорий
И т.д… То-есть те же запросы к базам данных могут встречаться в нижней части страницы. В тоже время каждый блок может проверять были ли эти данные уже загружены до него, и если да, то уже не вытягивать их с базы данных или еще откуда-то.
Хотел бы я сказать что сами переходы по ссылкам на сайте делаются стандартно через $.ajax(…), да вот не так, об этом чуть ниже.
Блочная структура сайта влечет за собой также несколько проблем:
- Проблемы с тайтлами страниц. Если раньше достаточно было в начале скрипта получить все нужные для тайтлов данные, а дальше, просто отобразить всю страницу на основе этих же данных, то теперь мы в начале скрипта незнаем какой контент на текущей странице должен отобразится, так как все страницы меняются через pushState из history.js. Поэтому для тайтлов нужно сделать отдельную обработку REQUEST_URI, что влечет за собой некие дополнительные телодвижения для того, чтобы потом эти данные передавать дальше по дереву блоков. Хотя наши блоки и самостоятельные и не нуждаются в этих данных, но дабы избавится от повторяющихся запросов к базе данных и т.д., то все же эти данные лучше передавать от места генерации тайтла до идентичного (похожего) запроса в блоке. Да, есть вариант с кешированием вывода конечно, но в наш шаблонизатор он никак не врезается.
- Проблема разных запросов. Если раньше можно было взять, допустим, с базы сразу название сериала + список эпизодов этого сериала одним запросов, положить в один массив и отобразить его, то теперь мы должны получать отдельным запросом название сериала и отдельным запросом список эпизодов. Так как это разные блоки и один блок находится в самом верху страницы, а второй где-то после 10 прокруток вниз и любой из этих блоков может быть изменен с помощью javascript’a
Но все эти проблемы решаемы и с заранее хорошо продуманной архитектурой не вызывают каких-либо проблем. Думаю было бы правильно описать нашу архитектуру, но так как здесь действует правило фломастеров, то для этого скорее нужно делать отдельную статью.
Javascript
А теперь самое интересное. Каждый браузер имеет такую кнопку как переход назад. И если у вас не ajax сайт, то никаких проблем с ней нету. Даже если у вас всего пару ajax подгрузок на сайте, то тоже нету с этим никаких проблем. А вот если ваш сайт полностью работает без перезагрузок, тогда на эту кнопку приходится тратить немало сил. Мало того, если сначала написать весь сайт без перезагрузок страниц, а потом в конце решить добавить handle на эту кнопку, то хлопот будет выше крыши.
Большинство разработчиков предпочитают просто отловить событие перехода назад (еще фокус в том, что переход назад ничем не отличается от перехода вперед да и вообще от обычной ajax загрузки) и перезагрузить страниц полностью. Но для нас фанатов идеальных реализаций это явно неправильная дорога. Поэтому решено было написать механизм на основе localStorage с запоминанием страницы полностью. Прелесть подхода в том, что страница грузится моментально, нету никаких подгрузок, но с этим одним плюсом тянется куча других сложностей и проблем:
- Размер localStorage
На каждом устройстве localStorage имеет заранее определенный максимальный размер. Поэтому если ваша страница в него не влезает, то она будет просто откинута и возможность у пользователя перейти назад или вперед просто не будет. Поэтому приходится заранее ограничивать глубину похода пользователя назад или вперед, а также создавать пул страниц. В среднем оно ограничено 5МБайтами, но на мобильных устройствах более-менее безопасно брать 2МБ. И это все при условии, что пользователь сам не tweak’ал его. - Необходимость в различных состояниях страницы перед сохранением
К примеру, вы зашли на страницу где по вашему хотению вылетел popup, в нём вы кликнули еще на одну ссылку и перешли на следующую. И тут вы решили перейти обратно. Конечно же при переходе обратно этот popup также там остался, потому что на момент перехода мы сохранили полную копию страницы с видимым popup’ом. Для решения этой проблемы приходится создавать callback pull запрос, который выполняет все необходимые эвенты при уходе со страницы. При этом состояние javascript переменных нужно сохранять в DOM дереве, иначе при возращении на эту страницу ничего работать так как нужно не будет, потому что при доставании с localStorage мы не достаем значение переменных javascript - Отключить автоудаление javascript'a
А вот здесь уже действительно нас ожидала засада. Дело в том, что когда мы сохранили исходный код страницы в локальное хранилище, то при вытягивании его оттуда нужно также чтобы и javascript’ы были рабочими. Так как у нас блочная структура, то скрипты могут находится в начале страницы, в середине, да где угодно. Кроме этого при загрузке блока нужно еще выполнить всякие $(document).ready(…), $().ready() (ой, это кажется одно и тоже ;))и т.д. Итак, приступим к реализации.Тянем контент через $.ajax(…), вставляет на страницу с помощью $(‘#block’).html(‘контент’); и при уходе со страницы(или входе) сохраняем весь исходный код страницы в локальное хранилище. Дальше при переходе назад ищем в нашем локальном пуле есть ли эта страница, и если есть, то вставляем через тот же .html(‘страница’). Жмем по ссылкам и … ничего не пашет. Javascript не выполнился при загрузке страницы, да и вообще его нету на странице. Где делся?
А вот собака оказывается зарыта в методе .html() из JQuery. При вставке контента он находит javascript, выполняет его, и удаляет из html кода. Таким образом на странице мы имеем чистый html и в локальный пул при уходе также сохраняем чистый html. Поэтому, если нужно вставить HTML код который имеет javascript то вставляем его только через innerHTML = ‘код’;
Сказано сделано, вставили, загрузили, опять смотрим и … ничего не пашет. А теперь мы видим, что javascript есть на странице, но ничего из него не выполняется, он присутствует как обычный HTML. Значит делаем вторые костыли.
Сначала нужно найти все <script src=''></script> теги и повыдёргивать адреса которые должны подгрузиться. Подгружаем их контент в массив, при этом распарсиваем оставшийся javascript код который есть на странице(в блоке).
Дальше нужно сообщить нашему браузеру что у нас есть код который нужно понимать как javascript а не html. Для этого делаем манипуляцию вроде этойif (window.execScript) { window.execScript(scripts); } else { var head = document.getElementsByTagName('head')[0] || document.documentElement; var scriptElement = document.createElement('script'); scriptElement.type = "text/javascript"; try { // doesn't work on ie... scriptElement.appendChild(document.createTextNode(scripts)); } catch(e) { // IE has funky script nodes scriptElement.text = scripts; } head.appendChild(scriptElement); head.removeChild(scriptElement); }
Теперь весь javascript, что у нас был на странице там же и остался, кроме того, браузер его понимает как нужно.
Ах да, чуть не забыл сказать, комментарии можно обрамлять только как /**/, // нельзя так как при кормлении такого кода браузеру через создание элемента, он считает его инвалидом, точнее невалидным ;).
- Повторное выполнение javascript'a при загрузке страниц
Осталось выполнить $(document).ready(..) и вроде все.
$(document).ready(..) JQuery выполняет 1 раз. То-есть, если вы загрузили блок где был код $(document).ready(mysuperfunction(){});, потом перешли куда-то, а потом опять загрузили этот же блок, то второй раз уже функция “mysuperfunction” не выполнится.
Дабы не разбираться в дебрях JQuery и не вытаскивать из него местоположение closure функции переданной .ready() + не зависеть от его внутрянки, каждой автозагрузке дали особое начало имени $(preDOM_*);. Тем самым, когда распарсиваем весь javascript(а мы ведь все равно это делаем), то просто собираем эти функции и потом их выполняем. Конечно при такой реализации нужно следить чтобы функция “mysuperfunction()” не вызвалась дважды (сначала jquery вызвал а потом еще мы догнали и добавили). Поэтому приходится в начале каждой функции добавлять маркер.
Итого код$(document).ready(function(){ //test Console.log(‘test’); });
теперь выглядит теперь так:
function preDOM_mysuperfunction (){ $().markFunction(‘preDOM_mysuperfunction’); /*test новый вид комментария*/ Console.log(‘test’); } $(preDOM_mysuperfunction);
С Javascript’ом вроде закончили.
Подытожим
- Каждый блок который будет меняться на сайте должен быть самодостаточен и не зависеть ни от каких переменных выше. Максимально что он может это использовать уже полученные данные ранее и то если их нету он должен уметь их сам доставать
- Решить проблему повторной загрузки блока (так как .ready() срабатывает только единожды) при помощи своей структуры функций или же выковыривать closure из jquery и потом их вызывать. В любом из этих случаев все равно придется их самостоятельно вызвать
- Отключить кнопку назад (просто полностью перезагружать страницу) или попробовать способ, описанный выше с LocalStorage и распарсиванием всего javascript кода
На этом пока все, надеюсь кто-то узнал хоть что-то полезного из этой статьи и время на нее не было потрачено впустую. Благодарю что дочитали аж до этой строки.
Если кому-то интересно будет посмотреть, как же оно в реальности работает то можете перейти по этой ссылке. Там конечно еще закрытая бета, но пощупать можно. И мой профайл (доступ только после ссылки выше) конечно же чтобы было понятно насколько наболело ;)
P.S. Заранее простите что попытался втыкнуть какой-то юмор и вставил 2 смайлика в столь строгую статью.
Автор: jimmi