Создайте страницу меню ресторана

Создание коллекции элементов списка, организованных по разделам, — это один из сценариев, которого нельзя избежать при создании приложения любого типа. В этом руководстве я покажу вам, как именно это сделать, создав простую страницу меню ресторана с помощью Vue.js и Firebase Cloud Firestore.

Попутно вы также узнаете, как правильно структурировать и запрашивать базу данных Cloud Firestore.

Давайте посмотрим, что мы будем строить к концу этого урока.

В типичном примере выше у меня вверху есть название ресторана. Ниже вы можете увидеть пункты меню, сгруппированные по разделам/типам, такие как Закуски, Дум Брияни и т. д.

Довольно прямолинейно.

Окончательная структура API объекта JavaScript

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

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

Чтобы сгенерировать этот объект, просто получите данные из Cloud Firestore и объедините их вместе, что очень похоже на вывод API при извлечении данных из HTTP-запроса).

Давайте подробно рассмотрим выходной объект JavaScript.

Каждый объект раздела/типа в массиве меню имеет свойство title и свойство menuItems, которое является еще одним массивом объектов. Каждый объект внутри пунктов меню состоит из свойств заголовка, описания и цены.

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

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

Запросы Cloud Firestore неглубокие, поэтому, когда вы делаете запрос к документу, в нем не будет подколлекции.

Рекомендуется Быстрое изучение CRUD-запросов Firestore [Руководство]

Вот инфографика, показывающая, как структурировать данные для страницы меню ресторана.

Как видите, у меня есть ресторанная коллекция, в которой есть несколько документов. Каждый документ имеет два поля: title, которое содержит строковое значение, и menu-types, которое является вложенной коллекцией.

Вот скриншот фактической структуры базы данных Cloud Firestore с примерами данных.

Добавьте некоторые данные, подобные приведенной выше структуре, в базу данных Cloud Firestore, чтобы их было легко отслеживать.

Создать проект Vue.js

1. Продолжайте и создайте проект vue.js, используя webpack.

vue init webpack my-project

2. Затем установите на него npm.

npm install

3. Запустите приложение.

npm run dev

4. После запуска приложения vue.js перейдите к HelloWord.vue и удалите файл.

5. App.vue — это файл, в который я буду помещать весь свой код, чтобы упростить этот урок.

6. Добавьте ссылку Материализировать CSS в index.html, что необязательно.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">

Инициализировать Firebase

1. Установите Firebase.

npm install --save firebase

2. Инициализируйте Firebase внутри main.js.

import firebase from "firebase"; 
var firebaseConfig = { 
    apiKey: "*************************", 
    authDomain: "*************************", 
    databaseURL: "*************************", 
    projectId: "*************************", 
    storageBucket: "*************************", 
    messagingSenderId: "*************************", 
    appId: "*************************" }; 
firebase.initializeApp(firebaseConfig);

Получите код инициализации из вашего проекта Firebase в консоли Firebase.

Запросить данные ресторана

data() { 
    return { 
        restaurant: {},
        error:"" 
    }; 
},

2. Определите функцию getRestaurantData() внутри объекта methods:{}.

Внутри блока try-catch объявите объект области действия функции с именем restaurant, который будет иметь окончательный выходной объект JavaScript.

methods: {
    async getRestaurantData(resId) {
        try {
            var restaurant = {};
            // Get Restaurant Data
            var resRef = firebase.firestore().collection("restaurants");
            var resSnap = await resRef.doc(resId).get();
            restaurant = resSnap.data();
            restaurant.id = resSnap.id;
            console.log(restaurant)
           
            return restaurant
        } catch (e) {
            return {
                errorMsg: "Something went wrong. Please Try Again."
            }
        }
    }
}

Также запустите для него метод get(), который представляет собой асинхронный вызов, который возвращает обещание, которое будет выполнено, когда снимок документа будет доступен для переменной resSnap.

Наконец, назначьте данные объекту restaurant, включая идентификатор документа, и верните его.

3. Вызвать метод getRestaurantData() внутри функции created() {}.

Внутри метода created() вызовите метод getRestaurantData(), передав фактический идентификатор ресторана в качестве аргумента, который также вернет обещание.

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

created() {
    this.getRestaurantData("anjappar-chettinad").then(response => {
        if (response && response.errorMsg) {
            this.error = response.errorMsg;
        } else {
            this.restaurant = response;
        }
    });
},

4. Вы можете получить сообщение об ошибке «Отсутствует/недостаточное разрешение» при запросе Cloud Firestore в консоли браузера.

Чтобы это исправить, перейдите на вкладку «Правила» базы данных Firebase и вставьте следующее:

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
    }
  }
}

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

Рекомендуется 6 обязательных правил безопасности Firestore

Но почему? Что случилось с подколлекцией типов меню? Это произошло потому, что, как я упоминал ранее, запросы Cloud Firestore неглубокие, поэтому он извлекает только документы с полями, но не вложенные коллекции.

Чтобы получить подколлекции, вам нужно будет сделать отдельный запрос для получения меню-типов (разделов).

Рекомендуется –› Быстрое изучение CRUD-запросов Firestore [Руководство]

Извлечение и объединение данных подколлекций

1. Внутри функции getRestaurantData() получите ссылку на подколлекцию menu-types.

2. Запустите на нем метод get() , который вернет обещание. Это обещание будет выполнено, когда будет доступен снимок меню.

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

// Get Restaurant Menu Types
var menuTypesSnap = await resRef
    .doc(resId)
    .collection("menu-types")
    .get();
restaurant.menu = [];
for (const menuTypeObj of menuTypesSnap.docs) {
    var menuType = menuTypeObj.data();
    menuType["menuItems"] = [];
    // Get Restaurant Menu Items
    var menuItemsSanp = await menuTypeObj.ref
        .collection("menu-items")
        .get();
    for (const menuItem of menuItemsSanp.docs) {
        menuType["menuItems"].push(menuItem.data());
    }
    restaurant.menu.push(menuType);
}

Затем создайте свойство с именем menu внутри объекта restaurant и на данный момент установите его значение в пустой массив. Здесь я собираюсь добавить разделы меню/типовые объекты.

Вы можете использовать forEach вместо for-of. Но for-of будет ждать завершения синхронной операции, прежде чем перейти к следующему взаимодействию, чего я и хотел.

Давайте получим ссылку на коллекцию menu-items. Используя ключевое слово await, он приостановит цикл, пока снимок не станет доступным.

Наконец, передавайте объект menuType в свойство restaurant.menu на каждой итерации.

На этом этапе объект restaurant с областью действия будет иметь правильную структуру данных, необходимую для построения представления.

Вот полный исходный код кода getRestaurantData().

methods: {
    async getRestaurantData(resId) {
        try {
            var restaurant = {};
            // Get Restaurant Data
            var resRef = firebase.firestore().collection("restaurants");
            var resSnap = await resRef.doc(resId).get();
            restaurant = resSnap.data();
            restaurant.id = resSnap.id;
            // Get Restaurant Menu Types
            var menuTypesSnap = await resRef
                .doc(resId)
                .collection("menu-types")
                .get();
            restaurant.menu = [];
            for (const menuTypeObj of menuTypesSnap.docs) {
                var menuType = menuTypeObj.data();
                menuType["menuItems"] = [];
                // Get Restaurant Menu Items
                var menuItemsSanp = await menuTypeObj.ref
                    .collection("menu-items")
                    .get();
                for (const menuItem of menuItemsSanp.docs) {
                    menuType["menuItems"].push(menuItem.data());
                }
                restaurant.menu.push(menuType);
            }
            return restaurant;
        } catch (e) {
            return {
                errorMsg: "Something went wrong. Please Try Again."
            };
        }
    }
}

Рекомендуется Создать безопасное приложение To-Do с помощью Vue + Firestore [Аутентификация]

Рендеринг объекта API ресторана в представлении

Внутри класса контейнера, который является частью Materialize CSS, добавьте элемент div, который отвечает за отображение ошибки, если таковая имеется.

Создайте li внутри ul и покажите название ресторана, которое является одним из свойств верхнего уровня объекта ресторана.

Создайте элемент ul для каждого раздела, проходя через restaurant.menu, который представляет собой массив объектов (разделы/типы меню), и покажите заголовок раздела/типа меню, используя свойство menu.title, которое будет печать Закуски, Дум Брияни и др.

<template>
    <div id="app">
        <div class="container">
            <div class="red" v-if="error">{{error}}</div>
            <ul class="collection with-header">
                <li class="collection-header #212121 grey darken-4 white-text">
                    <h4>{{restaurant.title}}</h4>
                </li>
            </ul>
            <ul class="collection with-header" v-for="menu in restaurant.menu" :key="menu.id">
                <li class="collection-item pink white-text">
                    <div>{{menu.title}}</div>
                </li>
                <li class="collection-item" v-for="item in menu.menuItems" :key="item.id">
                    <div>
                        {{item.title}}
                        <br />
                        <span class="grey-text">{{item.description}}</span>
                        <a href="#!" class="secondary-content">${{item.price}}</a>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</template>

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

Для этого выполните итерацию по массиву menu.menuItems, извлеките свойства title, description и price и покажите их в соответствующем месте, как в коде выше.

Если все пойдет хорошо, у вас должна появиться страница меню ресторана, как на скриншоте ниже.

Вывод

В этом кратком руководстве я показал вам, как создать простую страницу меню ресторана, элементы меню которой организованы по разделам. Попутно вы узнали, чем структура данных, хранящаяся в Cloud Firestore, отличается от окончательной выходной структуры объектов JavaScript.

Наконец, вы поняли, как запрашивать и объединять данные из Cloud Firestore, используя цикл for-of и await-async для сопоставления конечного объекта JavaScript, который затем преобразуется в страницу меню ресторана.

Вопрос к вам
Как бы вы структурировали свои данные при создании подобного приложения?

Если у вас другой подход, не стесняйтесь поделиться им в разделе комментариев ниже…

Я с нетерпением жду ответа от вас… Спасибо, что прочитали…

Рекомендуемое Создание безопасного приложения To-Do с помощью Vue + Firestore [Аутентификация]