С легкостью добавляйте динамические фильтры к своим данным, используя Vue и Cosmic JS Rest API.

TL;DR

вступление

Фильтрация данных — одна из наиболее распространенных функций любого приложения, работающего с данными, будь то интерфейсное или серверное приложение. Функция «Фильтр» используется для поиска записей в таблице или наборе данных, соответствующих определенным критериям. Например, если у вас есть список под названием «Книги» на веб-странице, и вы хотите показывать только те книги, которые в настоящее время продаются со скидкой. Вы можете сделать это, используя функцию фильтра.

Что именно мы строим?

В этом коротком руководстве мы создаем одностраничное веб-приложение, состоящее из двух частей. Первая часть будет списком студентов. Список будет отображаться в виде таблицы с несколькими столбцами для каждого учащегося. Каждый столбец будет соответствовать атрибуту данных записи учащегося. В конце списка также будет сводная строка, в которой указано количество записей. Это структура данных студента:

student: {
    id: unique identifier,
    firstName: string,
    lastName: string,
    dob: date (date of birth),
    gpa: number,
    address: string,
    city: string,
    county: string,
    state: string,
    zip: number
}

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

    string: [contains, startWith]
    date: [equal, greaterThan, lessThan, between]
    number: [equal, greaterThan, lessThan, between]
    lookup: [is, isNot]

Таким образом, в основном мы можем создать 12 функций сравнения, которые мы можем использовать со всеми нашими полями или любыми полями, которые мы можем добавить в будущем. Итак, давайте начнем с нашего приложения и посмотрим, как мы можем реализовать эти функции.

Запуск вашего приложения Vue

Чтобы запустить новое приложение, вам необходимо установить Вью и откройте новое окно терминала и введите следующее:

# initiating new Vue app
vue create col-admin

Vue CLI v3.0.0-rc.9
┌───────────────────────────┐
│  Update available: 3.5.1  │
└───────────────────────────┘
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

# adding other js libraries
vue add vue-cli-plugin-vuetify
? Choose a preset: Default (recommended)
✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify

# adding the backend library
npm install --save cosmicjs

после этого у нас должно быть готово наше стартовое приложение для настройки. Если вы хотите запустить приложение, просто откройте окно терминала и введите npm run serve а затем откройте приложение из своего браузера с этого URL-адреса приложения по умолчанию и вы готовы перейти к следующему шагу.

Настройте свой Rest API с помощью Cosmic JS

Как мы упоминали ранее, цель этого приложения — отобразить список студентов, а затем использовать функцию фильтра, чтобы сузить список. В этом проекте мы будем использовать Cosmic JS для хранения наших данных, а также для сервера данных с использованием встроенного Rest API, который поставляется с Cosmic JS.

  • Зарегистрируйтесь бесплатно Космический JS учетная запись.
  • Добавить новое ведро из панель разработки
  • Добавить новое Object Type с приборной панели. и укажите следующие атрибуты для этого типа объекта

космический js-студенты-таблица.png

  • На вкладке метаполя типа объекта добавьте следующие поля:
    SID: text field,
    firstName: text field,
    lastName: text field,
    DOB: text field,
    GPA: text field,
    Address: text field,
    City: text field,
    County: text field,
    State: text field,
    Zip: text field
  • Добавьте некоторые данные в таблицу «Студенты». Если вы хотите, вы можете скопировать мою таблицу данных из Cosmic JS, импортировав мой col-admin-bucket под своей учетной записью. Я вставил около 300 записей, поэтому вам не нужно вводить всю эту информацию вручную.
  • Получите доступ к своим данным Cosmic JS через встроенный Rest API по этому URL-адресу:
  • Взгляните на Документация по Cosmic JS API для получения подробного списка всех доступных вам API.

После этого вы сможете получить доступ к своим внутренним данным через Rest API.

Добавить хранилище данных с помощью Vuex

В корневую папку нашего проекта добавим новую папку ./src/store/ и двигаться ./src/store.js в папке магазина. Нам также нужно будет создать новый файл под ./src/api/cosmic.js

const Cosmic = require('cosmicjs')

const config = {
    bucket: {
        slug: process.env.COSMIC_BUCKET || 'col-admin',
        read_key: process.env.COSMIC_READ_KEY,
        write_key: process.env.COSMIC_WRITE_KEY
    }
}

module.exports = Cosmic().bucket(config.bucket)

Этот небольшой скрипт будет использоваться в качестве объекта подключения Cosmic JS.

Нам также нужно будет создать новый файл под ./src/store/modules/cosmic.js для всех функций, связанных с данными Cosmic JS.

import Cosmic from '../../api/cosmic' // used for Rest API

const actions = {
    async fetchStudents ({commit, dispatch}) {
        const recordLimit = 25
        let skipPos = 0
        let fetchMore = true

        while (fetchMore) {
            try {
                const params = {
                    type: 'students',
                    limit: recordLimit,
                    skip: skipPos
                }
                let res = await Cosmic.getObjects(params)
                if (res.objects && res.objects.length) {
                    let data = res.objects.map((item) => {
                        return {...item.metadata, id: item.metadata.sid,
                            firstName: item.metadata.firstname,
                            lastName: item.metadata.lastname }
                    })
                    commit('ADD_STUDENTS', data)
                    commit('SET_IS_DATA_READY', true)
                    // if fetched recodrs lenght is less than 25 then we have end of list
                    if (res.objects.length < recordLimit) fetchMore = false
                } else {
                    fetchMore = false
                }
                skipPos += recordLimit
            }
            catch (error) {
                console.log(error)
                fetchMore = false
            }
        }
        dispatch('fetchStates')
    }
}

export default {
    actions
}

Пока у нас есть только одна функция fetchStudents. Эта функция вызовет Cosmic JS getObjects вытащить 25 записей за раз. И он будет делать это внутри цикла while, пока мы не дойдем до конца или пока не будет найдено больше записей. Мы можем определить конец данных количества строк данных будет меньше 25 записей. После получения всех данных из Rest API мы вызовем метод ADD_STUDENTS мутация для хранения этих записей внутри переменной состояния Vuex. Для получения дополнительной информации о магазине Vuex, пожалуйста, прочитайте документация.

В конце этой функции есть еще один вызов fetchStates. Эта функция просто перебирает все записи учащихся, получает уникальный код состояния и сохраняет его в states переменная. Это можно использовать позже в фильтре по компоненту раскрывающегося списка состояния.

Это остальная часть магазина Vuex.

import Vue from 'vue'
import Vuex from 'vuex'
import _ from 'underscore'
import cosmicStore from './modules/cosmic'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        isDataReady: false,
        students: [],
        states: []
    },
    getters: {
        students (state) {
            return state.students
        },
        isDataReady (state) {
            return state.isDataReady
        },
        states (state) {
            return state.states
        }
    },
    mutations: {
        SET_STUDENTS (state, value) {
            state.students = value
        },
        SET_IS_DATA_READY (state, value) {
            state.isDataReady = value
        },
        ADD_STUDENTS (state, value) {
            state.students.push(...value)
        },
        SET_STATES (state, value) {
            state.states = value
        }
    },
    actions: {
        fetchStates ({commit, state}) {
            let states = []
            states = _.chain(state.students).pluck('state').uniq().sortBy((value) => value).value()
            commit('SET_STATES', states)
        }
    },
    modules: {
        cosmicStore
    }
})

Стилизация приложений с помощью Vuetify

Для этого проекта мы будем использовать Vuetify как наша библиотека внешних компонентов. Это очень полезно, особенно если вы хотите использовать Материальный дизайн Google в свой проект без больших накладных расходов. Кроме того, Vuetify великолепен, потому что в нем есть множество встроенных компонентов пользовательского интерфейса, которые полностью загружены. После добавления Vuetify в ваш проект с помощью команды добавления Vue CLI вы можете просто ссылаться на компоненты Vuetify из своих шаблонов страниц.
Давайте посмотрим на App.vue основной макет.

<template>
    <v-app>
        <v-toolbar app color="green accent-4">
            <v-icon>school</v-icon>
            <v-toolbar-title v-text="title"></v-toolbar-title>
            <v-spacer></v-spacer>
        </v-toolbar>

        <v-content>
            <router-view/>
        </v-content>

        <v-footer :fixed="fixed" app color="green accent-4">
            <span>&copy; 2017</span>
        </v-footer>
    </v-app>
</template>

<script>

export default {
  name: 'App',
  data () {
    return {
      fixed: false,
      title: 'College Query'
    }
  }
}
</script>

В приведенном выше шаблоне вы можете видеть, что макет страницы нашего приложения состоит из трех разделов:

  • v-toolbar: верхний компонент панели инструментов
  • v-content: который будет содержать внутреннее содержимое любой страницы
  • v-нижний колонтитул: в котором будут указаны авторские права приложения и контактная информация.

Добавление представления приложения и компонентов

Вы можете заметить, что в папке «./src» есть две папки:

  • ./src/components: эта папка будет использоваться для хранения всех веб-компонентов, которые можно использовать на любой странице. В настоящее время мы еще не используем никаких компонентов! но если наше приложение станет более сложным, мы сможем легко разбить каждую страницу на небольшие компоненты.
  • ./src/views: эта папка используется для хранения представлений. Представление эквивалентно веб-странице. В настоящее время у нас есть Home.vue которая является главной страницей, а About.vue

Добавление сетки данных на главную страницу

в Home.vue страницы у нас будет два основных раздела:

  • фильтры данных: содержит все фильтры, которые может выбрать пользователь.
  • сетка данных: это список учащихся, отображаемый как компонент сетки данных. Для нашей цели мы будем использовать Vuetify data-table составная часть.

Итак, давайте взглянем на шаблон домашней страницы:

<template>
    <v-container grid-list-lg>
        <v-layout row wrap>
            <v-flex xs2>
                <v-select
                    :items="filterFields"
                    v-model="filterField"
                    label="Filter by">
                </v-select>
            </v-flex>
            <v-flex xs2>
                <v-select
                    :items="filterOperators"
                    v-model="filterOperator"
                    label="Operator">
                </v-select>
            </v-flex>
            <v-flex xs2 v-show="filterOperator && filterType !== 'lookup'">
                <v-text-field
                    name="filterTerm"
                    :label="filterTermLabel"
                    :mask="filterTermMask"
                    :rules="filterTermRules"
                    return-masked-value
                    v-model="filterTerm"
                ></v-text-field>
            </v-flex>
            <v-flex xs2 v-show="filterOperator === 'between'">
                <v-text-field
                    name="filterTerm2"
                    :label="filterTermLabel"
                    :mask="filterTermMask"
                    :rules="filterTermRules"
                    return-masked-value
                    v-model="filterTerm2"
                ></v-text-field>
            </v-flex>
            <v-flex xs2 v-show="filterType === 'lookup'">
                <v-autocomplete
                  :items="filterLookupItems"
                  :label="filterLookupLabel"
                  v-model="filterLookupValue"
                ></v-autocomplete>
            </v-flex>
            <v-flex xs2>
                <v-btn color="warning" @click="onClearAllFilters">Clear All</v-btn>
            </v-flex>
            <v-flex xs12>

                <v-data-table
                    :headers="headers"
                    :items="filteredStudents"
                    xhide-actions
                    :pagination.sync="pagination"
                    :loading="!isDataReady"
                    class="elevation-1">
                    <template slot="items" slot-scope="props">
                        <td>{{ props.item.id }}</td>
                        <td>{{ props.item.firstName }}</td>
                        <td>{{ props.item.lastName }}</td>
                        <td>{{ props.item.dob | shortDate(dateFilterFormat) }}</td>
                        <td>{{ props.item.gpa | gpaFloat }}</td>
                        <td>{{ props.item.address }}</td>
                        <td>{{ props.item.city }}</td>
                        <td>{{ props.item.county }}</td>
                        <td>{{ props.item.state }}</td>
                        <td>{{ props.item.zip }}</td>
                    </template>

                    <template slot="pageText" slot-scope="props">
                        Total rows: {{ props.itemsLength }}
                    </template>

                </v-data-table>

            </v-flex>
        </v-layout>
    </v-container>
</template>

Как видите, из кода выше. в v-data-table компонент использует filteredStudents переменная в качестве источника данных. Внутри хранилища Vuex у нас есть две переменные состояния:

  • студенты: массив, содержащий всех студентов, выбранных из базы данных.
  • filterdStudents: массив, содержащий только студентов, соответствующих критериям фильтра. Изначально, если фильтр не выбран, эта переменная будет иметь то же значение, что и students переменная.

data-table компонент также имеет три раздела:

  • заголовки: в настоящее время мы храним заголовок в переменной данных, называемой заголовками
  • элементы: это раздел данных, который подает filteredStudents переменная
  • нижний колонтитул: отобразит элементы управления разбиением на страницы и информацию о количестве записей.

Добавление компонентов пользовательского интерфейса фильтров данных

Как видно из шаблона страницы Home.vue, компоненты фильтров состоят из следующих компонентов:

  • Фильтровать по: в настоящее время мы должны выбрать одно из доступных полей, таких как имя, фамилия, доб…
  • Оператор фильтра: это будет что-то вроде Contains‘Начать с’, ‘Больше чем’… Операторы будут меняться в зависимости от типа поля
  • Термин фильтра: это пользовательский ввод для выбранного фильтра. В настоящее время у нас есть два условия фильтра на случай, если нам нужно выбрать диапазон. Например, если пользователь выбирает дату рождения между ними, нам нужны два поля ввода даты.
  • Поиск фильтра: раскрывающийся список на случай, если критерии фильтра необходимо выбрать из заданного списка. В нашем приложении, когда нам нужно отфильтровать по состоянию, нам нужно выбрать значение из раскрывающегося списка.

Добавить функциональность фильтра

Мы можем обобщить функциональность фильтра по этим переменным:

    headers: [
        { text: 'ID', align: 'left', sortable: false, value: 'id' },
        { text: 'First', value: 'firstName' },
        { text: 'Last', value: 'lastName' },
        { text: 'DOB', value: 'dob', dataType: 'Date' },
        { text: 'GPA', value: 'gpa' },
        { text: 'Address', value: 'address' },
        { text: 'City', value: 'city' },
        { text: 'County', value: 'county' },
        { text: 'State', value: 'state' },
        { text: 'Zip', value: 'zip' }
    ],

Это заголовки таблицы данных.

    filterFields: [
        {text: 'First Name', value: 'firstName', type: 'text'},
        {text: 'Last Name', value: 'lastName', type: 'text'},
        {text: 'DOB', value: 'dob', type: 'date'},
        {text: 'GPA', value: 'gpa', type: 'number'},
        {text: 'Address', value: 'address', type: 'text'},
        {text: 'City', value: 'city', type: 'text'},
        {text: 'County', value: 'county', type: 'text'},
        {text: 'Zip', value: 'zip', type: 'number'},
        {text: 'State', value: 'state', type: 'lookup'}
    ],

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

    filterDefs: {
        text: {contains: {display: 'Contains', function: this.filterByTextContains},
               startsWith: {display: 'Starts with', function: this.filterByTextStartsWith}},
        number: {equal: {display: 'Equal', function: this.filterByNumberEqual, decimalPoint: 1},
                 greater: {display: 'Greater than', function: this.filterByNumberGreater, decimalPoint: 1},
                 less: {display: 'Less than', function: this.filterByNumberLess, decimalPoint: 1},
                 between: {display: 'Between', function: this.filterByNumberBetween, decimalPoint: 1}},
        date: {equal: {display: 'Equal', function: this.filterByDateEqual, format: 'MM/DD/YYYY'},
                 greater: {display: 'Greater than', function: this.filterByDateGreater, format: 'MM/DD/YYYY'},
                 less: {display: 'Less than', function: this.filterByDateLess, format: 'MM/DD/YYYY'},
                 between: {display: 'Between', function: this.filterByDateBetween, format: 'MM/DD/YYYY'}},
        lookup: {is: {display: 'Is', function: this.filterByLookupIs},
                 isNot: {display: 'Is not', function: this.filterByLookupIsNot}}
    },

Переменная filterDefs будет хранить информацию, которая сообщает нашему пользовательскому интерфейсу, какой оператор использовать для каждого типа поля. Мы также указываем в этой переменной конфигурации, какую функцию Javascript вызывать, когда нам нужно отфильтровать по выбранному полю. Эта переменная является моей собственной интерпретацией того, как функция фильтра должна быть настроена и разработана, однако вы, безусловно, можете обойтись без нее и использовать код Javascript с большим количеством if заявления.

Последняя часть — это фактические функции Javascript, которые мы будем вызывать для каждого типа фильтра. Я не буду перечислять их все, но давайте рассмотрим несколько примеров из Home.vue страница

    ...
    methods: {
        filterByTextContains (list, fieldName, fieldValue) {
            const re = new RegExp(fieldValue, 'i')
            return this.filterByRegExp(list, fieldName, fieldValue, re)
        },
        filterByTextStartsWith (list, fieldName, fieldValue) {
            const re = new RegExp('^' + fieldValue, 'i')
            return this.filterByRegExp(list, fieldName, fieldValue, re)
        },
        filterByRegExp(list, fieldName, fieldValue, regExp) {
            return list.filter(item => {
                if(item[fieldName] !== undefined) {
                    return regExp.test(item[fieldName])
                } else {
                    return true
                }
            })
        },
    ...
    }

Код выше имеет две функции filterByTextContains а также filterByTextStartsWith который будет вызываться каждый раз, когда пользователь использует функцию фильтрации текстового поля. И за этими двумя функциями мы вызываем filterByRegExp которая в основном представляет собой функцию, которая использует функцию регулярного выражения Javascript. Аналогичным образом я написал функции фильтрации для числовых полей, полей даты и полей поиска. Я использовал простую логику, такую ​​как сравнение дат, поиск массива или простой старый JS-оператор if. Наиболее важной частью является то, что эти функции должны быть достаточно общими, чтобы работать с любым полем, и ожидают несколько параметров, таких как список данных, которые должны быть отфильтрованы, имя поля и значение поля. Я рекомендую вам взглянуть на код для Home.vue для получения полной информации.

Использование вычисляемых свойств, наблюдателей и фильтров Vue

Вы также можете найти внутри ‘./src/views/Home.vue’ несколько методов в разделе вычислений, просмотра и фильтров. Вот как и почему я использую каждый тип.

  • Вычислено: я использовал эти вычисляемые свойства для students, filteredStudents, isDataReady, states. эти свойства будут автоматически обновляться каждый раз, когда изменяются базовые переменные, поступающие из хранилища Vuex. Это особенно полезно, если вы привязываете вычисляемые свойства к элементам пользовательского интерфейса и вносите изменения в пользовательский интерфейс или переключаетесь между разделами пользовательского интерфейса всякий раз, когда данные внутри вычисляемых свойств обновляются. Например isDataReady используется в таблице данных всякий раз, когда она false затем пользовательский интерфейс будет воспроизводить индикатор выполнения анимации ожидания, который сообщает пользователю, что данные загружаются. Однажды idDataReady обновляется до trueто индикатор загрузки исчезнет, ​​и в таблице будут отображаться фактические данные.

  • Наблюдатели: я использовал эти наблюдаемые свойства filterFieldа также filterOperator. разница в том, что отслеживаемые свойства не кэшируют значение, и каждый раз, когда базовые данные изменяются, функция будет вызываться. Я использовал это для обновления элементов пользовательского интерфейса фильтра на домашней странице.

  • Фильтры: не путайте фильтры Vue с фильтрацией данных. Фильтры — это функции, которые вы определяете в логике, а затем используете внутри HTML-шаблона для форматирования значения поля. Например, у меня есть shortDateа также gpaFloat функции, которые используются для форматирования даты и значений с плавающей запятой в желаемый формат отображения. Вы можете вызвать функцию фильтра из шаблона html, используя этот синтаксис <td>{{ props.item.gpa | gpaFloat }}</td>.

Я также хочу упомянуть, что я использовал обработчики событий жизненного цикла Vue, чтобы инициировать выборку данных из серверной части всякий раз, когда приложение запускается. Я делаю это из ./main.js файл

...
new Vue({
  store,
  router,
  render: h => h(App),
  created () {
    this.$store.dispatch('fetchStudents')
  }
}).$mount('#app')

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

330e50f0-5641-11e9-9183-0b7edebb0cbb-home-filter-action.gif

Вывод

В конце я хочу упомянуть, что создание простого приложения, подобного этому, звучит просто, однако создание расширяемого приложения может потребовать некоторого размышления и небольшого планирования, чтобы убедиться, что ваш код может легко разрешить будущие расширения и изменения без необходимости переписывать приложение. .
Также стоит упомянуть, что использование готового API-интерфейса, безусловно, сэкономило нам много времени. Наконец, я хочу добавить, что после завершения работы над приложением я понял, что страницу Home.vue, безусловно, можно разбить на небольшие компоненты, чтобы сделать ее более читабельной и удобной в сопровождении. Так что это, вероятно, будет следующим шагом, если вы когда-нибудь захотите использовать компоненты Vue.

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *