Html страница глазами разработчика приложений. Часть 2: «Реализация»

    В Первой части мы подготовили нашу страницу.


    После прочтения комментариев хочется сделать небольшое отступление: я не говорю что этот подход идеальный, или всем надо следовать и теперь писать только так. Я знаю о наличии программ для этого. Но суть статьи была поделиться мыслями как это можно сделать самому, и заодно разобраться в разных технологиях. Всегда интересно искать новые пути, пусть они и неверные, или слишком длинные, но они учат нас чему-то новому, пусть даже мы их прошли с ошибками.


    Имитация базы, как мне уже писали в комментариях, это json файлы с содержанием нужного текста. Вопрос: "Зачем тут Vue? Если это можно написать и на скриптах?". Если честно — для красоты html верстки. Ну и изучения новых технологий.


    Приступим!


    На данной странице разделение вовсе не обязательно, ибо переменных тут не очень много, но я предпочитаю делить все на разные части. У нас будет компонент отвечающий за header, content & footer(позже появится).


    Первое, это мы создаем json файлы, я создаю папку "data" и в ней создаю два файла "ru.json" и "en.json". В них соответственно названиям будет лежать наш текст. Далее открываем наш html и делаем подмены на будущее, стараясь назвать переменные так чтоб они полностью отображали смысл текста в них. В моем случае это было вот так:


     <header class="transition tr-header" id="header">
                        <div class="container">
                            <div class="nav-holder">
                                <nav class="scroll-nav">
                                    <ul>
                                         //заменили на переменные
                                        <li class="actscroll"><a  href="#sec1">{{main}} </a></li>
                                        <li><a  href="#sec2">{{aboutCompany}}</a></li>
                                        <li><a  href="#sec3">{{product}}</a></li>
                                        <li><a  href="#sec4">{{equipment}}</a></li>
                                        <li><a  href="#sec5">{{whereBuy}}</a></li>
                                        <li><a  href="#sec6">{{service}}</a></li>
                                        <li><a  href="#sec7">{{partners}}</a></li>
                                        <li><a  href="#sec8">{{contacts}}</a></li>
                                    </ul>
                                </nav>
                                <div class="lang-dropdown">
                                    <div class="flag-with-menu" id="flag-menu">
                                        <div class="flag flag-ru" lang-value="ru-RU"></div>
                                    </div>
                                    <div id="lang-menu" class="lang-menu lang-first-init">
                                        <div class="flag flag-us" lang-value="en-US"></div>
                                    </div>
                                </div>
                            </div>
                        </div>
    
                    </header>
                    <!-- End header -->
                    <!--================= Photo home  ================-->
                    <section class="is_overlay page-title-bg" id="sec1" name="sec1">
                        <div class="bg bg-parallax run-par2" style="background-image: url(images/paraplan.jpg) "></div>
                        <div class="overlay over-op6"></div>
                    </section>
                    <!-- section end -->
                    <div id="contentPage"> //добален для компонента
                        <section class="align-text" id="sec2" name="sec2">
                            <div class="content">
                                <div class="container">
                                    <div class="row">
                                        <div class="col-md-6 ">
                                            <h3>{{aboutCompanyHeader}}</h3>
                                            <div class="clearfix"></div>
                                            <div class="separator color-separator flt-l"></div>
                                            <div class="clearfix"></div>
                                            <p>{{aboutCompanyText}}</p>
                                        </div>
                                        <div class="col-md-6 ">
                                            <h3>{{ourMissionHeader}}</h3>
                                            <div class="clearfix"></div>
                                            <div class="separator color-separator flt-l"></div>
                                            <div class="clearfix"></div>
                                            <p>{{ourMissionText}}</p>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </section>
                    //у вас может быть больше секций, у меня все секции собранны в данном div
                    </div>

    Переходим к языковым файлам: (это можно добавить было все в один массив или один объект, но мне удобнее когда текст разбит подобным образом). "ru.json"


    [
      { "main": "Главная" },
      { "aboutCompany": "О компании" },
      { "product": "Наша продукция" },
      { "equipment": "Наши технологии" },
      { "whereBuy": "Где купить" },
      { "service": "Поддержка" },
      { "partners": "Партнеры" },
      { "contacts": "Контакты" },
      {"aboutCompanyHeader": "О компании"},
      {
        "aboutCompanyText": [
          "«Хабрахабр» — крупнейший в Европе ресурс для IT-специалистов, издаваемый компанией «ТМ». ",
          "С момента появления в 2006-м году «Хабр» трансформировался из небольшого отраслевого сайта в глобальную ",
          "профессиональную площадку, которую ежемесячно посещают более 8 миллионов уникальных пользователей.",
          "«Хабрахабр» одинаково интересен программистам и разработчикам, администраторам и тестировщикам, дизайнерам ",
          "и верстальщикам, аналитикам и копирайтерам, а также всем тем, для кого IT — это не просто две буквы алфавита.",
          "Расширение тематики «Хабра» дало начало сайту-спутнику — Geektimes, на который переехали непрофильные хабы ",
          "и значительная часть контента, не имеющего непосредственного отношен разработке и программированию."
        ]
      },
      { "ourMissionHeader": "Наша миссия" },
      {
        "ourMissionText": [
          "Данный сайт представляет собой платформу для информационного обмена между участниками пользовательского ",
          "сообщества. Сообщество пользователей сайта является саморегулируемым, поэтому разобраться во всех нюансах ",
          "работы проекта с первого раза получается далеко не у всех. Чтобы объяснить, как всё устроено, мы подготовили ",
          "данный справочный раздел. Справа представлен рубрикатор справочного раздела. Для получения разъяснений выберите ",
          "соответствующий пункт рубрикатора и ознакомьтесь с предложенной информацией. Если вам не удалось найти ответ ",
          "на интересующий вопрос, пожалуйста, воспользуйтесь формой обратной связи."
        ]
      }
    ]

    Ну и вы сами надеюсь справитесь с переводом данного текста на английский!


    После проверки у меня перестало нормально работать меню с языками, поэтому если вы столкнулись с подобной проблемой, вот быстрое решение:"multilanguage.js"


      replaceElementAndSelect(userLanguage); 
    //измененно
        $(document).on('click', '.flag ', function () {
            if (!isMenuClicked && !$(this).hasClass('select-flag')) {
                var newLang = $(this).attr('lang-value');
                language = newLang;
                setCookie("language", language);
                languageChange(newLang);
                hideMenu();
            }
            isMenuClicked = false;
        });
    
    //измененно
        $(document).on('click', "#flag-menu", function () {
            isMenuClicked = true;
            showOrHideMenu();
        });
    
    //замените все где используется menu на $('#lang-menu'). Например: menu.hasClass('lang-first-init') на $('#lang-menu').hasClass('lang-first-init'). В старом варианте у меня не работает. Если есть желание можете найти в чем проблема и ответить в комментариях

    В папку "scripts" добавляем новый "main-function.js". И добавляем в него пару методов(т.к в моем случае они потом переиспользовались):


    //Тут происходит сопоставление свойств компонента с объектами из json файла
    function findInArray(langArray, component) {
        $.each(langArray,
            function (index, value) {
                Object.keys(value).forEach(function (key) {
                    var val = value[key];
                    if ($.isArray(val)) {
                        component[key] = val.join(", ");
                    } else {
                        component[key] = value[key];
                    }
                });
            });
    }
    
    //переиспользование для удобства написания кода
    function getArrayFromJson(url) {
        return $.ajax({
            url: url,
            dataType: 'json'
        });
    }

    В папку "scripts" добавляем новый "index.js". И разбираем его


    $(document).ready(function () {
        var language = getCookie("language") || navigator.language || navigator.browserLanguage; //ищем язык. Если нет в куках то берем браузерный
    
        //пути наших файлов с языковыми данными
        var ruUrl = location.origin + '/data/ru.json';
        var enUrl = location.origin + '/data/en.json';
    
        //массивы для данных, и Vue компоненты для обращения к ним
        var en = [], ru = [];
        var vm, vmHeader;
    
        initialize();
    
        //отлавливаем событие, если был поменян язык
        $(document).on('onLanguageChange',
            function (e, eventInfo) {
                setPageTemplateByLanguage(eventInfo);
            });
    
        function initialize() {
            //создаем Vue компоненты
            createMainComponent();
    
            //ждем пока придут данные с наших json файлов и отправляем их в массив
            $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl))
                .done(function (a1, a2) {
                    ru = a1[0];
                    en = a2[0];
                    setPageTemplateByLanguage(language); //для изменения языка
                });
        }
    
        function createMainComponent() {
            //записываем все наши переменные принадлежащие этому участку, их должно быть больше,но для примера сойдет
            vm = new Vue({
                el: '#contentPage',
                data: {
                    siteHeader: "",
                    siteSubHeader: "",
                    aboutCompanyHeader: "",
                    aboutCompanyText: "",
                    ourMissionHeader: "",
                    ourMissionText: ""
                },
                //у меня на странице была сторонняя библиотека которая создавала карусель из картинок, так вот после обновления компонента она "ломалась". Этот метод пересоздавал ее.
                updated: function () {
                    this.$nextTick(function () {
                        // createCarusel();
                    });
                }
            });
    
            vmHeader = new Vue({
                el: '#header',
                data: {
                    aboutCompany: "",
                    product: "",
                    equipment: "",
                    whereBuy: "",
                    service: "",
                    partners: "",
                    contacts: ""
                }
            });
        }
    
        //в зависимости от языка перезаписываем данные в компонентах
        function setPageTemplateByLanguage(lang) {
            switch (lang) {
                case "en-US":
                    findInArray(en, vmHeader);
                    findInArray(en, vm);
                    break;
                case "ru-RU":
                    findInArray(ru, vmHeader);
                    findInArray(ru, vm);
                    break;
                default:
                    findInArray(ru, vmHeader);
                    findInArray(ru, vm);
                    break;
            }
        }
    });

    Теперь осталось это добавить на нашу страницу, и не забыть скачать vue.min.js в папку "scripts"


        <script src="scripts/jquery.min.js"></script>
        <script src="scripts/vue.min.js"></script>
        <script src="scripts/cookie.js"></script>
        <script src="scripts/multilanguage.js"></script>
        <script src="scripts/main-function.js"></script>
        <script src="scripts/index.js"></script>

    В принципе вот и все, совсем немного кода для красивого решения!


    Но я хочу пойти чуть дальше и добавлю пару Vue компонентов (для примера). Может будет кому-то от этого польза. Из новых компонентов это будет footer и заголовок сайта, а то у нас картинка выглядит пустой.


    Создаем в папке "data" два файла: "footer_ru.json" и "footer_en.json"


    [
      {"getInTouch": "Связаться"},
      {"region": "Россия, Санкт-Петербург"},
      {"street": "Невский проспект, дом 13 / 7" },
      {"phone": "8 (812) 666-66-66"},
      {"mobilePhone": "+7 (966) 666-66-66"},
      {"email": "ivanov@mail.ru"},
      {"secondEmail": "info@gmai.com"},
      {"findUs": "Ищите нас"},
      {"firstLine": "Инновация"},
      {"secondLine": "Мы помогаем"},
      {"firstPartLastLine": "2014 OOO "},
      {"colorPartLastLine": "Хабр "},
      {"thirdPartLastLine": "блог для разработчиков"}
    ]

    Я это выношу в файл с общими функциями, т.к у себя я это переиспользую. Добавляем в "main-function.js"


    // пути для данных 
    var ruFooterUrl = location.origin + '/data/footer_ru.json';
    var enFooterUrl = location.origin + '/data/footer_en.json';
    
    var vueFooter;
    var ruFooterInfo = [], enFooterInfo = [];
    
    //следим за изменениями языка
    $(document).on('onLanguageChange',
        function (e, eventInfo) {
            setPageTemplateByLanguageMain(eventInfo);
        });
    
    //в общем сам футер
    Vue.component('habr-footer',
        {
            props: ['get-in-touch', 'region', 'street', 'email', 'second-email', 'phone', 'mobile-phone', 'find-us',
                'first-line', 'second-line', 'first-part-last-line', 'color-part-last-line', 'third-part-last-line'],
            template:
                `<div>
                    <section class="page-widgets-holder">
                        <div class="content">
                            <div class="container">
                                <div class="row">
                                    <div class="col-md-4 ">
                                        <h3>{{getInTouch}}</h3>
                                        <div class="contact-info">
                                            <ul>
                                                <li><a class="ci-adress">{{region}}<br> {{street}}</a></li>
                                                <li><a class="ci-mail"> {{email}}</a></li>
                                                <li><a class="ci-mail"> {{secondEmail}}</a></li>
                                                <li> <a v-bind:href="'tel:' + phone" class="ci-phone">  {{phone}} </a></li>
                                                <li> <a v-bind:href="'tel:' + mobilePhone" class="ci-phone">  {{mobilePhone}} </a></li>
                                            </ul>
                                        </div>
                                    </div>
                                    <div class="col-md-4 ">
                                    </div>
                                    <div class="col-md-4 ">
                                        <h3>{{findUs}}</h3>
                                        <div class="social-links">
                                            <ul>
                                                <li><a href="#" target="_blank" class="transition"><i class="fa fa-facebook"></i></a></li>
                                                <li><a href="#" target="_blank" class="transition"><i class="fa fa-vk"></i></a></li>
                                                <li><a href="#"  target="_blank" class="transition"><i class="fa fa-twitter"></i></a></li>
                                                <li><a href="#"  target="_blank" class="transition"><i class="fa fa-youtube"></i></a></li>
                                                <li><a href="#"  target="_blank" class="transition"><i class="fa fa-instagram"></i></a></li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                    <!-- section end -->
                    <!--================= footer  ================-->
                        <section  class="page-widgets-holder footer">
                            <div class="container">
                                <div class="row">
                                    <div class="col-md-3 ">
                                        <h4>{{firstLine}}</h4>
                                        <h5>{{secondLine}}</h5>
                                    </div>
                                    <div class="col-md-9">
                                        <div class="policy-box">
                                            <p>{{firstPartLastLine}} <span>{{colorPartLastLine}} </span> {{thirdPartLastLine}}</p>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </section>
                </div>`
        });
    
    //создание компонента и запись в него информации
    function createFooterComponent() {
        vueFooter = new Vue({
            el: '#vueFooter',
            data: {
                footerInfo: {
                    getInTouch: "",
                    region: "",
                    street: "",
                    email: "",
                    secondEmail: "",
                    phone: "",
                    mobilePhone: "",
                    findUs: "",
                    firstLine: "",
                    secondLine: "",
                    firstPartLastLine: "",
                    colorPartLastLine: "",
                    thirdPartLastLine: ""
                }
            },
            created: function () {
                this.loadData();
            },
            methods: {
                loadData() {
                    $.when(getArrayFromJson(ruFooterUrl),
                        getArrayFromJson(enFooterUrl)
                    ).done(function (a1, a2) {
                        ruFooterInfo = a1[0];
                        enFooterInfo = a2[0];
                        setPageTemplateByLanguageMain();
                    });
                }
            }
        });
    }
    
    //в зависимости от языка перезаписываем данные в компонентах
    function setPageTemplateByLanguageMain(lang) {
        var userLanguage = lang || getCookie("language") || language;
        switch (userLanguage) {
        case "en-US":
            findInArray(enFooterInfo, vueFooter.footerInfo);
            break;
        case "ru-RU":
            findInArray(ruFooterInfo, vueFooter.footerInfo);
            break;
        default:
            findInArray(ruFooterInfo, vueFooter.footerInfo);
            break;
        }
    }

    Чтобы это все было красиво, скачиваем Font Awesome и добавляем его в "css" папку.


    В нашем "style.css" добавляем классы для красивого отображения:


    .page-widgets-holder {
        border-top:1px solid #ccc;
    }
    .page-widgets-holder h3  {
        font-size:14px;
        text-align:center;
        color:#666;
        font-family: 'Montserrat', sans-serif;
        text-transform:uppercase;
        margin-bottom:40px;
        position:relative;
    }
    .page-widgets-holder h3:before {
        content:'';
        position:absolute;
        width:40px;
        height:2px;
        background:#ccc;
        bottom:-10px;
        left:50%;
        margin-left:-20px;
    }
    
    .contact-info li {
        float:left;
        width:100%;
        margin-bottom:12px;
    }
    .contact-info li a {
        font-family: 'Montserrat', sans-serif;
    }
    .ci-adress {
        text-transform:uppercase;
        font-size:14px;
        text-align:left;
        color:#000;
        line-height:20px;
    }
    .ci-mail {
        font-size:14px;
        text-align:left;
    }
    .ci-phone {
        color:#666;
        line-height:20px;
    }
    .social-links  {
        padding-bottom:58px;
    }
    .social-links li {
        display:inline-block;
        margin:0 1px;
        box-sizing:border-box;
    }
    .social-links li a {
        width:50px;
        height:50px;
        background:#eee;
        border-radius:100%;
        line-height:50px;
        float:left;
        color:#666;
        font-size:20px;
        box-shadow:0 0 0 20px transparent;
    }
    .social-links li a:hover {
        box-shadow:0 0 0 0 rgba(0,0,0,0.1);
    }
    
    .fa {
        margin-left: 0.75em;
        margin-top: 0.75em;
    }

    В "index.js" добавляем в метод "initialize()":


    function initialize() {
            createFooterComponent(); //добавлено
            createMainComponent();
    
            $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl))
                .done(function (a1, a2) {
                  ........
                });
        }

    И в "index.html" добавляем:


        <link rel="stylesheet" href="css/font-awesome-4.7.0/css/font-awesome.min.css" media="all">
    
    ......
    
    <body>
        <!--================= main start ================-->
        <div id="main">
            <div id="wrapper">
                <div class="content-holder">
                    <!--================= Header ================-->
                    <header class="transition tr-header" id="header">
                        ...........
                    </header>
                    <!-- End header -->
                    <!--================= Photo home  ================-->
                    <section class="is_overlay page-title-bg" id="sec1" name="sec1">
                      ........
                    </section>
                    <!-- section end -->
                    <div id="contentPage">
                       ........
                    </div>
                    <!-- добавлено -->
                    <div id="vueFooter">
                        <habr-footer v-bind="footerInfo"></habr-footer>
                    </div>
                </div>
            </div>
        </div>
    </body>

    Ну и завершающий компонент, это заголовок сайта, я его добавляю в "main-function.js":


    var vmPageHeader, vueFooter;//добавлено 
    var ruFooterInfo = [], enFooterInfo = [];
    var ruHeaderInfo = [], enHeaderInfo = []; //добавлено 
    
    Vue.component('habr-header',
        {
            props: ['site-header', 'site-sub-header'],
            template:
                `<div class="container">
                    <div class= "page-title-bg-holder hero-wrapper">
                        <h2>{{ siteHeader }}</h2>
                        <p>{{siteSubHeader}}</p>
                    </div>
                </div>`
        });
    
    //в данном случае создавать файл ради 2х строк невыгодно,поэтому мы добавляем данные в наши основные файлы и ссылки передаем сюда, где происходит поиск и сортировка нужной информации
    function createHeaderComponent(ruUrl, enUrl) {
        vmPageHeader = new Vue({
            el: '#vueHeader',
            data: {
                siteSubHeader: "",
                siteHeader: ""
            },
            created: function () {
                this.loadData();
            },
            methods: {
                loadData() {
                    $.when(
                            getArrayFromJson(ruUrl),
                            getArrayFromJson(enUrl))
                        .done(function (a1, a2) {
                            ruHeaderInfo = a1[0];
                            enHeaderInfo = a2[0];
                            setPageTemplateByLanguageMain();
                        });
                }
            }
        });
    }
    
    function setPageTemplateByLanguageMain(lang) {
        var userLanguage = lang || getCookie("language") || language;
        switch (userLanguage) {
            case "en-US":
                findInArray(enHeaderInfo, vmPageHeader);//добавлено
            findInArray(enFooterInfo, vueFooter.footerInfo);
            break;
            case "ru-RU":
                findInArray(ruHeaderInfo, vmPageHeader);//добавлено
            findInArray(ruFooterInfo, vueFooter.footerInfo);
            break;
            default:
                findInArray(ruHeaderInfo, vmPageHeader);//добавлено
            findInArray(ruFooterInfo, vueFooter.footerInfo);
            break;
        }
    }

    В "en.json" и "ru.json" добавляем данные:


    [
      { "siteHeader": "«Хабрахабр»" },
      { "siteSubHeader": "Данный сайт представляет собой платформу для информационного обмена между участниками пользовательского сообщества" },
    ..........
    ]

    В "index.js" добавляем в метод "initialize()":


    function initialize() {
            createFooterComponent(); 
            createMainComponent();
            createHeaderComponent(ruUrl, enUrl);//добавлено
            $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl))
                .done(function (a1, a2) {
                  ........
                });
        }

    И в "index.html" добавляем:


        <link rel="stylesheet" href="css/font-awesome-4.7.0/css/font-awesome.min.css" media="all">
    
    ......
    
    <body>
        <!--================= main start ================-->
        <div id="main">
            <div id="wrapper">
                <div class="content-holder">
                    <!--================= Header ================-->
                    <header class="transition tr-header" id="header">
                        ...........
                    </header>
                    <!-- End header -->
                    <!--================= Photo home  ================-->
                    <section class="is_overlay page-title-bg" id="sec1" name="sec1">
                         <div class="bg bg-parallax run-par2" style="background-image: url(images/paraplan.jpg) "></div>
                        <div class="overlay over-op6"></div>
                    <!-- добавлено -->
                        <div class="content" id="vueHeader">
                            <habr-header :site-header="siteHeader" :site-sub-header="siteSubHeader"></habr-header>
                        </div>
                    </section>
                    <!-- section end -->
                    <div id="contentPage">
                       ........
                    </div>
                    <div id="vueFooter">
                        ........
                    </div>
                </div>
            </div>
        </div>
    </body>

    Вот в принципе и все!


    image


    Исходный код можно найти ТУТ.


    Только внимательно следите за путями к json-файлам, они у вас могут отличаться.

    Share post

    Comments 5

      0
      Пролистал обе части в безуспешной надежде посмотреть какой такой новый механизм изобрели под vue для локализации, что ради него статью опубликовали :) Но стремления похвальные, продолжайте в том-же духе и будете хорошим frontend.
        +2
        По прежнему смысл не ясен. Аргумент «буду писать так потому что хочу попробовать что то новое» не принимается. Пробовать можно, но не пилить же цикл статей с того. Сейчас новички посмотрят и за вами повторять начнут.
          0
          Или я переработал и глаза замылились, или с Vue один пример на обе части статьи.
          Что-то жиденько.
            0

            Тема Vue не раскрыта, вся суть сводится к идее выноса контента с локализацией в json-файлы и их подгрузкой аяксом и описывается парой предложений


            "Зачем тут Vue? Если это можно написать и на скриптах?". Если честно — для красоты html верстки. Ну и изучения новых технологий.

            Для этой цели лучше изучить pug и stylus — красота и удобство, а у вас в итоге оверхед по весу от смеси vue и jquery, причем в данном контексте можно было бы обойтись вообще VanillaJs


            В общем совсем не тот контент, который ожидался от человека со статусом Software Engineer
            И хоть я бекендер, даже для меня жиденько

              0
              Тот же вопрос, что и у многих: зачем здесь jquery, если есть vue и наоборот?

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