ГлавнаяБлог вэб-мастераповторное использование кода / Блог компании RUVDS.com / Хабрахабр

повторное использование кода / Блог компании RUVDS.com / Хабрахабр

  • Четверг, 16 Ноябрь 2017 13:05
Перед вами четвёртая часть серии материалов, которые посвящены разработке веб-приложения Budget Manager с использованием Node.js, Vue.js и MongoDB. В первой, второй и третьей частях речь шла о создании основных серверных и клиентских компонентов приложения. Сегодня мы продолжим развитие проекта, а именно — займёмся списками документов и клиентов. Кроме того, нельзя не заметить, что к настоящему моменту сделано уже немало, поэтому вполне можно критически взглянуть на то, что получилось, и поработать над повторным использованием кода.

go site image

Совершенствование компонентов Начнём с изменения имени папки Budget на List. Кроме того, переименуем три компонента, которые находятся в этой папке. А именно, BudgetList теперь будет называться List, BudgetListHeader получит название ListHeader, а BudgetListBody — ListBody. В итоге папка и файлы компонентов должны выглядеть так, как показано на рисунке ниже.image Откроем файл компонента List и приведём его к такому виду:<template>  <section class="l-list-container">    <slot name="list-header"></slot>    <slot name="list-body"></slot>  </section> </template> <script>  export default {} </script> Тут мы изменили имя класса таким образом, чтобы оно соответствовало имени компонента. Кроме того, мы поменяли имена слотов.Откроем файл компонента ListHeader и внесём в него следующие изменения:<template>  <header class="l-list-header">    <div class="md-list-header white--text"         v-if="headers != null"         v-for="header in headers">        {{ header }}    </div>  </header> </template> <script>  export default {    props: ['headers']  } </script> <style lang="scss">  @import "./../../assets/styles";  .l-list-header {    display: none;    width: 100%;    @media (min-width: 601px) {      margin: 25px 0 0;      display: flex;    }    .md-list-header {      width: 100%;      background-color: $background-color;      border: 1px solid $border-color-input;      padding: 0 15px;      display: flex;      height: 45px;      align-items: center;      justify-content: center;      font-size: 22px;      @media (min-width: 601px) {        justify-content: flex-start;      }    }  } </style> Здесь, опять же, мы поменяли имена классов, а так же отредактировали шаблон, настроив его на вывод данных из свойств (props). Так мы сможем повторно использовать этот компонент на других страницах.Теперь пришёл черёд компонента ListBody:<template>  <section class="l-list-body">    <div class="md-list-item"         v-if="data != null"         v-for="item in data">         <div class="md-info white--text" v-for="info in item" v-if="info != item._id">           {{ info }}         </div>        <div class="l-actions">          <v-btn small flat color="light-blue lighten-1">            <v-icon small>visibility</v-icon>          </v-btn>          <v-btn small flat color="yellow accent-1">            <v-icon>mode_edit</v-icon>          </v-btn>          <v-btn small flat color="red lighten-1">            <v-icon>delete_forever</v-icon>          </v-btn>        </div>    </div>  </section> </template> <script>  export default {    props: ['data']  } </script> <style lang="scss">  @import "./../../assets/styles";  .l-list-body {    display: flex;    flex-direction: column;    .md-list-item {      width: 100%;      display: flex;      flex-direction: column;      margin: 15px 0;      @media (min-width: 960px) {        flex-direction: row;        margin: 0;      }      .md-info {        flex-basis: 25%;        width: 100%;        background-color: rgba(0, 175, 255, 0.45);        border: 1px solid $border-color-input;        padding: 0 15px;        display: flex;        height: 35px;        align-items: center;        justify-content: center;        &:first-of-type, &:nth-of-type(2) {          text-transform: capitalize;        }        &:nth-of-type(3) {          text-transform: uppercase;        }        @media (min-width: 601px) {          justify-content: flex-start;        }      }      .l-actions {        flex-basis: 25%;        display: flex;        background-color: rgba(0, 175, 255, 0.45);        border: 1px solid $border-color-input;        align-items: center;        justify-content: center;        .btn {          min-width: 45px !important;          margin: 0 5px !important;        }      }    }  } </style> Этот компонент мы тоже подготовили к повторному использованию, задействовав вывод данных из свойств.Теперь откроем файл компонента Home и отредактируем его:<template>  <main class="l-home-page">    <app-header></app-header>    <div class="l-home">      <h4 class="white--text text-xs-center my-0">        Focus Budget Manager      </h4>      <list>        <list-header slot="list-header" :headers="budgetHeaders"></list-header>        <list-body slot="list-body" :data="budgets"></list-body>      </list>    </div>    <v-snackbar :timeout="timeout"                bottom="bottom"                color="red lighten-1"                v-model="snackbar">      {{ message }}    </v-snackbar>  </main> </template> <script>  import Axios from 'axios'  import Authentication from '@/components/pages/Authentication'  import ListHeader from './../List/ListHeader'  import ListBody from './../List/ListBody'  const BudgetManagerAPI = `http://${window.location.hostname}:3001`  export default {    components: {      'list-header': ListHeader,      'list-body': ListBody    },    data () {      return {        budgets: [],        clients: [],        budgetHeaders: ['Client', 'Title', 'Status', 'Actions'],        clientHeaders: ['Client', 'Email', 'Phone', 'Actions'],        snackbar: false,        timeout: 6000,        message: ''      }    },    mounted () {      this.getAllBudgets()    },    methods: {      getAllBudgets () {        Axios.get(`${BudgetManagerAPI}/api/v1/budget`, {          headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },          params: { user_id: this.$cookie.get('user_id') }        }).then(({data}) => {          this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state')        }).catch(error => {          this.snackbar = true          this.message = error.message        })      },      dataParser (targetedArray, ...options) {        let parsedData = []        targetedArray.forEach(item => {          let parsedItem = {}          options.forEach(option => (parsedItem[option] = item[option]))          parsedData.push(parsedItem)        })        return parsedData      }    }  } </script> <style lang="scss" scoped>  @import "./../../assets/styles";  .l-home {    background-color: $background-color;    margin: 25px auto;    padding: 15px;    min-width: 272px;  } </style> Здесь мы поменяли команды импорта и теги, привели их в соответствие переименованным компонентам, добавили snackbar, что даёт возможность показывать сообщения об ошибках в том случае, если нам не удастся получить данные. Кроме того, мы добавили сюда новый массив для данных компонента, budgetHeaders, а также данные, необходимые для snackbar.Мы будем использовать budgetHearders для показа заголовков списка. Кроме того, мы внесли некоторые изменения в метод getAllBudgets:getAllBudgets () {  Axios.get(`${BudgetManagerAPI}/api/v1/budget`, {    headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },    params: { user_id: this.$cookie.get('user_id') }  }).then(({data}) => {    this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state')  }).catch(error => {    this.snackbar = true    this.message = error.message  }) }, Теперь, вместо того, чтобы передавать данные документов напрямую, мы используем новый метод:dataParser (targetedArray, ...options) {  let parsedData = []  targetedArray.forEach(item => {    let parsedItem = {}    options.forEach(option => (parsedItem[option] = item[option]))    parsedData.push(parsedItem)  })  return parsedData } Этот метод, в качестве первого аргумента, принимает массив и произвольное число аргументов, которые сформируют массив options с использованием оператора расширения.Метод будет брать каждый элемент из массива, в данном случае это — документы, и создавать новый объект parsedItem.Этот объект будет содержать данные массива options, после завершения его подготовки он будет помещён в массив parsedData, который мы возвращаем из этого метода.И, наконец, мы перехватываем ошибки (если таковые возникнут), активируя snackbar.Теперь нужно отредактировать код маршрутизатора, для этого перейдём в папку router и откроем index.js:import Vue from 'vue' import Router from 'vue-router' import * as Auth from '@/components/pages/Authentication' // Pages import Home from '@/components/pages/Home' import Authentication from '@/components/pages/Authentication/Authentication' // Global components import Header from '@/components/Header' import List from '@/components/List/List' // Register components Vue.component('app-header', Header) Vue.component('list', List) Vue.use(Router) const router = new Router({  routes: [    {      path: '/',      name: 'Home',      components: {        default: Home,        header: Header,        list: List      }    },    {      path: '/login',      name: 'Authentication',      component: Authentication    }  ] }) router.beforeEach((to, from, next) => {  if (to.path !== '/login') {    if (Auth.default.user.authenticated) {      next()    } else {      router.push('/login')    }  } else {    next()  } }) export default router Тут мы поправили команды импорта, имена компонентов, теги и пути, а также сделали некоторые улучшения в router.beforeEach, так как мы собираемся защищать любой маршрут, отличающийся от login, мы убираем meta из маршрута страницы Home.Вывод информации о клиентах Вместо того, чтобы создавать новую страницу, предназначенную для вывода списка клиентов, мы будем использовать уже существующую страницу Home. Поэтому вернёмся к компоненту Home и создадим новый массив в данных компонента, дав ему имя clients, а также создадим массив clientHeaders и логическую переменную budgetsVisible:return {  budgets: [],  clients: [],  budgetHeaders: ['Client', 'Title', 'Status', 'Actions'],  clientHeaders: ['Client', 'Email', 'Phone', 'Actions'],  budgetsVisible: true,  snackbar: false,  timeout: 6000,  message: '' } Теперь добавим новый метод:getAllClients () {  Axios.get(`${BudgetManagerAPI}/api/v1/client`, {    headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },    params: { user_id: this.$cookie.get('user_id') }  }).then(({data}) => {    this.clients = this.dataParser(data, '_id', 'client', 'email', 'phone')  }).catch(error => {    this.snackbar = true    this.message = error.message  }) }, Вызовем этот метод при монтировании компонента:mounted () {  this.getAllBudgets()  this.getAllClients() }, Как теперь вывести сведения о клиентах? Очень просто. Достаточно внести ещё некоторые изменения в компонент Home:<template>  <main class="l-home-page">    <app-header :budgetsVisible="budgetsVisible" @toggleVisibleData="budgetsVisible = !budgetsVisible"></app-header>    <div class="l-home">      <h4 class="white--text text-xs-center my-0">        Focus Budget Manager      </h4>      <list>        <list-header slot="list-header" :headers="budgetsVisible ? budgetHeaders : clientHeaders"></list-header>        <list-body slot="list-body"                   :budgetsVisible="budgetsVisible"                   :data="budgetsVisible ? budgets : clients">        </list-body>      </list>    </div>    <v-snackbar :timeout="timeout"                bottom="bottom"                color="red lighten-1"                v-model="snackbar">      {{ message }}    </v-snackbar>  </main> </template> <script>  import Axios from 'axios'  import Authentication from '@/components/pages/Authentication'  import ListHeader from './../List/ListHeader'  import ListBody from './../List/ListBody'  const BudgetManagerAPI = `http://${window.location.hostname}:3001`  export default {    components: {      'list-header': ListHeader,      'list-body': ListBody    },    data () {      return {        budgets: [],        clients: [],        budgetHeaders: ['Client', 'Title', 'Status', 'Actions'],        clientHeaders: ['Client', 'Email', 'Phone', 'Actions'],        budgetsVisible: false,        snackbar: false,        timeout: 6000,        message: ''      }    },    mounted () {      this.getAllBudgets()      this.getAllClients()    },    methods: {      getAllBudgets () {        Axios.get(`${BudgetManagerAPI}/api/v1/budget`, {          headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },          params: { user_id: this.$cookie.get('user_id') }        }).then(({data}) => {          this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state')        }).catch(error => {          this.snackbar = true          this.message = error.message        })      },      getAllClients () {        Axios.get(`${BudgetManagerAPI}/api/v1/client`, {          headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },          params: { user_id: this.$cookie.get('user_id') }        }).then(({data}) => {          this.clients = this.dataParser(data, 'name', 'client', 'email', 'phone')        }).catch(error => {          this.snackbar = true          this.message = error.message        })      },      dataParser (targetedArray, ...options) {        let parsedData = []        targetedArray.forEach(item => {          let parsedItem = {}          options.forEach(option => (parsedItem[option] = item[option]))          parsedData.push(parsedItem)        })        return parsedData      }    }  } </script> <style lang="scss" scoped>  @import "./../../assets/styles";  .l-home {    background-color: $background-color;    margin: 25px auto;    padding: 15px;    min-width: 272px;  } </style> Теперь мы передаём переменную budgetVisible в Header и, кроме того, используем эту переменную в тернарном операторе сравнения для вывода нужных данных. В Header так же попадает переменная toggleVisibleData, где мы инвертируем значение budgetsVisible. Причина, по которой мы передаём в Header свойства, заключается в том, что благодаря такому подходу мы можем сделать ещё некоторые улучшения, о которых поговорим ниже. Кроме того, в слотах list-header и list-body мы используем тернарные операторы сравнения.Итак, теперь внесём улучшения в Header:<template>  <header class="l-header-container">    <v-layout row wrap :class="budgetsVisible ? 'l-budgets-header' : 'l-clients-header'">      <v-flex xs12 md5>        <v-text-field v-model="search"                      label="Search"                      append-icon="search"                      :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'">        </v-text-field>      </v-flex>      <v-flex xs12 offset-md1 md1>        <v-btn block               :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'"               @click.native="$emit('toggleVisibleData')">               {{ budgetsVisible ? "Clients" : "Budgets" }}        </v-btn>      </v-flex>      <v-flex xs12 offset-md1 md2>        <v-select label="Status"                  :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'"                  v-model="status"                  :items="statusItems"                  single-line>        </v-select>      </v-flex>      <v-flex xs12 offset-md1 md1>        <v-btn block color="red lighten-1 white--text" @click.native="submitSignout()">Sign out</v-btn>      </v-flex>    </v-layout>  </header> </template> <script>  import Authentication from '@/components/pages/Authentication'  export default {    props: ['budgetsVisible'],    data () {      return {        search: '',        status: '',        statusItems: [          'All', 'Approved', 'Denied', 'Waiting', 'Writing', 'Editing'        ]      }    },    methods: {      submitSignout () {        Authentication.signout(this, '/login')      }    }  } </script> <style lang="scss">  @import "./../assets/styles";  .l-header-container {    background-color: $background-color;    margin: 0 auto;    padding: 0 15px;    min-width: 272px;    .l-budgets-header {      label, input, .icon, .input-group__selections__comma {        color: #29b6f6!important;      }    }    .l-clients-header {      label, input, .icon, .input-group__selections__comma {        color: #66bb6a!important;      }    }    .input-group__details {      &:before {        background-color: $border-color-input !important;      }    }    .btn {      margin-top: 15px;    }  } </style> Теперь цвет заголовка будет зависеть от состояния переменной budgetsVisible. Если документы видимы, заголовок будет иметь светло-синий цвет, если нет — зелёный.Кроме того, цвет и надпись на кнопке будут меняться в зависимости от значения budgetVisible, по её щелчку вызывается обработчик соответствующего события, меняющий состояние логической переменной.Кроме того, мы внесли некоторые изменения в scss.И, наконец, займёмся компонентом ListBody:<template>  <section class="l-list-body">    <div class="md-list-item"         v-if="data != null"         v-for="item in data">         <div :class="budgetsVisible ? 'md-budget-info white--text' : 'md-client-info white--text'"              v-for="info in item"              v-if="info != item._id">           {{ info }}         </div>        <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'">          <v-btn small flat color="light-blue lighten-1">            <v-icon small>visibility</v-icon>          </v-btn>          <v-btn small flat color="yellow accent-1">            <v-icon>mode_edit</v-icon>          </v-btn>          <v-btn small flat color="red lighten-1">            <v-icon>delete_forever</v-icon>          </v-btn>        </div>    </div>  </section> </template> <script>  export default {    props: ['data', 'budgetsVisible']  } </script> <style lang="scss">  @import "./../../assets/styles";  .l-list-body {    display: flex;    flex-direction: column;    .md-list-item {      width: 100%;      display: flex;      flex-direction: column;      margin: 15px 0;      @media (min-width: 960px) {        flex-direction: row;        margin: 0;      }      .md-budget-info {        flex-basis: 25%;        width: 100%;        background-color: rgba(0, 175, 255, 0.45);        border: 1px solid $border-color-input;        padding: 0 15px;        display: flex;        height: 35px;        align-items: center;        justify-content: center;        &:first-of-type, &:nth-of-type(2) {          text-transform: capitalize;        }        &:nth-of-type(3) {          text-transform: uppercase;        }        @media (min-width: 601px) {          justify-content: flex-start;        }      }      .md-client-info {        @extend .md-budget-info;        background-color: rgba(102, 187, 106, 0.45)!important;        &:nth-of-type(2) {          text-transform: none;        }      }      .l-budget-actions {        flex-basis: 25%;        display: flex;        background-color: rgba(0, 175, 255, 0.45);        border: 1px solid $border-color-input;        align-items: center;        justify-content: center;        .btn {          min-width: 45px !important;          margin: 0 5px !important;        }      }      .l-client-actions {        @extend .l-budget-actions;        background-color: rgba(102, 187, 106, 0.45)!important;      }    }  } </style> Изменения, внесённые сюда, похожи на те, что мы выполнили в коде компонента Header.Промежуточные результаты Вот как теперь выглядит список документов:image А вот — список клиентов:image Теперь, когда мы можем видеть списки зарегистрированных документов и клиентов, создадим плавающую кнопку (Floating Action Button, FAB), которая будет содержать кнопки, позволяющие работать со списком. Всё ещё находясь в коде компонента Home, добавим следующий код ниже v-snackbar:<v-fab-transition>  <v-speed-dial v-model="fab"                bottom                right                fixed                direction="top"                transition="scale-transition">      <v-btn slot="activator"             color="red lighten-1"             dark             fab             v-model="fab">            <v-icon>add</v-icon>            <v-icon>close</v-icon>      </v-btn>      <v-tooltip left>        <v-btn color="light-blue lighten-1"               dark               small               fab               slot="activator">              <v-icon>assignment</v-icon>        </v-btn>        <span>Add new Budget</span>      </v-tooltip>      <v-tooltip left>        <v-btn color="green lighten-1"               dark               small               fab               slot="activator">              <v-icon>account_circle</v-icon>        </v-btn>        <span>Add new Client</span>      </v-tooltip>  </v-speed-dial> </v-fab-transition> В FAB содержится три кнопки. Первая действует как активатор для FAB, вторая служит для добавления документов, третья — для добавления клиентов. Добавим теперь новое логическое значение для FAB в данные компонента Home:data () {  return {    budgets: [],    clients: [],    budgetHeaders: ['Client', 'Title', 'Status', 'Actions'],    clientHeaders: ['Client', 'Email', 'Phone', 'Actions'],    budgetsVisible: true,    snackbar: false,    timeout: 6000,    message: '',    fab: false  } }, Здесь мы добавили логическое значение fab, которое используется для указания того, активна плавающая кнопка или нет.Итоги Сегодня мы внесли некоторые улучшения в компоненты, переработали их с прицелом на повторное использование кода, добавили функционал вывода списка клиентов. Полный вариант приложения, как обычно, можно найти в репозитории проекта.

http://www.banmark.fi/?aftepatius=preguntas-para-conocer-a-un-chico-cristiano&0c6=ee В следующем материале мы продолжим работу над приложением, и, вероятнее всего, её завершим.

get link Уважаемые читатели! Стремитесь ли вы к возможности повторного использования кода при работе над своими проектами?

go site Authors:

http://sundekantiner.dk/bioret/829 opcje binarne prawda czy fałsz Read more https://habrahabr.ru/post/342402/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best