DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on • Edited on

QPANC - Parte 12 - Quasar - Serviços

QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.

24 Injetar objetos nos componentes, stores e routes.

Para o correto funcionamento de uma aplicação SSR, alguns serviços precisam ser instanciados de maneira isolada, de forma, que um usuário não consiga acessar a uma instancia destinada a outro usuário, em resumo, todos os serviços devem está preferencialmente isoladas dentro do escopo do usuário.

O primeiro passo para conseguir este objetivo, é criar um utilitário que irá injetar estes serviços nos components, store e routes.

QPANC.App/src/boot/inject.js

import Vue from 'vue' const mixins = [] const inject = function (bootCb) { return async function (ctx) { const { app, router, store } = ctx let boot if (typeof bootCb === 'function') { const response = bootCb(ctx) boot = response.then ? await response : response } else { boot = bootCb } for (const name in boot) { const key = `$${name}` if (mixins.indexOf(name) === -1) { mixins.push(name) Vue.mixin({ beforeCreate () { const options = this.$options if (options[name]) { this[key] = options[name] } else if (options.parent) { this[key] = options.parent[key] } } }) } app[name] = boot[name] store[key] = boot[name] router[key] = boot[name] } } } export default inject 

Agora, um exemplo de utilização.:

Digamos que tenhamos um serviço que faz o print da mensagem Hello World no console.:

class DummieService { sayHello ({ name }) { console.log(`${name}: Hello Wolrd`) } } 

E agora queremos injetar uma instancia do serviço acima em todos os componentes, stores e routes, poderemos faze-lo em um arquivo de boot.

import inject from './inject' import DummieService from 'services/dummie' export default inject(({ Vue }) => { const dummie = new DummieService() return { dummie: dummie } }) 

feito isto, poderemos acessar this.$dummie nos componentes e stores, assim como router.$dummie nos navigation guards, segue alguns exemplos.:

sample/page/dummie.js

export default { data () { const initialMsg = this.$dummie.sayHello({ name: 'me' }) return { msg: initialMsg } }, methods: { changeName ({ name }) { this.msg = this.$dummie.sayHello({ name }) } } } 

sample/store/dummie.js

export default { namespaed: true, state () { return { name: 'me', msg: '' } }, mutations: { name (state, value) { state.name = value }, msg (state, value) { state.msg = value } }, actions: { setMessage ({ commit }, name) { const msg = this.$dummie.sayHello({ name }) commit('msg', msg) } }, getters: { getMessage (state) { return this.$dummie.sayHello({ name: state.name }) } } } 

sample/router/dummie.js

export default function (context) { return { path: '/dummie', beforeEnter (to, from, next) { const { $dummie } = context.router const msg = $dummie.sayHello({ name: to.params.name }) if (msg.length > 50) { next('/hello-long-name') } else { next('/hello-short-name') } } } } 

25 Customizando os componentes do Quasar

Para esta tarefa, estarei usando a extensão @toby-mosque/utils, porém não irei mostrar um código equivalente sem o uso da extensão, pois envolve um transparent wrapper bem intricado, onde um pequeno deslize, pode levar a um bug difícil de rastrear ou a um comportamento indesejado.

Para esta demostração, estaremos personalizando apenas o QInput, mas podemos customizar qual quer componente, inclusive aqueles que são instalados através de extensões.

O primeiro passo, é criar um boot, aqui chamaremos ele de brand

quasar new boot brand 

QPANC.App/quasar.config.js

module.exports = function (ctx) { return { boots: [ 'brand' ] } } 

QPANC.App/src/boot/inject.js

import { factory } from '@toby.mosque/utils' import inject from './inject' import { QInput } from 'quasar' // "async" is optional export default inject(({ Vue }) => { const brand = {} brand.input = Vue.observable({ /* style: { 'font-size': '12px' }, class: { 'custom-input': true }, */ props: { outlined: true } }) factory.reBrand('q-input', QInput, brand.input) return { brand } }) 

O objeto brand será injetado nos componentes e stores, então poderemos acessa-lo futuramente.

A propriedade input é um Vue.observable, estão qual quer alteração nele, será refletido para os componentes que fazem uso dele. input é apenas um nome, poderia ser qual quer coisa no lugar.

O factory.reBrand, é o responsável por injetar o brand.input em todos os q-input. Ele fará uso apenas das propriedades style, class e props, onde estas propriedades serão injetadas no style, class e props do respectivo componente, sendo que nenhum deles é obrigatório.

Caso execute a aplicação agora, verá que todos os inputs estarão com a propriedade :outlined="true"

Alt Text

Agora, vamos adaptar o exemplo acima, para usar o Dark Mode, onde os inputs deverão ser filled no modo dark e outlined no light

QPANC.App/src/boot/inject.js

import { factory } from '@toby.mosque/utils' import inject from './inject' import { QInput, Dark } from 'quasar' // "async" is optional export default inject(({ Vue }) => { const brand = {} brand.input = Vue.observable({ /* style: { 'font-size': '12px' }, class: { 'custom-input': true }, */ props: { filled: Dark.isActive outlined: !Dark.isActive } }) factory.reBrand('q-input', QInput, brand.input) return { brand } }) 

O problema aqui, é que o Dark.isActive está sendo usado apenas como o valor inicial para o brand.input, porém quando o Dark.isActive é alterado, ele não é propagado para o brand.input.

Então, precisaremos de um watch no App.vue.

QPANC.App/src/App.vue

export default { name: 'App', watch: { '$q.dark.isActive' () { this.$brand.input.props.filled = this.$q.dark.isActive this.$brand.input.props.outlined = !this.$q.dark.isActive } } } 

E por fim, algumas prints.:

Alt Text
Alt Text

25 Persistência em Cookies

Como se trata de uma aplicação SSR, é natural que alguns dados serão persistidos no lado do cliente, coisas como o Token JWT, o tema e o idioma preferido.

Porém, alguns destes dados precisam ser acessados no lado do servidor, e como não podem ser recuperados de outra forma, teremos de usar Cookies.

O primeiro passo, será ativar o plugin responsável por ler os Cookies.

QPANC.App/quasar.config.js

module.exports = function (ctx) { return { framework: plugins: [ 'Cookies' ] } } } 

Agora, iremos adicionar um plugin para o vuex, no caso o vuex-persistedstate

yarn add vuex-persistedstate 

Agora, adicione o boot persist, e não deixe e adicionar ele ao quasar.config.js > boots, é vital que ele seja adicionado antes do boot do axios e do i18n.

quasar new boot persist 

QPANC.App/quasar.config.js

module.exports = function (ctx) { return { boots: [ 'persist', 'i18n', 'axios' ] } } 

Antes de codificamos o boot persist, precisamos criar um pequeno serviço, que será responsável por detectar o idioma recomendado para o usuário.

QPANC.App/src/services/locales.js

const locales = ['en-us', 'pt-br'] const regions = { en: 'en-us', pt: 'pt-br' } const fallback = regions.en const detectLocale = function () { if (process.env.CLIENT) { const locale = navigator.language.toLowerCase() if (locales.includes(locale)) { return locale } const region = locale.split('-')[0] if (region in regions) { return regions[region] } return regions.en } else { return fallback } } export { locales, regions, fallback, detectLocale } 

Agora, vamos ao boot:

QPANC.App/src/boot/persist.js

import { Cookies, Quasar } from 'quasar' import createPersistedState from 'vuex-persistedstate' const persistState = function ({ name, store, storage }) { createPersistedState({ key: name, paths: [name], filter ({ type }) { return type.startsWith(name) }, storage })(store) } export default function ({ store, ssrContext }) { const cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies const cookieStorage = { getItem (key) { return JSON.stringify(cookies.get(key)) }, setItem (key, value) { cookies.set(key, value, { path: '/' }) }, removeItem (key) { cookies.remove(key, { path: '/' }) } } persistState({ name: 'app', store, storage: cookieStorage }) if (process.env.CLIENT) { // persistState({ name: 'local', store, storage: window.localStorage }) store.commit('app/localeOs', detectLocale()) } } 

Um pequeno detalhamento sobre o que está sendo feito:

persistState({ name: 'app', store, storage: cookieStorage }) 

Estamos instruindo o vuex-persistedstate à persistir todo o modulo app em um Cookie

if (process.env.CLIENT) { // persistState({ name: 'local', store, storage: window.localStorage }) } 

Caso o comentário seja removido, estamos instruindo o vuex-persistedstate à persistir todo o modulo local no localStorage

if (process.env.CLIENT) { store.commit('app/localeOs', detectLocale()) } 

Estamos atualizando o localeOs para que ele seja igual ao locale disponível mais próximo ao que é utilizado pelo browser, este vai ser o idioma da aplicação, caso o usuário também não especifique o localeUser.

Porém vale lembrar, que o código no cliente é executado após a execução no servidor, então, na primeira requisição, o servidor irá sempre utilizar a linguagem padrão, no caso, o inglês (isto é configurável em quasar.config.js > framework > lang).

Desta forma, na primeira requisição, o app será carregado usando a linguagem padrão, e irá alternar para a linguagem informada pelo browser após a conclusão do carregamento da pagina.

Agora, vamos criar o nosso modulo app

QPANC.App/src/store/app.js

import { factory } from '@toby.mosque/utils' class AppStoreModel { constructor ({ token = '', localeOs = '', localeUser = '' } = {}) { this.token = token this.localeOs = localeOs this.localeUser = localeUser } } const options = { model: AppStoreModel } export default factory.store({ options, getters: { locale (state) { return state.localeUser || state.localeOs } } }) export { options, AppStoreModel } 

Como se trata de um modulo global, não esqueça de registra-lo no QPANC.App/src/store/index.js

QPANC.App/src/store/index.js

import Vue from 'vue' import Vuex from 'vuex' import app from './app' Vue.use(Vuex) export default function (context) { const Store = new Vuex.Store({ modules: { app }, strict: process.env.DEV }) return Store } 

Top comments (0)