Создание одностраничного приложения без фреймворка

  1. Идея
  2. Настройка
  3. Продукты JSON
  4. products.json
  5. index.html
  6. Код JavaScript
  7. script.js
  8. Заключение
  9. Bootstrap Studio

Идея одностраничных приложений (SPA) состоит в том, чтобы создать плавный просмотр, подобный тому, который есть в нативных настольных приложениях. Весь необходимый код для страницы загружается только один раз, и ее содержимое динамически изменяется с помощью JavaScript. Если все сделано правильно, страница не должна перезагружаться, если пользователь не обновляет ее вручную.

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

Примечание. Чтобы запустить этот пример после его загрузки, вам необходим локально работающий веб-сервер, такой как Apache. Наша демонстрационная программа использует AJAX, поэтому она не будет работать, если вы просто дважды щелкнете index.html по соображениям безопасности.

Идея

Мы не будем использовать каркас, но мы будем использовать две библиотеки - jQuery для манипулирования DOM и обработки событий, и Рули для шаблонов. Вы можете легко опустить их, если хотите быть еще более минимальными, но мы будем использовать их для повышения производительности, которое они обеспечивают. Они будут здесь еще долго после того, как хип-клиентские рамки дня будут забыты.

Приложение, которое мы будем создавать, извлекает данные о продукте из файла JSON и отображает его, отображая сетку продуктов с Рули , После начальной загрузки наше приложение останется на том же URL-адресе и будет прослушивать изменения в хэш- части с событием hashchange . Чтобы перемещаться по приложению, мы просто изменим хеш. Это имеет то преимущество, что история браузера будет работать без дополнительных усилий с нашей стороны.

Настройка

Папка нашего проекта

Как вы можете видеть, в нашей папке проекта не так много. У нас есть обычная настройка веб-приложения - файлы HTML, JavaScript и CSS, сопровождаемые файлом products.json с данными о товарах в нашем магазине и папкой с изображениями товаров.

Продукты JSON

Файл .json используется для хранения данных о каждом продукте для нашего SPA. Этот файл может быть легко заменен серверным скриптом для извлечения данных из реальной базы данных.

products.json

[{"id": 1, "name": "Sony Xperia Z3", "price": 899, "specs": {"производитель": "Sony", "хранилище": 16, "os": "Android" , "camera": 15}, "description": "Lorem ipsum dolor sit amet, consitteur adipiscing elit. Nullam tristique ipsum в efficitur pharetra. Maecenas luctus ante в neque maximus, sed viverra sem posuere. Vestibulum lectus nisi, laoreet , feugiat at odio. Etiam eget tellus arcu. "," rating ": 4," image ": {" small ":" /images/sony-xperia-z3.jpg "," large ":" / images / sony- xperia-z3-large.jpg "}}, {" id ": 2," name ":" Iphone 6 "," price ": 899," specs ": {" производитель ":" Apple "," хранилище ": 16, "os": "iOS", "camera": 8}, "description": "Lorem ipsum dolor sit amet, consittetur adipiscing elit. Nullam tristique ipsum в efficitur pharetra. Maecenas luctus ante в neque maximus, sed viverra sem posuere Vestibulum lectus nisi, laoreet vel suscipit nec, feugiat at odio. Etiam eget tellus arcu. "," Rating ": 4," image ": {" small ":" /images/iphone6.jpg "," large ":" / изображения / IP hone6-large.jpg "}}]

HTML

В нашем html-файле у нас есть несколько div, использующих один и тот же класс "page". Это разные страницы (или как они называются в состояниях SPA), которые может показать наше приложение. Однако при загрузке страницы все они скрыты с помощью CSS, и для их отображения требуется JavaScript. Идея состоит в том, что одновременно может быть видна только одна страница, и наш сценарий должен решить, какая она есть.

index.html

<div class = "main-content"> <div class = "all-products page"> <h3> Наши продукты </ h3> <div class = "filters"> <form> Флажки здесь </ form> </ div > <ul class = "products-list"> <script id = "products-template" type = "x-handlebars-template"> {{#each this}} <li data-index = "{{id}} "> <a href="#" class="product-photo"> <img src =" {{image.small}} "height =" 130 "alt =" {{name}} "/> </a> <h2> <a href="#"> {{name}} </a> </ h2> <ul class = "product-description"> <li> <span> Производитель: </ span> {{specs. производитель}} </ li> <li> <span> Хранилище: </ span> {{specs.storage}} ГБ </ li> <li> <span> ОС: </ span> {{specs.os}} </ li> <li> <span> Камера: </ span> {{specs.camera}} Mpx </ li> </ ul> <button> Купить сейчас! </ button> <p class = "product-price "> {{price}} $ </ p> <div class =" highlight "> </ div> </ li> {{/ each}} </ script> </ ul> </ div> <div class = "страница одного продукта"> <div class = "overlay"> </ div> <div class = "preview-large"> <h3> Представление одного продукта </ h3> <img src = "" /> <p> </ p> <span class = "close"> & times; </ span> </ div> </ div> <div class = "error page"> <h3> Sor что-то пошло не так: (</ h3> </ div> </ div>

У нас есть три страницы: все продукты (список продуктов), один продукт (страница отдельного продукта) и ошибка .

Страница всех продуктов состоит из заголовка, формы, содержащей флажки для фильтрации, и тега <ul> с классом «список продуктов». Этот список создается с помощью рулей, используя данные, хранящиеся в products.json, создавая <li> для каждой записи в json. Вот результат:

Продукты

Один продукт используется для отображения информации только об одном продукте. Он пуст и скрыт при загрузке страницы. Когда соответствующий хеш-адрес достигнут, он заполняется данными о продукте и показывается.

Страница ошибок состоит только из сообщения об ошибке, которое сообщит вам, когда вы достигли ошибочного адреса.

Код JavaScript

Во-первых, давайте сделаем быстрый предварительный просмотр функций и того, что они делают.

script.js

$ (function () {checkboxes.click (function () {// Флажки в нашем приложении служат для фильтров. // Здесь при каждом щелчке мы добавляем или удаляем критерии фильтрации из объекта фильтров. // Затем мы вызываем это функция, которая записывает критерии фильтрации в хэш URL. createQueryHash (filters);}); $ .getJSON ("products.json", function (data) {// Получить данные о наших продуктах из products.json. // Вызов функция, которая преобразует эти данные в HTML. generateAllProductsHTML (data); // Вручную инициировать хэш-обмен для запуска приложения. $ (window) .trigger ('hashchange');}); $ (window) .on ('hashchange' , function () {// При каждом изменении хеша функция рендеринга вызывается с новым хешем. // Так происходит навигация нашего приложения. render (decodeURI (window.location.hash));}); функция render (url) {// Эта функция решает, какой тип страницы отображать //, в зависимости от текущего значения хеша URL.} function generateAllProductsHTML (data) {// Использует Handlebars для создания списка продуктов с использованием пр. Овидимые данные. // Эта функция вызывается только один раз при загрузке страницы. } function renderProductsPage (data) {// Скрывает и показывает продукты на странице «Все продукты» в зависимости от полученных данных. } function renderSingleProductPage (index, data) {// Показывает страницу отдельного продукта с соответствующими данными. } function renderFilterResults (filters, products) {// Создает объект с отфильтрованными продуктами и передает его в renderProductsPage. renderProductsPage (результаты); } function renderErrorPage () {// Показывает страницу ошибки. } function createQueryHash (filters) {// Получить объект фильтров, превратить его в строку и записать в хеш. }});

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

Однако мы все еще хотим иметь возможность пойти куда-нибудь в приложении и, например, скопировать URL-адрес и отправить его другу. Если мы никогда не изменим адрес приложения, они просто получат приложение так, как оно выглядит в начале, а не то, что вы хотели бы поделиться с ними. Чтобы решить эту проблему, мы записываем информацию о состоянии приложения в URL как #hash. Хэши не приводят к перезагрузке страницы и легко доступны и управляются.

На каждом hashchange мы называем это:

function render (url) {// Получить ключевое слово из URL. var temp = url.split ('/') [0]; // Скрыть ту страницу, которая отображается в данный момент. $ ('. main-content .page'). removeClass ('visible'); var map = {// Домашняя страница. '': function () {// Очистить объект фильтров, снять все флажки, показать все продукты filters = {}; checkboxes.prop ( 'проверено', ложь); renderProductsPage (продукции); }, // Страница отдельных продуктов. '#product': function () {// Получить индекс продукта, который мы хотим показать, и вызвать соответствующую функцию. var index = url.split ('# product /') [1] .trim (); renderSingleProductPage (индекс, продукты); }, // Страница с отфильтрованными продуктами '#filter': function () {// Захватить строку после ключевого слова # filter /. Вызовите функцию фильтрации. url = url.split ('# filter /') [1] .trim (); // Попробуем разобрать объект filters из строки запроса. try {filters = JSON.parse (url); } // Если это не правильный json, вернитесь на домашнюю страницу (остальная часть кода не будет выполнена). catch (err) {window.location.hash = '#'; } renderFilterResults (фильтры, продукты); }}; // Выполнить нужную функцию в зависимости от ключевого слова url (хранится в temp). if (map [temp]) {map [temp] (); } // Если ключевое слово не указано выше - отобразить страницу с ошибкой. else {renderErrorPage (); }}

Эта функция учитывает начальную строку нашего хэша, решает, какую страницу нужно показать, и вызывает соответствующие функции.

Например, если хеш равен '#filter / {"storage": ["16"], "camera": ["5"]}', наше кодовое слово будет #filter. Теперь функция рендера знает, что мы хотим видеть страницу со списком отфильтрованных продуктов, и перейдет к ней. Остальная часть хэша будет проанализирована в объекте, и будет показана страница с отфильтрованными продуктами, что приведет к изменению состояния приложения.

Это вызывается только один раз при запуске и превращает наш JSON в реальный контент HTML5 с помощью руля.

function generateAllProductsHTML (data) {var list = $ ('. all-products .products-list'); var theTemplateScript = $ ("# products-template"). html (); // Скомпилируем шаблон var theTemplate = Handlebars.compile (theTemplateScript); list.append (шаблон (данные)); // Каждый продукт имеет атрибут data-index. // При нажатии изменить URL-хэш, чтобы открыть предварительный просмотр только для этого продукта. // Помните: каждый hashchange вызывает функцию рендеринга. list.find ('li'). on ('click', function (e) {e.preventDefault (); var productIndex = $ (this) .data ('index'); window.location.hash = 'product / '+ productIndex;})}

Эта функция получает объект, содержащий только те продукты, которые мы хотим показать, и отображает их.

function renderProductsPage (data) {var page = $ ('. all-products'), allProducts = $ ('. all-products .products-list> li'); // Скрыть все товары в списке товаров. allProducts.addClass ( 'скрытый'); // Перебирать все продукты. // Если их идентификатор где-то в объекте данных, удалите скрытый класс, чтобы показать их. allProducts.each (function () {var that = $ (this); data.forEach (function (item) {if (that.data ('index') == item.id) {that.removeClass ('hidden') ;}});}); // Показать саму страницу. // (функция рендеринга скрывает все страницы, поэтому нам нужно показать ту, которую мы хотим). page.addClass ( 'видимого'); }

Показывает одну страницу предварительного просмотра продукта:

function renderSingleProductPage (index, data) {var page = $ ('. single-product'), container = $ ('. preview-large'); // Найти нужный продукт путем итерации объекта данных и поиска выбранного индекса. if (data.length) {data.forEach (function (item) {if (item.id == index) {// Заполните «.preview-large» данными выбранного продукта. container.find ('h3'). text (item.name); container.find ('img'). attr ('src', item.image.large); container.find ('p'). text (item.description);}}); } // Показать страницу. page.addClass ( 'видимого'); }

Принимает все продукты, фильтрует их по нашему запросу и возвращает объект с результатами.

function renderFilterResults (filters, products) {// Этот массив содержит все возможные критерии фильтрации. критерий var = [«производитель», «хранилище», «ОС», «камера»], результаты = [], isFiltered = false; // Снимите все флажки. // Мы будем проверять их снова один за другим. checkboxes.prop ('флажок', ложь); crit.forEach (function (c) {// Проверить, присутствует ли каждый из возможных критериев фильтра в объекте фильтров. if (filters [c] && filters [c] .length) {// После того, как мы отфильтровали продукты однажды мы хотим продолжать их фильтровать. // Поэтому мы делаем объект, в котором мы ищем (products), равным результату. // Затем массив результатов очищается, поэтому он может быть заполнен вновь отфильтрованными данными. . if (isFiltered) {products = results; results = [];} // В этих вложенных циклах for мы будем перебирать фильтры и продукты // и проверять, содержат ли они одинаковые значения (те, которые мы фильтруем by). // Перебирать записи внутри filters.criteria (помните, что каждый критерий содержит массив). filters [c] .forEach (function (filter) {// Перебирать продукты. products.forEach (function (item) { // Если продукт имеет то же значение спецификации, что и значение в фильтре, // помещаем его в массив результатов и помечаем флаг isFiltered true. If (typeof item.specs [c] == 'number') {if (item.specs [c] == фильтр) {results.push (item); isFiltered = true; }} if (typeof item.specs [c] == 'string') {if (item.specs [c] .toLowerCase (). indexOf (filter)! = -1) {results.push (item); isFiltered = true; }}}); // Здесь мы можем сделать флажки, представляющие фильтры, истинными, // поддерживая приложение в актуальном состоянии. if (c && filter) {$ ('input [name =' + c + '] [value =' + filter + ']'). prop ('checked', true); }}); }}); // Вызвать renderProductsPage. // В качестве аргумента передаем объект с отфильтрованными продуктами. renderProductsPage (результаты); }

Показывает состояние ошибки:

function renderErrorPage () {var page = $ ('. error'); page.addClass ( 'видимого'); }

Стригирует объект фильтра и записывает его в хеш.

function createQueryHash (filters) {// Здесь мы проверяем, не являются ли фильтры пустыми. if (! $. isEmptyObject (filters)) {// Stringify объект через JSON.stringify и запишите его после ключевого слова '#filter'. window.location.hash = '# filter /' + JSON.stringify (filters); } else {// Если оно пустое, измените хеш на '#' (домашняя страница). window.location.hash = '#'; }}

Заключение

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

Bootstrap Studio

Революционный инструмент веб-дизайна для создания адаптивных веб-сайтов и приложений.

Учить больше

ГОРЯЧАЯ ЛИНИЯ

(062) 348 60 00
(095) 210 57 42

Дед Мороз в офис Донецк

Дед Мороз на детском утреннике Донецк

Дед Мороз на дом Донецк

Новости

Телеканал Юнион в гостях у Морозко

Последние записи