Neste ponto, temos um componente muito bom. No entanto, se quisermos passá-lo para outras pessoas (especialmente não desenvolvedores), sua utilidade será muito limitada. Ele oferecerá apenas uma opção entre maçãs e peras. Embora isso tenha sido perfeito para o nosso primeiro cliente (provavelmente uma fábrica de bolos de frutas), a empresa automotiva na qual estamos trabalhando provavelmente não usará a realidade aumentada para ajudar os funcionários 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 clicar em um componente, será exibida uma barra lateral com as opções de configuração. Tornar os componentes configuráveis consiste em duas partes: definir o que o usuário pode configurar e processar os valores selecionados.
Definição das 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 variedadede 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": "Option Title"
},
"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:
- um
título
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 exibir - um
valor
: que é o que o usuário inseriu no campo de entrada (ou um valor padrão)
Você também pode adicionar um atributo info
, 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, há muitos atributos específicos, dependendo do tipo de entrada usado.
Há técnicas avançadas disponíveis que permitem aprimorar 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. Isso será mostrado nos exemplos a seguir.
Processamento de 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 §{ ... }§.
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 "Title" (Título) que definimos no exemplo acima em seu layout de etapa da seguinte forma:
<mapping>
<ui_element name="Topic">
<param name="content">§{ tab1.title_text.value }§</param>
</ui_element>
</mapping>
Dicas e truques: Um erro comum 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
- percorrer os valores configurados e criar regras/ações/... para cada valor
- manipular os valores configurados para processamento posterior
Vamos dar uma olhada em um exemplo de uso de auxiliar também:
§{#each tab2.options.value}§
§{#unless @last}§
<rule id="opt_§{key}§">
<expression><![CDATA[ #{event:command} == '§{value}§' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
§{/unless}§
§{#if @last}§
§{#if (and 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:
- O
#
na frente das funções auxiliares é obrigatório e sinaliza que ele 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 auxiliar de
coleção
retorna um tamanho de 3, que é então comparado
a 1, resultando em true. Depois de aplicar um "and lógico" a esse resultado e ao valor da caixa de seleção, obtemos um valor booleano final para nossa função auxiliar if. - Há diversas variáveis definidas automaticamente dentro de alguns auxiliares de bloco que o ajudam a gerenciar sua posição em matrizes/mapas de dados. Essas variáveis 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">
<expression><![CDATA[ #{event:command} == 'Option 1' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
<rule id="opt_option2">
<expression><![CDATA[ #{event:command} == 'Option 2' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
<rule id="special_opt_option3">
<expression><![CDATA[ #{event:command} == 'Option 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 possibilita funcionalidades como a duplicação de um grupo de elementos.
Aqui estão alguns atributos específicos e suas descrições.
containerGroup: Diferencia os diferentes tipos de grupos. Isso pode ser usado para analisar os contêineres na marcação do fluxo de trabalho.
repetível: Permite que o usuário crie cópias de um grupo. Elas podem ser alteradas separadamente, o que permite a implementação de elementos repetíveis.
recolhível: Isso permite que o grupo seja minimizado, deixando apenas o título à mostra.
deletable: Remove um contêiner da configuração. É definido automaticamente para contêineres copiados e não deve ser usado para contêineres de 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": true
}
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 ocultado. Suponha, por exemplo, que seu componente tenha um recurso opcional que pode ser configurado em detalhes. Se o recurso não for usado, você não desejará mostrar os parâmetros de configuração detalhados.
Vamos dar uma olhada em 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": "root.Camera.use_camera.value && root.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 o resultado esperado.
Como a caixa de seleção está definida como falsa, todos os outros campos de entrada não são exibidos. Se fosse verdadeira, todas as caixas de seleção, exceto a "Show Zoom Level", seriam exibidas, pois essa caixa de seleção só aparece se o nível de zoom for maior que um:
Atribuição
Atribuição 1:
- Adicione campos de configuração para o título, bem como a imagem e o texto nos dois botões.
- Mostre as imagens somente se houver 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 e recursos
Acesso à configuração em arquivos de layout:
Nos atributos das tags de layout, você pode acessar a configuração como sempre:
<Button Name="§{ ... }§" .../>
No entanto, para funções auxiliares de bloco, você terá que usar um <Script>
como este:
<Script>§{#if ...}§</Script>
<Button .../>
<Script>§{/if}§</Script>
Também é importante observar que você não poderá usar a tag <Script>
dentro de todas as outras tags. Abaixo está um exemplo inválido:
<Button>
<Script>§{#...}§</Script>
...
<Script>§{/...}§</Script>
</Button>
Por fim, se suas condicionais resultarem em um erro de sintaxe Duplicate unique value (Valor exclusivo duplicado
), porque um nome de elemento existe duas vezes, mas você tem certeza de que apenas um deles existirá por vez após a pré-compilação, você pode ignorar o erro de sintaxe.
Solução
Abaixo está uma solução exemplar para o layout:
<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical">
<Content PlaceHolder="Content" Weight="1" Orientation="Horizontal">
<Script>§{#if (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§</Script>
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Image Name="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{else}§</Script>
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{/if}§</Script>
<Script>§{#if (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§</Script>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Image Name="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{else}§</Script>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Text Name="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 a partir de arquivos de layout como esse pode ser complicado. Se você se deparar com uma situação semelhante novamente, talvez valha a pena dar uma olhada em nosso elemento de UI de widget curinga. Esse elemento pode ser editado dinamicamente durante o tempo de execução.
Uma solução com o widget curinga seria semelhante a esta:
Primeiro, você criaria dois PartTemplates
com as variantes de dois botões.
<PartTemplate Name="OptionButtonsWithImage" Orientation="Horizontal">
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Image Name="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="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">
<Image Name="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
</PartTemplate>
<PartTemplate Name="OptionButtonsText" Orientation="Horizontal">
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
</PartTemplate>
Seu LayoutModel
inclui apenas 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>
Por fim, 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 (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§OptionButtonsWithImage§{else}§OptionButtonsText§{/if}§</param>
</ui_element>
</mapping>
Dessa forma, você evitaria erros de sintaxe e casos especiais ao acessar a configuração a partir dos arquivos de layout.
Faça o download da solução com o widget curinga (pós-atribuição).