Pular para o conteúdo principal

A caixa: o que um bloco pode e não pode fazer

O HTML de cada bloco roda isolado, numa caixa: um quadro de outro endereço, sem acesso ao construtor, ao app nem aos dados de quem está usando. As regras abaixo valem igual no construtor, no "ver" e no app: o que funciona no construtor funciona no app.

Em resumo​

PodeNão pode
Script, CSS e fonte de seis CDNs, em httpsScript, CSS ou fonte de qualquer outro endereço
Imagem em data: e SVG escrito no HTMLImagem de um endereço, nem de um CDN
Chamar os endpoints do cliente com fetch('/api/ops/...')Qualquer outra conexão: fetch para fora, XMLHttpRequest, WebSocket, EventSource, sendBeacon
Guardar dados em variáveis enquanto a Página está abertaCookie, localStorage, sessionStorage
Um modal feito no próprio HTMLalert(), confirm(), prompt() e window.print()
Links para as Páginas do workflow, também numa aba novaLinks e janelas para outro site
Baixar um arquivo que a Página gera; copiar texto num cliqueLer a área de transferência

O primeiro <!doctype html> é opcional: sem ele, a caixa o põe.

Os CDNs​

Script, CSS e fontes só vêm destes seis endereços, sempre com https:

  • https://cdnjs.cloudflare.com
  • https://cdn.jsdelivr.net
  • https://unpkg.com
  • https://cdn.tailwindcss.com
  • https://fonts.googleapis.com
  • https://fonts.gstatic.com

Funciona: a fonte Inter do Google Fonts e os ícones do Font Awesome no cdnjs.

<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body { font-family: 'Inter', sans-serif; margin: 0; padding: 24px; }
</style>
</head>
<body>
<h1><i class="fa-solid fa-file-invoice"></i> Cobranças</h1>
</body>
</html>

Não funciona: o jQuery de code.jquery.com, que não é um dos seis. O script não carrega, e o $ não existe.

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

O mesmo jQuery no cdnjs (https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js) carrega. Também não carregam: um CDN em http://, e um endereço que só parece um CDN (https://cdnjs.cloudflare.com.outro.site/...).

As imagens​

Uma imagem só vem de data: (o arquivo escrito dentro do HTML) ou de um <svg> no próprio HTML. Nenhuma imagem vem de um endereço, nem de um dos seis CDNs, e isso vale também para o background-image: url(...) do CSS.

Funciona: um ícone em SVG no HTML, e uma imagem em data:.

<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="10" fill="#4f46e5" />
</svg>

<img alt="Ponto verde" width="16" height="16"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='8' fill='%2316a34a'/%3E%3C/svg%3E">

Não funciona: um logo num endereço.

<img src="https://cdn.jsdelivr.net/gh/exemplo/marca/logo.png" alt="Logo">
Ícones

Para ícones, uma fonte de ícones de um CDN (como o Font Awesome, acima) ou o SVG escrito no HTML.

As imagens do construtor ainda não existem

Subir uma imagem pelo construtor e usá-la numa Página vem depois. Até lá, uma imagem é data: ou SVG.

A rede: só a ponte​

O HTML não tem rede. A única conexão é o fetch para os endpoints do cliente, fetch('/api/ops/...'), que o construtor (e o app) faz por ele (veja Chamar a API).

Não funciona: buscar um dado de outro site, ou mesmo de um CDN.

// Barrado: outro site. O fetch rejeita com TypeError.
const cotacao = await fetch('https://api.exemplo.com/cotacao');

// Barrado também: um JSON de um CDN.
const dados = await fetch('https://cdn.jsdelivr.net/npm/pacote@1/dados.json');

Também são barrados o XMLHttpRequest (mesmo para /api/ops/), o WebSocket, o EventSource e o navigator.sendBeacon. Um dado de fora que a Página precisa vem por um endpoint: é um pedido ao time de desenvolvimento.

Nada fica no navegador​

A caixa não tem cookie nem armazenamento: o document.cookie, o localStorage e o sessionStorage dão erro, e o script para naquela linha.

Não funciona:

localStorage.setItem('filtro', 'abertas'); // erro: o resto do script não roda

Funciona: guardar numa variável enquanto a Página está aberta; levar o valor a outra Página num parâmetro do link; guardar de verdade por um endpoint.

<a href="/cobrancas?status=aberta">Ver as cobranças abertas</a>

<script>
// Na Página /cobrancas:
const status = new URLSearchParams(location.search).get('status'); // 'aberta'
</script>

Sem janelas do navegador​

O alert() e o window.print() não fazem nada, o confirm() responde sempre não (false) e o prompt(), null. Nenhum avisa.

Não funciona: o confirm() responde false, e a exclusão nunca acontece.

if (confirm('Excluir este cliente?')) {
excluir();
}

Funciona: uma confirmação feita no próprio HTML.

<style>
.modal { position: fixed; inset: 0; background: rgb(0 0 0 / 0.4); display: grid; place-items: center; }
.modal[hidden] { display: none; }
.modal > div { background: white; padding: 24px; border-radius: 8px; }
</style>

<button type="button" id="excluir">Excluir</button>

<div id="confirmar" class="modal" hidden>
<div>
<p>Excluir este cliente?</p>
<button type="button" id="nao">Cancelar</button>
<button type="button" id="sim">Excluir</button>
</div>
</div>

<script>
const confirmar = document.getElementById('confirmar');
document.getElementById('excluir').addEventListener('click', () => (confirmar.hidden = false));
document.getElementById('nao').addEventListener('click', () => (confirmar.hidden = true));
document.getElementById('sim').addEventListener('click', () => {
confirmar.hidden = true;
excluir();
});

function excluir() {
// a chamada ao endpoint (veja "Chamar a API")
}
</script>

Para imprimir, gere um PDF (por exemplo, com o jsPDF do cdnjs) e baixe: quem usa imprime o arquivo.

Para fora da caixa​

  • Abrir numa aba nova: um link com target="_blank" para uma Página do workflow abre uma aba nova (veja Os links entre Páginas). Para outro site, nada abre, e o endereço aparece como bloqueado.
  • Baixar: um arquivo que a Página gera (um <a download> de um blob: ou de um data:, o save() do jsPDF) baixa. Um <a download> para outro site, não.
  • Copiar: navigator.clipboard.writeText() dentro de um clique põe o texto na área de transferência. Ler dela, não.
  • Formulários: veja Os formulários.
  • Não há: iframe dentro do bloco, Worker, top.location, link com target="_top", <base href>.

Funciona: baixar um CSV gerado na Página.

<button type="button" id="baixar">Baixar CSV</button>
<script>
document.getElementById('baixar').addEventListener('click', () => {
const csv = 'nome;preco\nAcme;1500.00\n';
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
link.download = 'clientes.csv';
link.click();
});
</script>

A altura da tela​

O que o HTML chama de "altura da tela" (100vh, o h-screen e o min-h-screen do Tailwind) depende de onde o bloco está:

  • No canvas, e numa Página de vários blocos: é a altura de uma tela da largura da Página (numa de 1280, 800 px), mesmo num bloco mais estreito. O bloco tem a altura do conteúdo.
  • Numa Página de um bloco só, no "ver" e no app: é a janela de verdade. O bloco tem a altura da janela e rola sozinho: um cabeçalho position: fixed ou sticky fica no alto, e um modal position: fixed; inset: 0 abre no meio da tela.

A troca vale para o CSS que está no HTML (no <style> e no style="..."), não para uma folha de CSS de um CDN.

O que foi barrado: o selo​

Quando a caixa barra alguma coisa (um script de fora, uma imagem, um fetch, um link para outro site), o construtor mostra:

  • no canvas: um selo no bloco, como "2 bloqueados"; com o bloco selecionado, o painel lista os endereços;
  • no "ver": o mesmo selo, com os endereços ao passar o mouse.

No app, nada aparece: quem usa só vê a Página sem aquilo. Confira o selo antes de publicar.

Quando o bloco para​

A mensagem no blocoO que aconteceuO que fazer
"Este bloco parou."O HTML levou a caixa para outro endereço (um <meta http-equiv="refresh">, um location.href para fora). No canvas, também um location.href que roda sozinho ao abrir, mesmo para uma Página do workflow.Tire a navegação para fora, e navegue só depois de um clique. Para ir a outra Página, use um link (veja Os links).
"Este bloco travou."Um script que não termina (um laço sem fim) segurou a caixa.Corrija o laço.
"Não foi possível abrir este bloco: … não respondeu."O endereço da caixa não respondeu: a rede, ou um filtro de internet da empresa que bloqueia o endereço da caixa.Recarregue. Se continua, peça à TI para liberar o endereço da mensagem.

O bloco que parou ou travou fica assim até o HTML dele mudar; recarregar a página tenta todos de novo.