Como permanecer na mesma página

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 == Trueentã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 yesnoparte é 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'] = Truefalse

É 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 getretorna 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 se scrollyé 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()appendprependbeforeafter

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 replaceStatee o pushState.

Essas duas funções funciona de forma semelhante, eles precisam de 3 parâmetros: infos, title, url.

Infosserá útil saber onde está a página anterior e onde estava a barra de rolagem. A titleparte é inútil (e não é usada por nenhum navegador que eu conheço). A urlparte é … 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.urlwindow.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 nullestado. 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 ajaxLocationfunção 🙂 e voilà! Feliz?

Oh espere. Mais uma coisa. O Chrome e o Safari fazem isso PopStatequando 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 nullestado 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 🙂