DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on

2

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é.

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay