Neste ponto, temos um componente decente. No entanto, se quisermos compartilhá-lo com outros (especialmente não desenvolvedores), sua utilidade é limitada. Ele só oferecerá uma escolha entre maçãs e peras. Embora isso tenha sido perfeito para nosso primeiro cliente (provavelmente uma fábrica de bolos de frutas), a empresa automotiva em que estamos trabalhando em seguida provavelmente não usará Realidade Aumentada para ajudar os trabalhadores a escolher frutas na sala de descanso. É hora de tornar nossos componentes mais reutilizáveis e configuráveis.

Você já sabe como configurar componentes. Ao selecionar um componente no fluxograma do fluxo de trabalho, o painel de configuração do componente aparecerá no lado direito do fluxograma do fluxo de trabalho com as opções de configuração.

Tornar os componentes configuráveis consiste em duas partes, definindo o que o usuário pode configurar e processando os valores selecionados.

Definindo as opções de configuração

As opções de configuração que você deseja oferecer são definidas no formato JSON. Os usuários podem escolher entre uma sériede tipos de entrada, como entrada de texto simples, entrada de caixa de seleção e entrada de upload de arquivo.

Aqui está um exemplo de configuração.

{
  "tab1": {
        "title_text": {
            "title": "Title",  
            "inputType": "textinput",
            "value": "Please select"
        }
  },
  "tab2": {
        "options": {
            "title": "Options",
            "inputType": "map-input",
            "placeholder": {
                "key": "Option Key",
                "value":  "Título da opção"
            },
            "value": [
                {
                    "key": "option1",
                    "value": "Option 1"
                },
                {
                    "key": "option2",
                    "value": "Option 2"
                },
                {
                    "key": "option3",
                    "value": "Option 3"
                }
            ]
        },
         "use_all": {
             "title": "Offer last option",
             "inputType": "checkbox-input",
             "value": "true"
         }
  }
}

Os objetos no nível raiz do arquivo JSON são as guias do painel de configuração (sua chave será mostrada como o título da guia). Cada objeto de campo de entrada tem pelo menos três atributos:

  • a title que é o título mostrado acima do campo de entrada no painel de configuração
  • um inputType que é o tipo de campo de entrada que você deseja que seja mostrado
  • a value: que é o que o usuário inseriu no campo de entrada (ou um valor padrão)

Você também pode adicionar um info atributo, que será renderizado como uma dica de ferramenta e pode ser usado para uma explicação mais detalhada da finalidade do campo de entrada.

Além disso, muitos atributos específicos são possíveis dependendo do tipo de entrada usado.

Existem técnicas avançadas disponíveis, que permitem melhorar a interface do usuário do painel de configuração. Por exemplo, é possível agrupar vários campos de entrada em contêineres repetíveis e recolhíveis ou mostrar determinados campos de entrada apenas condicionalmente. Eles serão mostrados nos exemplos a seguir.

Processando valores selecionados

Depois de definir a configuração, é hora de incorporar os valores configurados em seu componente. Você pode acessar os valores do arquivo de configuração usando §{ ... }§o . Dentro dos colchetes, você pode usar a notação de ponto para acessar o objeto de configuração. Esses espaços reservados são substituídos pelos valores configurados em uma etapa de pré-compilação.

Por exemplo, você pode criar um mapeamento para incluir o valor "Título" que definimos no exemplo acima em seu layout de etapa da seguinte maneira:

<mapping>
    <ui_element name="Topic">
        <param name="content">§{ tab1.title_text.value }§</param>
    </ui_element>
</mapping>

Dicas & truques: Um erro típico ao acessar a configuração é esquecer o fechamento ".value".

Você também pode usar funções auxiliares para implementar sua configuração. Essas funções ajudam você a fazer muitas coisas, como:

  • implementar regras que só são adicionadas ao fluxo de trabalho condicionalmente com base na configuração
  • Faça um loop pelos valores configurados e crie regras/ações/... para cada valor
  • manipular os valores configurados para processamento adicional

Vejamos também um exemplo de uso do auxiliar:

§{#each tab2.options.value}§
    §{#unless @last}§
    <rule id="opt_§{key}§">
        <expressão><![ CDATA[ #{event:command} == '§{value}§' ]]></expression>
        <actions>
            <action ref="set_command"/>
        </actions>
    </rule>
    §{/unless}§

    §{#if @last}§
    §{#if (e tab2.use_all.value (compare (collection tab2.options.value "size") ">" 1))}§
        <rule id="special_opt_§{key}§">
            <expression><![ CDATA[ #{event:command} == '§{value}§' ]]></expression>
            <actions>
                <action ref="special_action"/>
            </actions><
        /rule>    
    §{/if}§§{/if}§
    
§{/each}§

Há alguns pontos importantes a serem observados aqui:

  • A # frente das funções auxiliares é necessária e sinaliza que ela enquadra e se refere a um bloco de código.
  • Você pode aninhar funções auxiliares e elas são avaliadas de dentro para fora. Neste exemplo, o collection auxiliar retorna um tamanho de 3, que é então d para 1, compareresultando em true. Depois de aplicar um "logical and" neste resultado e o valor da checkbox-input, obtemos um valor booleano final para nossa função if-helper.
  • Existem várias variáveis definidas automaticamente dentro de alguns auxiliares de bloco que ajudam você a gerenciar sua posição em matrizes/mapas de dados. Estes são @first, @last@index e @key.

Após a pré-compilação com a configuração de exemplo acima, isso resultaria em:

    <rule id="opt_option1">
        <expressão><![ CDATA[ #{event:command} == 'Opção 1' ]]></expression>
        <actions>
            <action ref="set_command"/>
        </actions>
    </rule>

    <rule id="opt_option2"><
        expression><![ CDATA[ #{event:command} == 'Opção 2' ]]></expression>
        <actions>
            <action ref="set_command"/>
        </actions>
    </rule>

    <rule id="special_opt_option3"><
        expression><![ CDATA[ #{event:command} == 'Opção 3' ]]></expression>
        <actions>
            <action ref="special_action"/>
        </actions>
    </rule>  

Exemplos

Os dois exemplos a seguir mostram maneiras avançadas de tornar a configuração do componente mais fácil de entender.

Agrupamento de campos de entrada

Você pode agrupar vários campos de entrada usando o inputType "container". Isso permite alguma clareza visual e também permite funcionalidades como a duplicação de um grupo de elementos.

Aqui estão alguns atributos específicos e suas descrições.

  • grupo de contêineres: diferencia entre diferentes tipos de grupos. Isso pode ser usado para passar por cima de contêineres na marcação do fluxo de trabalho.
  • repetível: permite que o usuário crie cópias de um grupo. Estes podem ser alterados separadamente, o que permite a implementação de elementos repetíveis.
  • dobrável: Permite que o grupo seja minimizado, deixando apenas o título a ser mostrado.
  • excluído: remove um contêiner da configuração. Ele é definido automaticamente para contêineres copiados e não deve ser usado para contêineres base.
  • editável: permite que o usuário altere o título do contêiner.
"base_sensor": {
            "title": "Sensor 1",
            "inputType": "container",
            "containerGroup": "sensors",
            "repeatable": true,
            "collapsible": true,
            "deleteable": false,
            "editable": true,
            "value": {
                "sensor_shown": {
                    "title": "Value Shown",
                    "inputType":  "checkbox-input",
                    "value": false,
                    "showIfComputed": true
                },
                "sensor_type": {
                    "inputType": "file-upload",
                    "title": "Icon",
                    "accept": "image/png",
                    "multiple": false,
                    "value": "",
                    "showIfComputed": true
                },
                "sensor_unit": {
                    "title": " Unit",
                    "inputType": "textinput",
                    "value": "rpm",
                    "showIfComputed": true
                },
                "sensor_json_path": {
                    "title": "JSON Path ",
                    "inputType": "textinput",
                    "value": "rpm",
                    "showIfComputed": true
                }
            },
            "showIfComputed": true,
            "container_editing": false,
            "container_opened": verdadeiro
        }

Exibição condicional de campos de entrada

É possível usar o atributo "showif" para definir uma condição sob a qual um campo de entrada deve ser mostrado ou oculto. Suponha, por exemplo, que seu componente tenha um recurso opcional que possa ser configurado em detalhes. Se o recurso não for usado, você não gostaria de mostrar os parâmetros de configuração detalhados.

Vejamos um exemplo:

{
  "Camera":{
    "use_camera":{
      "title": "Use Device Camera",
      "inputType": "checkbox-input",
      "value": "false"
    },
    "zoom_level":{
      "title": "Zoom Level",
      "inputType": "dropdown-input",
      "showIf": "root. Camera.use_camera.value",
      "value": { "name": 1 },
      "elements": [
        {
          "name": 1
        },
        {
          "name": 2
        },
        {
          "name": 3
        }
      ]
    },
    "show_zoom_level": {
      "title": "Show Zoom Level",
        "inputType": "checkbox-input",
        "showIf": "raiz. Camera.use_camera.value && raiz. Camera.zoom_level.value.name > 1",
        "value": "false"
    },
    "timeout":{
      "title": "Camera Timeout (ms)",
      "showIf": "root. Camera.use_camera.value",
      "inputType": "textinput",
      "value": 5000
    }
  }
}

Abaixo você pode ver a saída esperada.

Como a caixa de seleção está definida como false, todos os outros campos de entrada não são mostrados. Se fosse verdade, todas, exceto a caixa de seleção "Mostrar Nível de Zoom" apareceriam, já que essa caixa de seleção só aparece se o Nível de Zoom for maior que um:

📌Designação

  • Adicione campos de configuração para o título, bem como a imagem e o texto nos dois botões.
  • Mostrar as imagens somente se existir uma imagem para ambos os botões. Se apenas um botão tiver uma imagem configurada, mostre apenas o texto.

 Componente de Download (Pré-Atribuição)

Ajuda & Recursos

Acessando a configuração em arquivos de layout:

Dentro dos atributos das tags de layout, você pode acessar a configuração como sempre:

<Nome do botão="§{ ... } §" .../>

Para funções auxiliares de bloqueio, no entanto, você terá que usar um <Script> como este:

<Script>§{#if ...} §</Script>
<Botão .../>
<Script>§{/if}§</Script>

Também é importante notar que você não poderá usar a tag dentro de <Script> todas as outras tags. Abaixo está um exemplo inválido :

<Botão>
<Script>§{#...} §</Roteiro>
...
<Script>§{/...} §</Script>
</Botão>

Finalmente, se suas condicionais resultarem em um erro Duplicate unique valuede sintaxe, porque um nome de elemento existe duas vezes, mas você tem certeza de que apenas um deles existirá de cada vez após a pré-compilação, você pode ignorar o erro de sintaxe.

Solução

Veja abaixo uma solução exemplar para o layout:

<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical">
  <Content PlaceHolder="Content" Weight="1" Orientation="Horizontal">
    <Script>§{#if (e (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§</Script>
    <Nome do botão="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle"><
      Nome da imagem="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
      <Nome do texto="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
      <Events/>
    </Button>
    <Script>§{else}§</Script>
    <Nome do botão="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
      <Nome do texto="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
      <Events/>
    </Button>
    <Script>§{/if}§</Script>

    <Script>§{#if (e (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" "") )}§</Script>
    <Nome do botão="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
      <Nome da imagem="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
      <Nome do texto="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
      <Events/>
    </Button>
    <Script>§{else}§</Script>
    <Nome do botão="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
      <Nome do texto="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
      <Events/>
    </Button>
    <Script>§{/if}§</Script>

  </Content>
</LayoutModel>

 Componente de Download (Pós-Atribuição)

Acessar a configuração de dentro de arquivos de layout como este pode ser confuso. Se você se deparar com uma situação semelhante novamente, pode valer a pena dar uma olhada em nosso elemento de interface do usuário widget curinga. Esse elemento pode ser editado dinamicamente durante o tempo de execução.

Uma solução com o widget curinga ficaria assim:

Primeiro, você criaria dois PartTemplates com as variantes de dois botões.

<Nome do PartTemplate="OptionButtonsWithImage" Orientação="Horizontal">
  <Nome do botão="§{configuration.rightImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
    <Nome da imagem="RIGHT_IMAGE" Peso="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
    <Nome do texto="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
    <Events/>
  </Button>
  <Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
    <Nome da imagem="LEFT_IMAGE" Peso="0,8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
    <Nome do texto="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
    <Events/>
  </Button>
</PartTemplate>
<PartTemplate Name="OptionButtonsText" Orientation="Horizontal">
  <Nome do botão="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
    <Nome do texto="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
    < Eventos/>
  </Button>
  <Nome do botão="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
    <Nome do texto="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
    <Events/>
  </Button>
</ PartTemplate>

Seu LayoutModel apenas inclui o "WildcardWidget".

<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical">
  <Content PlaceHolder="Content" Weight="1" Orientation="Horizontal">
    <WildcardWidget Name="Options" PartTemplateName="OptionButtonWithImage" Weight="1"/>
  </Content>
</LayoutModel>

Finalmente, no fluxo de trabalho, você define o PartTemplate que deseja usar com base na configuração:

<mapping>
    <ui_element name="Options">
        <param name="parttemplatename">§{#if (e (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§OptionButtonsWithImage§{else}§OptionButtonsText§{/if}§</param>
    </ui_element>
</mapping>

Dessa forma, você evitaria quaisquer erros de sintaxe e casos especiais ao acessar a configuração de dentro de arquivos de layout.

 Componente de download com widget curinga (pós-atribuição)