Implementa padrão MVVM no ZK Framework usando @ViewModel, @Command, @Init, @NotifyChange e data binding. Use quando: estiver criando novas UIs ZK 8+, modernizando código legado MVC, ou precisando de ViewModels testáveis sem dependência de componentes UI.
Padrão arquitetural MVVM (Model-View-ViewModel) do ZK Framework que separa lógica de apresentação da UI através de data binding automático. Reduz ~40% do código boilerplate comparado ao MVC com Composers.
Crie uma classe POJO anotada com @ViewModel (opcional, apenas documentação). O ViewModel deve:
@Init em método de inicialização (executado após construtor)@Command em métodos acionados por eventos da view@NotifyChange("prop") para notificar mudanças em propriedades específicas@ViewModel // Opcional - apenas documentação
public class FuncionariosViewModel {
private List<Funcionario> funcionarios;
private Funcionario selecionado;
@Init
public void init() {
carregarFuncionarios();
}
@Command
@NotifyChange("funcionarios")
public void carregarFuncionarios() {
this.funcionarios = service.buscarTodos();
}
}
Método anotado com @Init é executado automaticamente após o construtor. Use para carregar dados iniciais:
@Init
public void init(@BindingParam("id") Long id) {
if (id != null) {
this.entity = service.buscarPorId(id);
}
}
Métodos anotados com @Command são invocados por eventos na view (cliques, submits):
@Command
@NotifyChange({"funcionarios", "mensagem"})
public void salvar() {
service.salvar(funcionario);
this.mensagem = "Salvo com sucesso!";
carregarFuncionarios();
}
Informe quais propriedades foram alteradas para atualizar a UI automaticamente:
// Notifica uma propriedade
@NotifyChange("selecionado")
// Notifica múltiplas propriedades
@NotifyChange({"funcionario", "mensagem", "erros"})
// Notifica tudo (menos eficiente)
@NotifyChange("*")
Use expressões @bind, @load, @save, e @command no arquivo .zul:
<window viewModel="br.com.zkminsamples.viewmodel.FuncionariosViewModel">
<!-- Data binding bidirecional -->
<textbox value="@bind(vm.selecionado.nome)" />
<!-- Data binding unidirecional (leitura) -->
<label value="@load(vm.usuario.nome)" />
<!-- Invocar command -->
<button label="Salvar" onClick="@command('salvar')" />
<!-- Binding com parâmetros -->
<button onClick="@command('editar', item=vm.selecionado)" />
<!-- Template para listas -->
<listbox model="@load(vm.funcionarios)">
<template name="model">
<listitem>
<listcell label="@bind(each.nome)" />
<listcell label="@bind(each.email)" />
</listitem>
</template>
</listbox>
</window>
Use @BindingParam no método e passe parâmetros na view:
ViewModel:
@Command
public void editar(@BindingParam("item") Funcionario item) {
this.selecionado = item;
}
View ZUL:
<button label="Editar"
onClick="@command('editar', item=each)" />
Implemente validação manual nos commands ou use Bean Validation:
@Command
@NotifyChange("erros")
public void salvar() {
this.erros = new HashMap<>();
if (funcionario.getNome() == null || funcionario.getNome().isEmpty()) {
erros.put("nome", "Nome é obrigatório");
}
if (!erros.isEmpty()) {
return; // Não salva se houver erros
}
service.salvar(funcionario);
}
Na view:
<textbox value="@bind(vm.funcionario.nome)" />
<label value="@load(vm.erros['nome'])" style="color: red" />
Em projetos Spring Boot, injete services via construtor:
@ViewModel
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // Importante!
public class UsuariosViewModel {
private final UsuarioService service;
@Autowired
public UsuariosViewModel(UsuarioService service) {
this.service = service;
}
@Init
public void init() {
// Service já está injetado
}
}
Configuração ZK + Spring (ZkAutoConfiguration.java):
@Bean
public ServletRegistrationBean<DHtmlLayoutServlet> zkLayoutServlet(
ApplicationContext applicationContext) {
DHtmlLayoutServlet servlet = new DHtmlLayoutServlet();
servlet.setWebAppContext(applicationContext); // Habilita DI
return new ServletRegistrationBean<>(servlet, "*.zul");
}
Para obter usuário logado e permissões:
@Init
public void init() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
this.usuarioLogado = auth.getName();
this.per missoes = auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
}
}
public boolean podeEditar() {
return per missoes.contains("ROLE_ADMIN");
}
Na view:
<button label="Excluir"
visible="@load(vm.podeEditar())"
onClick="@command('excluir')" />