Início
NexoPOS

Adicionar botão Criar no campo de seleção de pesquisa

Um “search-select” é um componente que ajuda a filtrar uma lista de opções e facilita a seleção. Por padrão, o componente não permite criar novas entradas, que são adicionadas posteriormente à lista de opções. Com algumas configurações, você pode adicionar suporte para criar entradas via um pop-up e, após o envio bem-sucedido, fazer com que elas sejam adicionadas às opções.

Este recurso funciona “out of the box” com os componentes internos de CRUD e de configurações, sem nenhuma modificação adicional no código. Isso significa que, se você tiver, por exemplo, um componente de CRUD que permite selecionar produtos, basta definir o suporte no campo de search-select; é só isso. No entanto, se você quiser ter um controle mais profundo ou entender melhor como as coisas são feitas, talvez você queira continuar a leitura deste conteúdo.

Princípio de Funcionamento

Um ponto importante a observar é que suas implementações devem carregar os campos de forma assíncrona. Isso garante que os campos possam ser recarregados a qualquer momento sem recarregar a página (é assim que os campos do componente CRUD são carregados no NexoPOS).

Agora, ao definir as estruturas do seu campo, você vai defini-las no lado do backend. A partir daí, você vai definir qual componente ele deve carregar quando o botão “+” for clicado e as configurações que serão passadas como propriedades (é um componente Vue 3).

Configuração do Campo

Todos os componentes CRUD fornecem sua configuração como um array usando o método getFormConfig. Quando criamos nosso campo, devemos fornecer duas entradas extras: “component” e “props”. O componente pode ser qualquer componente que você quiser.

Se você quiser usar seu componente, deverá registrá-lo primeiro. Além disso, esse componente resolve a resposta de um servidor (o que é útil para selecionar a entrada que foi criada automaticamente). Porém, no nosso exemplo, usaremos "nsCrudForm", um componente construído sobre o NexoPOS para renderizar um formulário CRUD.

Veja como você definirá, então, a configuração do seu campo de seleção de pesquisa.

<?php
use App\Crud\ProductCategoryCrud;

$fields = [
    [
        'type' => 'search-select',
        'name' => 'category_id',
        'label' => 'Assigned Category',
        'description' => 'any description you want goes here',
        //Here is how to enable support for a create button
        'component' => 'nsCrudForm',
        'props' => ProductCategoryCrud::getFormConfig()
    ]
];

Você notará que usamos a classe ProductCategoryCrud para recuperar sua configuração. A partir do NexoPOS 5, esse método é disponibilizado para todos os componentes CRUD disponíveis. A variável $fields deve então ser retornada como resposta em uma chamada de API.

Configurando o Componente Vue

Suponha que você não esteja usando nossos componentes nsSettings ou nsCrudForm. Nesse caso, você terá que fornecer uma ponte de comunicação entre seu componente Vue que renderiza o formulário, o campo search-select e o componente usado para criar uma entrada. Como você pode imaginar, nesse cenário temos 3 componentes Vue. Vamos começar com o componente que renderiza o formulário.

<template>
  <div>
    <form>
      <ns-field v-for="field in fields"/>
    </form>
  </div>
</template>

<script lang="ts">
declare const FormValidation;
export default {
  data() {
    return {
      fields: [],
      formValidation: new FormValidation,
    };
  },
  methods; {
    loadFields() {
      nsHttpClient('/api/path/to/the/fields')
        .subscribe({
          next: fields => {
            this.fields = this.formValidataion.createForm(fields);
          },
          error: error => {
            // handle error here
          });
    }
  }
  created() {
    this.loadFields();
  },
};
</script>

Conforme mencionado acima, estamos carregando os campos de forma assíncrona. Agora, para garantir que as opções sejam preenchidas, sempre que o componente que cria a entrada resolver a resposta do servidor, precisamos adicionar um callback ao elemento <ns-field> assim:

<template>
  <div>
    <form>
      <ns-field @saved="handleSaveEvent( $event, field )" v-for="field in fields"/>
    </form>
  </div>
</template>

<script lang="ts">
declare const FormValidation;
export default {
  data() {
    return {
      fields: [],
      formValidation: new FormValidation,
    };
  },
  methods: {
    async handleSaveEvent( serverResponse, field ) {
      try {
        field.options.push({
            label: serverResponse.data.entry[ field.props.optionAttributes.label ],
            value: serverResponse.data.entry[ field.props.optionAttributes.value ]
        });
        field.value     =   serverResponse.data.entry[ field.props.optionAttributes.value ];
      } catch ( exception ) {
        // something went wrong
      }
      
    },
    loadFields() {
      return new Promise( ( resolve, reject ) => {
        nsHttpClient('/api/path/to/the/fields')
          .subscribe({
            next: fields => {
              resolve( fields );
              this.fields = this.formValidataion.createForm(fields);
            },
            error: error => {
              reject( error )
            });
      });
    }
  }
  created() {
    this.loadFields();
  },
};
</script>

Estamos usando field.props para obter a configuração real do componente Crud que está sendo carregado. Nesse objeto, temos “optionsAttributes”, que definem qual propriedade deve ser considerada como rótulo ou qual deve ser usada como valor. Observe que essas duas propriedades são obrigatórias para serem usadas como uma opção. Por padrão, o NexoPOS assume que você está usando “id” como valor e “name” como rótulo. Você pode alterar esse comportamento editando “optionsAttributes” no componente crud.

Se você não estiver usando o nsCrudForm para criar uma entrada, mostraremos como criar seu componente personalizado que será exibido em um pop-up. Quando um componente é aberto como pop-up, o NexoPOS fornece como uma prop adicional a variável “popup”; ela pode então ser usada para detectar se ele está sendo exibido em um pop-up ou não. Veja como definiremos nosso componente:

<template>
  <div>
    <form>
      <ns-field v-for="field in fields"/>
      <button @click="createEntry()">Save</button>
    </form>
  </div>
</template>

<script lang="ts">
declare const FormValidation;
export default {
  props: [ 'popup' ],
  data() {
    return {
      fields: [],
      formValidation: new FormValidation,
    };
  },
  methods: {
    createEntry() {
      if ( this.formValidation.validateFields( this.fields ) ) {
          const form = this.formValidation.extractFields( this.fields );
          nsHttpClient.post( 'api/to/create/entry', form )
            .subscribe({
                next: response => {
                    this.popup.params.resolve( response );
                },
                error: error => {
                    this.popup.params.reject( error );
                }
            });
         return; // stop it here
      }
      
      // form is not valid
    },
    loadFields() {
      nsHttpClient('/api/path/to/the/fields')
        .subscribe({
          next: fields => {
            this.fields = this.formValidataion.createForm(fields);
          },
          error: error => {
            reject( error )
          });
    }
  }
  created() {
    this.loadFields();
  },
};
</script>

Usando a propriedade “popup”, temos acesso a vários parâmetros fornecidos no nosso componente. Dois desses parâmetros são “resolve” e “reject”. Garantimos que ambos retornem a resposta do servidor.