DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on

QPANC - Parte 10 - Quasar - Configurações e Customizações

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

18 Customização de Temas e Modos (Dark e Light)

A customização do tema e do modo dark é feita de forma semelhante, porém a classe do tema deve ser definida pelo desenvolvedor, enquanto a classe do modo dark é injetada pelo framework, porém o recomendado é que ambas sejam inseridas no body.

Para ativar o modo dark, você deve alterar o quasar.config.js > framework > config > dark
Quasar.App/quasar.config.js

module.exports = function (ctx) {
  return {
    framework: {
      config: {
        dark: 'auto'
      }
    }
  }
}

abaixo segue um exemplo de customização para o modo dark.:
Quasar.App/src/css/app.sass

// app global css in Sass form
body.body--dark
  color: $grey-1
  --q-color-primary: #26a69a
  --q-color-secondary: #027BE3
  --q-color-accent: #9C27B0
  --q-color-positive: #21BA45
  --q-color-negative: #C10015
  --q-color-info: #31CCEC
  --q-color-warning: #F2C037
  .text-main
    color: $grey-10
  .text-content
    color: $grey-9
  .text-content-2
    color: $grey-8
  .bg-main
    background: $grey-10
  .bg-content
    background: $grey-9
  .bg-content-2
    background: $grey-8
body.body--light
  color: $grey-10
  --q-color-primary: #027BE3
  --q-color-secondary: #26A69A
  --q-color-accent: #9C27B0
  --q-color-positive: #21BA45
  --q-color-negative: #C10015
  --q-color-info: #31CCEC
  --q-color-warning: #F2C037
  .text-main
    color: white
  .text-content
    color: $grey-2
  .text-content-2
    color: $grey-4
  .bg-main
    background: white
  .bg-content
    background: $grey-2
  .bg-content-2
    background: $grey-4

No caso acima, criamos as cores personalizadas main, content e content-2 para os modos dark e light, assim como definimos as cores base (primary, secondary, accent, positive, negative, info, warning)

Note que as cores definidas para o modo dark, não foram bem selecionas, por tanto pode haver um problema de contraste no modo dark

caso haja a necessidade de implementar múltiplos temas ou esquemas de cores, você poderá definir classes base com a seguinte estrutura: body.${nome_tema}.body--dark e body.${nome_tema}.body--light, e um nível abaixo, definir as customizações.

19 Internacionalização - I18n

A internacionalização no Quasar é bem simples, basicamente são arquivos JSON, então cabe a você desenvolvedor definir como estes textos serão organizados.

Mas dado a estrutura deste projeto, não será possível a flexibilização quanto a estrutura dos textos usados nas validações, ou seja, os erros e os nomes dos rótulos dos campos, aqui chamados de fields.

Então, iremos criar dois arquivos dentro da pasta Quasar.App/src/i18n.
Quasar.App/src/i18n/en-us/index.js

export default {
  validations: {
    compare: '{field} and {other} do not match',
    email: 'The {field} field is not a valid e-mail address',
    required: 'The {field} field is required'
  },
  fields: {
    confirmPassword: 'Confirm your Password',
    confirmUserName: 'Confirm your Email',
    firstName: 'First Name',
    lastName: 'Last Name',
    password: 'Password',
    userName: 'Email'
  }
}

Quasar.App/src/i18n/pt-br/index.js

export default {
  validations: {
    compare: '{field} e {other} não são iguais',
    email: 'O campo {field} não possui um email válido',
    required: 'O campo {field} é requerido'
  },
  fields: {
    confirmPassword: 'Confirme à Senha',
    confirmUserName: 'Confirme o Email',
    firstName: 'Nome',
    lastName: 'Sobrenome',
    password: 'Senha',
    userName: 'Email'
  }
}

Apenas para ficar claro, os nomes entre {} são placeholders, por exemplo, {field} em The {field} field is required será substituído pelo nome do campo que está sendo validado.

Agora precisamos registrar estes textos localizados.

Quasar.App/src/i18n/index.js

import enUS from './en-us'
import ptBR from './pt-br'

export default {
  'en-us': enUS,
  'pt-br': ptBR
}

20 Validações

Iremos utilizar o sistema de validações que o Quasar oferece, porém iremos encapsular isto dentro de um serviço, para que posamos aplicar os textos localizados.

então, crie o seguinte serviço:

Quasar.App/src/services/validations.js

const validations = {}
validations.required = ({ self, field }) => {
  return function required (val) {
    return !!val || self.$t('validations.required', {
      field: self.$t(`fields.${field}`)
    })
  }
}

const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
validations.email = ({ self, field }) => {
  return function required (val) {
    return emailRegex.test(val) || self.$t('validations.email', {
      field: self.$t(`fields.${field}`)
    })
  }
}

validations.server = ({ self, field }) => {
  return function server (val) {
    return self.validationArgs[field].server
  }
}

export default function validation (self, context) {
  const _validations = {}
  for (const field in context) {
    const _rules = []
    const rules = context[field]
    const names = Object.keys(validations)
    for (const rule of names) {
      if (rules.includes(rule)) {
        _rules.push(validations[rule]({ self, field }))
      }
    }
    _validations[field] = _rules
  }
  _validations.resetServer = function reset () {
    for (const field in context) {
      if (field in self.validationArgs && 'server' in self.validationArgs[field]) {
        self.validationArgs[field].server = true
      }
    }
  }
  return _validations
}

no serviço acima, as linas que merecem atenção são:

function required (val) {
  return !!val || self.$t('validations.required', {
    field: self.$t(`fields.${field}`)
  })
}
function email (val) {
  return emailRegex.test(val) || self.$t('validations.email', {
    field: self.$t(`fields.${field}`)
  })
}

Em ambos os casos, temos uma função que retorna um boolean ou uma string, caso retorne true, é por que passou no teste, se retornar uma string, é por que o teste falhou, e a string é a mensagem de erro. o self.$t serve para recuperamos esta mensagem já localizada.

return function server (val) {
  return self.validationArgs[field].server
}

Já esta validação dependente de um dado proveniente do componente, neste caso, o retorno da API.

Desta forma, se quisemos validar o userName e o password, podemos montar as rules da seguinte forma.:

import validations from 'services/validations'

export default {
  data () {
    const self = this
    const validation = validations(self, {
      userName: ['required', 'email', 'server'],
      password: ['required', 'server']
    })
    return {
      userName: '',
      password: '',
      validation,
      validationArgs: {
        userName: {
          server: true
        },
        password: {
          server: true
        }
      }
    }
  }
}
<q-nput label="$t('fields.userName')" v-model="userName" :rules="validation.userName" @blur="validationArgs.userName.server = true" />
<q-nput label="$t('fields.password')" type="password" v-model="password" :rules="validation.password" @blur="validationArgs.password.server = true" />

Através do objeto validationArgs, conseguimos passar alguma informação complementar o serviço que constrói as validações, por exemplo, validationArgs.userName.server será utilizado pela validação server.

Lembrando que userName e password devem está presentes no arquivo de localização (i18n), assim como as regras required, email e server.

Assim como as regras utilizadas (no caso acima, required, email e server) devem está devidamente implementadas no serviço validations.

Nos próximos capítulos, nós veremos estas validações em ação.

E para fechamos estes assunto, precisamos criar um alias para a pasta services, o primeiro passo é adicionar o seguinte bloco de código no quasar.config.js > build > extendWebpack

QPANC.App/quasar.config.js

const path = require('path')

module.exports = function (ctx) {
  return {
    build: {
      extendWebpack (cfg, { isServer, isClient }) {
        cfg.resolve.alias = {
          ...cfg.resolve.alias,
          services: path.resolve(__dirname, './src/services'),
        }
      }
    }
  }
}

e para permitir que o intellisense funcione no VSCode, precisamos adicionar o seguinte item jsconfig.json > compilerOptions > paths

QPANC.App/jsconfig.json

{
  "compilerOptions": {
    "paths": {
      "services/*": [
        "src/services/*"
      ]
    }
  }
}

20 Validações

Iremos utilizar o sistema de validações que o Quasar oferece, porém iremos encapsular isto dentro de um serviço, para que posamos aplicar os textos localizados.

então, crie o seguinte serviço:

Quasar.App/src/services/validations.js

const validations = {}
validations.required = ({ self, field }) => {
  return function required (val) {
    return !!val || self.$t('validations.required', {
      field: self.$t(`fields.${field}`)
    })
  }
}

const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
validations.email = ({ self, field }) => {
  return function required (val) {
    return emailRegex.test(val) || self.$t('validations.email', {
      field: self.$t(`fields.${field}`)
    })
  }
}

validations.server = ({ self, field }) => {
  return function server (val) {
    return self.validationArgs[field].server
  }
}

export default function validation (self, context) {
  const _validations = {}
  for (const field in context) {
    const _rules = []
    const rules = context[field]
    const names = Object.keys(validations)
    for (const rule of names) {
      if (rules.includes(rule)) {
        _rules.push(validations[rule]({ self, field }))
      }
    }
    _validations[field] = _rules
  }
  _validations.resetServer = function reset () {
    for (const field in context) {
      if (field in self.validationArgs && 'server' in self.validationArgs[field]) {
        self.validationArgs[field].server = true
      }
    }
  }
  return _validations
}

no serviço acima, as linhas que merecem atenção são:

function required (val) {
  return !!val || self.$t('validations.required', {
    field: self.$t(`fields.${field}`)
  })
}
function email (val) {
  return emailRegex.test(val) || self.$t('validations.email', {
    field: self.$t(`fields.${field}`)
  })
}

Em ambos os casos, temos uma função que retorna um boolean ou uma string, caso retorne true, é por que passou no teste, se retornar uma string, é por que o teste falhou, e a string é a mensagem de erro. o self.$t serve para recuperamos esta mensagem já localizada.

return function server (val) {
  return self.validationArgs[field].server
}

Já esta validação dependente de um dado proveniente do componente, neste caso, o retorno da API.

Desta forma, se quisemos validar o userName e o password, podemos montar as rules da seguinte forma.:

import validations from 'services/validations'

export default {
  data () {
    const self = this
    const validation = validations(self, {
      userName: ['required', 'email', 'server'],
      password: ['required', 'server']
    })
    return {
      userName: '',
      password: '',
      validation,
      validationArgs: {
        userName: {
          server: true
        },
        password: {
          server: true
        }
      }
    }
  }
}
<q-nput label="$t('fields.userName')" v-model="userName" :rules="validation.userName" @blur="validationArgs.userName.server = true" />
<q-nput label="$t('fields.password')" type="password" v-model="password" :rules="validation.password" @blur="validationArgs.password.server = true" />

Através do objeto validationArgs, conseguimos passar alguma informação complementar ao serviço que constrói as validações, por exemplo, validationArgs.userName.server será utilizado pela validação server.

Lembrando que userName e password devem está presentes no arquivo de localização (i18n), assim como as regras required, email e server.

Assim como as regras utilizadas (no caso acima, required, email e server) devem está devidamente implementadas no serviço validations.

Nos próximos capítulos, nós veremos mais sobre estas validações.

E para fechamos estes assunto, precisamos criar um alias para a pasta services, o primeiro passo é adicionar o seguinte bloco de código no quasar.config.js > build > extendWebpack

QPANC.App/quasar.config.js

const path = require('path')

module.exports = function (ctx) {
  return {
    build: {
      extendWebpack (cfg, { isServer, isClient }) {
        cfg.resolve.alias = {
          ...cfg.resolve.alias,
          services: path.resolve(__dirname, './src/services'),
        }
      }
    }
  }
}

e para permitir que o intellisense funcione no VSCode, precisamos adicionar o seguinte item jsconfig.json > compilerOptions > paths

QPANC.App/jsconfig.json

{
  "compilerOptions": {
    "paths": {
      "services/*": [
        "src/services/*"
      ]
    }
  }
}

21 Instalando extensões

Como a aplicação está rodando em uma VM, e ao instalar uma extensão será adicionado um pacote pelo npm ou yarn. você terá que adotar uma das seguintes rotinas.:

21.1 - quasar ext add extensao_id

Você poderá adicionar a extensão usando o quasar ext add extensao_id, porém será preciso excluir a pasta node_modules e os arquivos de lock (yarn.lock e/ou package-lock.json). exemplo:

quasar ext invoke qautomate

21.2 - quasar ext invoke extensao_id

Para usar o invoke, é preciso instalar o pacote utilizado pela extensão antes, por exemplo, para a extensão qautomate:

QPANC.App/package.json

{
  "devDependencies": {
    "quasar-app-extension-qautomate": "^1.0.0-alpha.5"
  }
}

então execute a aplicação para que o pacote seja instalado a partir da VM gerenciada pelo Docker.

Após parar o container, podemos usar o invoke:

quasar ext invoke qautomate

21.3 - Considerações

Eu sempre opto por fazer o add, então apago o node_modules e o yarn.lock, por mais que seja mais demorado, é mais pratico, menos propenso a erros e me dá uma desculpa para ir tomar um café.

Discussion (0)