AJAX & Snippets
Na era dos aplicativos modernos da Web, em que a funcionalidade geralmente se estende entre o servidor e o navegador, o AJAX é um elemento de conexão essencial. Que opções o Nette Framework oferece nessa área?
- envio de partes do modelo, os chamados snippets
- passagem de variáveis entre PHP e JavaScript
- ferramentas para depuração de solicitações AJAX
Solicitação de AJAX
Basicamente, uma solicitação AJAX não difere de uma solicitação HTTP clássica. Um apresentador é chamado com parâmetros específicos. Cabe ao apresentador decidir como responder à solicitação: ele pode retornar dados no formato JSON, enviar uma parte do código HTML, um documento XML etc.
No lado do navegador, iniciamos uma solicitação AJAX usando a função fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// processamento da resposta
});
No lado do servidor, uma solicitação AJAX é reconhecida pelo método $httpRequest->isAjax()
do serviço que encapsula a solicitação HTTP. Ele usa o cabeçalho HTTP X-Requested-With
, portanto, é
essencial enviá-lo. No apresentador, você pode usar o método $this->isAjax()
.
Se você quiser enviar dados no formato JSON, use o método sendJson()
método. O método também encerra a atividade do
apresentador.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Se você planeja responder com um modelo especial projetado para AJAX, pode fazer isso da seguinte forma:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
//...
}
Trechos
A ferramenta mais poderosa oferecida pela Nette para conectar o servidor com o cliente são os snippets. Com eles, você pode transformar um aplicativo comum em um aplicativo AJAX com o mínimo de esforço e algumas linhas de código. O exemplo Fifteen demonstra como tudo isso funciona, e seu código pode ser encontrado no GitHub.
Os snippets, ou recortes, permitem que você atualize apenas partes da página, em vez de recarregar a página inteira. Isso é mais rápido e mais eficiente, além de proporcionar uma experiência de usuário mais confortável. Os snippets podem lembrá-lo do Hotwire para Ruby on Rails ou do Symfony UX Turbo. É interessante notar que a Nette introduziu os snippets 14 anos antes.
Como os snippets funcionam? Quando a página é carregada pela primeira vez (uma solicitação não AJAX), a página inteira, incluindo todos os snippets, é carregada. Quando o usuário interage com a página (por exemplo, clica em um botão, envia um formulário etc.), em vez de carregar a página inteira, é feita uma solicitação AJAX. O código no apresentador executa a ação e decide quais trechos precisam ser atualizados. A Nette renderiza esses trechos e os envia na forma de uma matriz JSON. O código de manipulação no navegador insere os trechos recebidos de volta na página. Portanto, somente o código dos trechos alterados é transferido, economizando largura de banda e acelerando o carregamento em comparação com a transferência de todo o conteúdo da página.
Naja
Para lidar com snippets no navegador, é usada a biblioteca Naja. Instale-a como um pacote node.js (para uso com aplicativos como Webpack, Rollup, Vite, Parcel e outros):
npm install naja
… ou insira-a diretamente no modelo da página:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Primeiro, você precisa inicializar a biblioteca:
naja.initialize();
Para transformar um link comum (sinal) ou o envio de um formulário em uma solicitação AJAX, basta marcar o respectivo
link, formulário ou botão com a classe ajax
:
<a n:href="go!" class="ajax">Go</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
or
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Redesenho de snippets
Todos os objetos da classe Control (inclusive o próprio Presenter) mantêm um registro da
ocorrência de alterações que exijam o redesenho. O método redrawControl()
é usado para essa finalidade.
public function handleLogin(string $user): void
{
// depois de fazer login, é necessário redesenhar a parte relevante
$this->redrawControl();
//...
}
O Nette também permite um controle mais preciso do que precisa ser redesenhado. O método mencionado acima pode usar o nome do snippet como argumento. Assim, é possível invalidar (ou seja, forçar um redesenho) no nível da parte do modelo. Se o componente inteiro for invalidado, todos os trechos dele também serão redesenhados:
// invalida o trecho de "cabeçalho
$this->redrawControl('header');
Snippets em Latte
Usar snippets no Latte é extremamente fácil. Para definir uma parte do modelo como um snippet, basta envolvê-la nas tags
{snippet}
e {/snippet}
:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
O snippet cria um elemento <div>
na página HTML com um id
especialmente gerado. Ao redesenhar
um snippet, o conteúdo desse elemento é atualizado. Portanto, quando a página é renderizada inicialmente, todos os snippets
também devem ser renderizados, mesmo que inicialmente estejam vazios.
Você também pode criar um snippet com um elemento diferente de <div>
usando um atributo n::
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Áreas de snippet
Os nomes dos snippets também podem ser expressões:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
Dessa forma, obteremos vários snippets como item-0
, item-1
, etc. Se invalidássemos diretamente um
snippet dinâmico (por exemplo, item-1
), nada seria redesenhado. O motivo é que os snippets funcionam como
verdadeiros trechos e somente eles são renderizados diretamente. Entretanto, no modelo, não há tecnicamente um trecho chamado
item-1
. Ele só aparece quando se executa o código ao redor do snippet, nesse caso, o loop foreach. Por isso,
marcaremos a parte do modelo que precisa ser executada com a tag {snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
E redesenharemos tanto o snippet individual quanto toda a área abrangente:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Também é essencial garantir que a matriz $items
contenha apenas os itens que devem ser redesenhados.
Ao inserir outro modelo no modelo principal usando a tag {include}
, que tem snippets, é necessário envolver
novamente o modelo incluído em um snippetArea
e invalidar o snippet e a área juntos:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* incluído.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Snippets em componentes
Você pode criar snippets em componentes, e o Nette os redesenha automaticamente. Entretanto, há
uma limitação específica: para redesenhar os snippets, ele chama o método render()
sem nenhum parâmetro.
Portanto, a passagem de parâmetros no modelo não funcionará:
OK
{control productGrid}
will not work:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Envio de dados do usuário
Juntamente com os snippets, você pode enviar quaisquer dados adicionais ao cliente. Basta gravá-los no objeto
payload
:
public function actionDelete(int $id): void
{
//...
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
}
Parâmetros de envio
Quando enviamos parâmetros ao componente por meio de solicitação AJAX, sejam eles parâmetros de sinal ou parâmetros
persistentes, devemos fornecer seu nome global, que também contém o nome do componente. O nome completo do parâmetro retorna
o método getParameterId()
.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
Um método handle com os parâmetros correspondentes no componente:
public function handleFoo(int $bar): void
{
}