Sempre sonhei em criar esse tipo de site que carregue todas as suas páginas de forma dinâmica. Parecia difícil, então adiei aquele momento para … agora. E agora que fiz isso, sinto que foi mais fácil do que eu esperava. Então, por que não compartilhar a diversão?
Existem 3 coisas: o html, os controladores que os servem (php, python, ruby …) e o javascript. Vamos começar do mais fácil para o mais difícil, embora você possa ler o html e os controladores (visualizações se você estiver usando uma MTV).
O HTML
As páginas HTML que tenho são geradas pelo sistema de modelos do Django. Se você não usa um sistema de templates, terá que fazer diferentes arquivos: um para chamadas normais (para quem não tem o javascript ativado!) E outro sem as coisas que não queremos carregar (então não <head >, não <header>, não <footer>, normalmente apenas o conteúdo).
No Twig, eu normalmente faria isso:
{% if ajax is not true %}
{% extends 'layout.html' %}
{% endif %}
{% block content %}
my content :)
{% endblock %}
É simples. Se ajax == True
então você não estender seu bloqueio.
Para o sistema de modelagem do Django, tive que usar uma solução alternativa porque você não pode envolver a função extends em um if
.
{% extends ajax|yesno:"blogs/ajax.html,blogs/layout.html" %}
{% block content %}
my content :)
{% endblock %}
e eu tive que criar outro ajax.html contendo apenas o conteúdo do bloco
{% block content %}{% endblock %}
a yesno
parte é apenas um operador ternário
O PHP / Python / Ruby …
Agora, você tem que chamar seus modelos. Se você não usa nenhum modelo, apenas entenda que uma variável GET é enviada aos controladores. se queremos apenas o conteúdo, se queremos a página inteira.GET['ajax'] = True
false
É muito fácil. Vou te dar o código, caso você não tenha entendido:
Em Django
# ajax?
ajax = request.GET.get('ajax', False)
# output
context = { 'ajax': ajax}
return render(request, 'content.html', context);
O get
retorna False se nada for encontrado.
No CodeIgniter
// ajax?
$ajax = ($this->input->get('ajax'));
// output
$data = array('ajax' => $ajax);
$this->twig->display('content.html', $data);
(note que eu uso o twig no Code Igniter, que não é o mecanismo de modelagem padrão)
O Javascript
Agora vem a parte interessante. Mas se você entender o que escrevi antes, você entenderá como meu sistema funciona.
Carregar uma página
Primeiro, aqui está minha função para carregar uma nova página: (e sim, esqueci, uso jQuery )
function ajaxLocation(url, scrolly){
// spectacle
$('.flash_helper').show()
$(window).scrollTop(0)
scrolly = typeof scrolly !== undefined ? scrolly : 0
// load
$.get(url, { ajax : true }, function(data){
$('main').empty().html(data)
$(document).ready(function(){
$(window).scrollTop(scrolly)
$('.flash_helper').fadeOut('xslow')
})
})
}
url
é o caminho completo que você deseja carregar, por exemplo: ‘ ‘ ou ‘ ‘/blog/blog_23.html
/blog/23/title/
scrolly
é a posição vertical da barra de rolagem, é inútil no momento, mas quando o usuário deseja voltar às páginas anteriores, é incômodo não devolver a localização anterior exata.scrollTop
é a função jQuery que define a barra de rolagem em uma posição. Eu também testo sescrolly
é dado como um parâmetro para a função (com um operador ternário novamente) e o defino como 0 se não for.
A primeira parte do código é apenas para o show, eu uso um para produzir um efeito de “flash branco” e carrego a nova página por trás da cena..flash_helper
razões:
- É bom porque mostra ao usuário que o conteúdo mudou
Eu uso uma regra css global que desativa a estrutura de tópicos quando você clica em um link. Se a página não mudar instantaneamente, o usuário pode pensar que seu clique não funcionou. No código, a primeira coisa que faço é piscar a página, para que o usuário saiba que algo está acontecendo.
a:active, a:focus{
outline:0;
}
.flash_helper{
position:static;
top:0;
left:0;
background-color:#FFF:
width:100%;
height:100%;
z-index:99;
display:none;
}Em seguida, carrego o url com a função jQuery , que é um atalho para
$.get
$.ajax('get')
Eu esvazio a div onde o conteúdo está
Em vez disso, adiciono minha nova página
Quando a página carregar todo o texto (use se quiser que as imagens também sejam carregadas), vamos para a posição certa (inútil no momento) e removemos a tela branca.
load()
Nota : você pode usar a função jQuery para remover inteiramente o div (s), e você pode usar as funções , , , para inserir o conteúdo exatamente onde você quer.remove()
append
prepend
before
after
Substituir o comportamento padrão dos links
Quando a página é carregada, tenho certeza de que os links clicados iniciam minha função especial em vez de abrir uma nova página. Apenas links internos! pois o javascript não pode carregar links externos.
$(document).ready(function(){ // page loaded
$(document).on('click', '.internal', function(e){
// new tab or same tab?
if(ctrlPressed) return true
e.preventDefault()
// update this state first
var stateObj = { 'url': history.state.url, 'scrolly': $(window).scrollTop()}
history.replaceState(stateObj, "page", window.location.pathname)
// next state
var url = $(this).attr('href')
var stateObj = { 'url': url };
history.pushState(stateObj, "page", url)
// load
ajaxLocation(url, 0)
})
})
- Estou usando uma jQuery em vez de uma jQuery simples porque preciso garantir que os links futuros que serão carregados também sejam clicáveis. É o novo que está obsoleto.
on()
click()
live()
- Estou testando para ctrl + click (ou cmd + click no mac) para quem deseja abrir o link em uma nova aba. O
ctrlPressed
é definido como verdadeiro em caso afirmativo (em uma função que explicarei mais tarde) e interrompe o código aqui. - Os dois bits de código que atualizam o estado e enviam o próximo estado são para os botões anterior e avançar do seu navegador. Conforme explicado na fabulosa página developer.mozila .
- Finalmente carrego o URL na função que acabamos de criar!
Estados?
OK. Devo explicar o replaceState
e o pushState
.
Essas duas funções funciona de forma semelhante, eles precisam de 3 parâmetros: infos
, title
, url
.
Infos
será útil saber onde está a página anterior e onde estava a barra de rolagem. A title
parte é inútil (e não é usada por nenhum navegador que eu conheço). A url
parte é … bem, é o url.
Eu usei qual é a url do estado atual (é impossível em javascript verificar os outros estados). E eu também uso . Eles são iguais aqui.history.state.url
window.location.pathname
/! Cuidado, porém, na primeira vez que você carregar uma página (abrindo uma nova guia, por exemplo), você terá um estado = null (trataremos disso mais tarde).
Estou atualizando o estado atual com a posição final da barra de rolagem (estamos saindo da página)
Estou criando um novo estado . Isso mudará o endereço na barra de endereço do navegador (por isso é legal se você precisar copiar / colar o url ou salvá-lo) e também criará um novo estado no histórico. Agora, se você clicar no botão anterior do seu navegador, ele ficará na mesma página e carregará as variáveis do estado anterior (que acabamos de modificar). Isso não atualiza a página e é por isso que podemos fazer o carregamento dinâmico de Ajax aqui 🙂
Tudo bem, estamos todos bem.
Agora ainda precisamos de algumas outras coisas. O mais fácil primeiro:
Ctrl + Clique ou Cmd + Clique no mac
Esta é a função que verifica se o usuário deseja abrir o link em uma nova guia:
var ctrlPressed = false
$(window).on('keydown', function(e){
if(e.metakey || e.ctrlKey) ctrlPressed = true
}).on('keyup', function(e){
if(e.metakey || e.ctrlKey) ctrlPressed = false
})
bem direto.
Primeiro estado
Como eu disse antes, se você carregar a página pela primeira vez, isso criará um null
estado. Isso é ruim, pois não poderemos voltar à nossa primeira página se continuarmos pressionando o botão anterior.
Solução? Envie um novo estado primeiro no código.
if(history.state === null){
var stateObj = { 'url': window.location.pathname, 'scrolly': 0}
history.pushState(stateObj, "initial", window.location.pathname)
}
Se você entendeu as partes anteriores, não será difícil entender.
Botão Anterior / Avançar
Agora, se alguém apertar o botão anterior (ou botão de avançar), não fará nada. Porque como eu disse, ele irá obter as variáveis de estados anteriores / anteriores que criamos e eles não alteram o window.location.
window.onpopstate = function(event) {
if(event.state != null){
ajaxLocation(event.state.url, event.state.scrolly)
}
};
Este ouvinte de evento fará algo PopState
. Que é chamado toda vez que clicamos no botão anterior / avançar de nossos navegadores. Depois de um PopState, o estado atual muda e só temos que verificar qual é o seu URL e a posição de sua barra de rolagem e atribuí-los à nossa ajaxLocation
função 🙂 e voilà! Feliz?
Oh espere. Mais uma coisa. O Chrome e o Safari fazem isso PopState
quando carregam uma página pela primeira vez. É inútil, mas é realmente o que você deve fazer de acordo com as especificações.
Portanto, apenas verificamos se não estamos em um null
estado primeiro 🙂
Palavras Finais
Eu não queria escrever muito quando comecei isso, mas tudo bem … Espero que você tenha entendido tudo, você pode me fazer perguntas nos comentários 🙂