📜 ⬆️ ⬇️

Grails, jQuery, AJAX: do anchor-navigation. Part 1

AJAX and everything, everything, everything


In the previous series, we made a simple Grails application using jQuery, and also decided for ourselves that using jQuery in Grails is possible and even necessary. Let's discuss more serious things that can be done with such a bunch.

It is easy to see that more and more websites use AJAX and partial page updates, and in incredible amounts. In particular, “stuffed” AJAX links can be used to navigate the page internally, to switch some tabs. This is good because
A) less data needs to be distilled from the server - only the necessary piece of the page and
B) Web pages often load simply gigantic CSS and JavaScript files that you can not download again with an AJAX update.

So, building a scripted application is very common: one large “start” page that loads all JavaScript code and CSS and smaller “internal” functional blocks loaded via AJAX. There are a number of problems with this:
  1. As a result of AJAX actions, the internal state of the page is not reflected in the address bar of the browser.
  2. As a result, internal pages cannot be bookmarked; you cannot “send a link to a friend.”
  3. Back / Forward browser navigation does not work, because AJAX links do not go down in browser history.
However, large sites have found some kind of “hacker” solution, which we will now consider and write a small one of our own on Grails and jQuery.
')

Anchor navigation


The fact is that you can actually change the address bar of your browser without reloading the page, if you change only the anchor, i.e. The last part of the address bar next to the bars is # . The browser perceives this transition within the page, and quietly ignores the situation when there is no necessary anchor on the page, simply updating the address and history . This is exactly what we need. If you keep the state of the page inside the anchor, then you can return to it through the bookmark and you can use Back / Forward transitions (!). In this case, the base URL of the page will not change and the page will not reload.

There is no need to go far for examples of implementing such a solution. This scheme is used in Facebook, Gmail, Google Picasa Web Albums, in a significant amount it can be seen on odnoklassniki.ru. The Google Web Toolkit library is entirely based on anchor navigation.

Let's say in Gmail you can get a direct link to the letter, and the link will be bookmarkable. The link will look something like this:

mail.google.com/mail/?shva=1 #inbox/12c5f14c01a5473c

The hedgehog is clear that 12c5f14c01a5473c is some kind of internal letter ID.

We write application


Let's think about the implementation of this approach. The address bar changes simply:

 document.location.hash = '#myAnchor'; 

(either directly through the link
 <a href="#myAnchor">My Link</a> 
).

Let's start writing a Grails app with the intriguing name my-app with navigation based entirely on AJAX. Our application will have three tabs:

anchor1

Outwardly, it looks like a regular page, but we want to ensure that only the inside of the page is updated without a full reload.

First, let's draw a SiteMesh layout of the following form:

grails-app/views/layouts/main.gsp
 <html> <head> <title><g:layoutTitle default="Grails" /></title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" /> <g:layoutHead /> <g:javascript library="jquery"/> <g:javascript library="application" /> </head> <body> ... %{--   --}% <div class="navbar"> <div class="navitem"> <a href="#do/receipts" class="navlink"></a> <div class="spinner" /> </div> <div class="navitem"> <a href="#do/buy" class="navlink"> </a> <div class="spinner" /> </div> <div class="navitem"> <a href="#do/feedback" class="navlink"></a> <div class="spinner" /> </div> </div> %{--   --}% <div id="pageContent"> <g:layoutBody /> </div> ... </body> </html> 


There are no surprises here yet. As you can see, links are no different from ordinary anchor links. How do they work? To do this, we write this code on jQuery:

web-app/js/application.js
 //      var currentState = ''; function buildURL(anchor) { return document.location.toString().substr(0, document.location.toString().indexOf('#')) + anchor.substr(1); } function clickNavLink() { //  ? var href = $(this).attr('href'); //       if (href == currentState) { return false; } if (document.location.hash != href) { document.location.hash = href; } //   var link = this; //    $(this).parent().find('.busy').show(); $(this).hide(); var targetURL = buildURL(href); currentState = href; //   ,     $.ajax({ context:$('#pageContent'), url:targetURL, dataType:'html', method:'GET', complete: function() { //   . $(link).show(); updateNavLinks(); }, success: function(data) { //  ""  . $('#pageContent').html(data); } }); return true; } //   ,   / function updateNavLinks() { $('a.navlink').each(function(i) { var href = $(this).attr('href'); $(this).parent().find('.busy').hide(); if (href == currentState) { $(this).addClass('disabled'); } else { $(this).removeClass('disabled'); } }); } // .     . jQuery(document).ready(function() { $('a.navlink').each(function() { $(this).click(clickNavLink); }); }); 


Everything is quite simple here: we store the current state of the page in the JavaScript variable currentState . When clicking on the link, the internal page is loaded via AJAX, the result of the AJAX call is saved in the # pageContent div . In this case, the URL of the page being loaded is formed by adding the anchor path to the base address of the page, i.e.

 /my-app/#do/receipts => /my-app/do/receipts 

This simple rule immediately helps us understand what the link does. In order to display the link as “current”, we assign the class disabled . In CSS (which I will not give), this class will be displayed in a different color so that you can see which link is current (visited).

Server part


Now it is good to make a server stuffing. I wrote a simple controller to handle all three links:

grails-app/controllers/DoSomethingController.groovy
 class DoSomethingController { def receipts = { [receipts:['  ', '']] } def buy = { [places:['  ', ' â„–1']] } def feedback = { [feedback:['',' ',' ,  !']] } } 


and to him made three simple pages. I will cite only one of them:

grails-app/views/doSomething/receipts.gsp
 <%--   --%> <%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <meta name="layout" content="main" /> </head> <body> <h1>    </h1> <ul> <g:each in="${receipts}" var="receipt"> <li>${receipt.encodeAsHTML()}</li> </g:each> </ul> </body> </html> 


Now let's hang up the controller on our / do / * links:

grails-app/conf/UrlMappings.groovy
 class UrlMappings { static mappings = { "/do/$action?/$id?" { controller = 'doSomething' } } } 


However, this is not enough. There are problems: our client and server parts need to be finalized, which I will write about in the next part .

Source: https://habr.com/ru/post/108611/


All Articles