QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.
- Source
- Introdução
- Parte I - ASP.NET - Inicializando os Projetos
- Parte 2 - PostgreSQL
- Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente
- Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity
- Parte 5 - ASP.NET - Documentação Interativa com Swagger
- Parte 6 - ASP.NET - Regionalização
- Parte 7 - ASP.NET - Autenticação e Autorização
- Parte 8 - ASP.NET - CORS
- Parte 9 - Quasar - Criação e Configuração do Projeto
- Parte 10 - Quasar - Configurações e Customizações
- Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR
- Parte 12 - Quasar - Serviços
- Parte 13 - Quasar - Regionalização e Stores
- Parte 14 - Quasar - Consumindo a API
- Parte 15 - Quasar - Login
- Parte 16 - Quasar - Áreas Protegidas
- Parte 17 - Quasar - Registro
- Parte 18 - Docker - Maquina Virtual Linux
- Parte 19 - Docker - Registro e Build
- Parte 20 - Docker - Traefik e Publicação
- Demo Online
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"
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.:
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)