Milagres acontecem - qualquer programador lhe dirá isso. (em vez da epígrafe)Boa tarde!Um exemplo será simples.Há uma visão:<mvc:View
controllerName="MyController"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
<Button text="!" press="handlePress" />
<Table
items="{view>/list/items}"
mode="MultiSelect"
selectionChange="handleTableSelection">
<columns>
<Column>
<Text text="" />
</Column>
<Column>
<Text text="" />
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<ObjectIdentifier title="{view>id}" />
<Text text="{view>description}" />
</cells>
</ColumnListItem>
</items>
</Table>
</mvc:View>
e o mesmo controlador "enganado":sap.ui.controller("MyController", {
onInit: function () {
this.__oViewModel = new sap.ui.model.json.JSONModel({
list: {
items: [
{ id: 1, description: 'one' },
{ id: 2, description: 'two' },
{ id: 3, description: 'three' }
],
selected: []
}
});
this.getView().setModel(this.__oViewModel, "view");
},
handlePress: function (oEvent) {
sap.m.MessageToast.show(this.__oViewModel.getProperty('/list/selected').join(', '));
},
handleTableSelection: function (oEvent) {
const aSelectedCtx = oEvent.getSource().getSelectedContexts(),
aSelected = aSelectedCtx.map(o => o.getObject().id);
this.__oViewModel.setProperty('/list/selected', aSelected);
}
});
Para parecer e se comportar, tudo isso é bastante esperado:
ao clicar no botão, os identificadores selecionados listados com uma vírgula serão exibidos (deixaremos a verificação de uma matriz não vazia e de outros ui-ninhais fora do escopo do artigo).Mas imagine isso clicando no botão "Vamos lá!" Você precisa não apenas exibir os identificadores dos elementos selecionados, como está acontecendo agora, mas excluí-los do banco de dados.Obviamente, essas coisas precisam ser feitas somente após confirmação adicional do usuário.Bem, assunto trivial - adicione um fragmento de exibição para a caixa de diálogo de confirmação:ConfirmDialog.fragment.xml<core:FragmentDefinition
xmlns='sap.m'
xmlns:core='sap.ui.core' >
<Dialog
id='confirmDialog'
title=''
type='Message'
state='Warning'>
<content>
<Label text=' ?' />
</content>
<beginButton>
<Button
text=''
press='handleConfirmBtn'/>
</beginButton>
<endButton>
<Button
text=''
press='handleCancelBtn'/>
</endButton>
</Dialog>
</core:FragmentDefinition>
E o controlador para ele:ConfirmDialog.controller.jssap.ui.define([
"sap/ui/base/ManagedObject"
], function (ManagedObject) {
"use strict";
return ManagedObject.extend("project.ConfirmDialog", {
constructor: function (oView, fnConfirmBtn) {
this.__oView = oView;
this.__fnConfirmBtn = fnConfirmBtn;
},
exit: function () {
delete this.__oView;
delete this.__fnConfirmBtn;
},
open: function () {
const oView = this.__oView;
let oDialog = oView.byId("confirmDialog");
if (!oDialog) {
const oFragmentController = {
handleConfirmBtn: () => {
this.__fnConfirmBtn();
oDialog.close();
},
handleCancelBtn: () => {
oDialog.close();
}
};
oDialog = sap.ui.xmlfragment(oView.getId(), "project.view.fragment.ConfirmDialog", oFragmentController);
oView.addDependent(oDialog);
}
oDialog.open();
}
});
});
Como você pode ver, tudo é bastante clássico, no nível de exemplos do SAP UI5 SDK.Agora, para chamar esta caixa de diálogo, redefinimos o manipulador do nosso botão principal da seguinte maneira:handlePress: function (oEvent) {
const aSelected = this.__oViewModel.getProperty('/list/selected');
this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
aSelected.forEach(o => {
});
this.__confirmDialog.exit();
});
this.__confirmDialog.open();
}
Ou seja, aqui criamos nosso diálogo, passando como parâmetro a visualização "principal" na qual ele estará localizado e a função do manipulador de confirmação.Aqui chegamos ao mais interessante.Antes de ligar, revise o código do controlador de diálogo mais uma vez.Viu algo estranho?Não?Bem, então, ative o modo de rastreamento e, após selecionar os dois primeiros elementos, fique à vontade para clicar no botão. handlePress: function (oEvent) {
const aSelected = this.__oViewModel.getProperty('/list/selected');
this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
aSelected.forEach(o => {
});
this.__confirmDialog.exit();
});
this.__confirmDialog.open();
}
Viva, tudo funciona! Parece, qual é o problema? É possível que o usuário fique insaciável e queira excluir outra coisa. Vamos tentar prever suas ações: selecione o terceiro elemento e clique em "Vamos lá!": handlePress: function (oEvent) {
const aSelected = this.__oViewModel.getProperty('/list/selected');
this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
aSelected.forEach(o => {
Por que [1,2], pergunte-me você. É bom para você, há alguém para perguntar.E a primeira vez que vi um comportamento tão feio do intérprete, comecei a duvidar silenciosamente de mim mesma. Trabalho com várias estruturas há muito tempo, mas nunca encontrei nada parecido: sabe-se que const não garante a imutabilidade de objetos e matrizes ao longo de sua existência - mas aSelected nem é mencionado em nenhum outro lugar. Então ele apareceu, se apropriou e agora foi transferido para um retorno de chamada.Mas não vou atormentá-lo por muito tempo.É tudo sobre os contextos e encerramentos que todos os programadores de JS adoram.Isto é, de fato, durante a primeira execução do handleConfirmBtn, ainda temos um link para o manipulador junto com todo o contexto (incluindo aSelected). E com a confirmação subsequente da exclusão, é isso que é chamado.A maneira de corrigir o erro não era tão simples e inequívoca. Não bastava mover a declaração oFragmentController (após a 1ª chamada, o contexto foi perdido). E a seguinte foi a maneira mais concisa (darei apenas o código do método aberto):open: function () {
const oView = this.__oView;
let oDialog = oView.byId("confirmDialog");
if (!oDialog) {
const oFragmentController = {
handleConfirmBtn: function () {
this.__fnConfirm();
oDialog.close();
},
handleCancelBtn: function () {
oDialog.close();
}
};
oDialog = sap.ui.xmlfragment(oView.getId(), "project.view.fragment.ConfirmDialog", oFragmentController);
oDialog.controller = oFragmentController;
oView.addDependent(oDialog);
}
oDialog.controller.__fnConfirm = this.__fnConfirmBtn.bind(this);
oDialog.open();
}
Preste atenção nas últimas 4 linhas executáveis: dessa forma, eu "role" o ponteiro para o manipulador atual junto com o contexto correto.Em geral, apenas algumas linhas tiveram que ser adicionadas à versão original.Foi possível encontrar umoDialog.setBeginButton(new sap.m.Button({ text: '', press: this.__fnConfirmBtn }));
mas aqui, toda vez que você abre uma caixa de diálogo, um novo botão é criado, isso não é permitido.Também considerei a opção de atualizar o retorno de chamada via oDialog.getBeginButton (). AttachPress, mas apenas trava um manipulador adicional e foi nojento remover todos os disponíveis via .detachPress em uma linha.Aqui, essa aventura surgiu quase do nada (longe do primeiro ... ah, ui5!)PS Passando uma função para a função construtora do objeto, na qual um dos métodos possui um objeto controlador que contém referências às funções do manipulador. Parece que o que poderia dar errado?
Em geral, a situação descrita é um recurso da linguagem, e a estrutura da UI5 simplesmente não permite que ela seja resolvida com perfeição.