⬆️ ⬇️

We write single-page web application framework in 60 lines of code

As a preface





Web design people have recently tried out single page web applications. What is justified in many cases.

But it is obviously wrong to assume that a single page web application cannot be done without something like AngularJS, Ember and other Knockouts.

In any case, if you need to do something simple like the To-do list, it is not necessary to drag the kilobyte mega framework to the client. In fact, kilobytes of traffic is half the trouble. The main price, say AngularJS, is that, like any universal data binding mechanism, it creates a significant run-time load.



This article is about how in 60 lines of code + jQuery / Zepto to make a simple app framework that can be expanded to fit your needs and without unnecessary entities in the load.

')

Formulation of the problem





Our framework should ...:



  1. ... support routing, i.e. it should be possible to say in the declarative model: “this url hash should be shown in this view”.
  2. It should be possible to dynamically load different views. Some parts of our application may be either heavy to boot, or not needed, for example, for a non-logged in user.
  3. Must be able to dynamically load scripts. For the reasons stated in paragraph 2
  4. Our application will support browsing history - the back button in the browser should show the previous page, etc.
  5. Well, all this should be compact and extensible, as the logic of our application will require.




An example of what we want to get





The Bootstrap application contact list contains the list itself, the card contains contact details and a dashboard. What will be on that panel is not important for us - we know that something will be fine.



Personas demo







Go...





Single page web application consists of one, as a rule static, html file. But by design we have explicit sub-pages or views. We agree with ourselves that these sub-pages will be represented by <section> elements in our markup:



<body> ... <section id=dashboard class="container" src="pages/dashboard.htm"></section> <section id=about class="container" src="pages/about.htm"></section> <section id=contact class="container" src="pages/contact.htm"></section> <section id=contacts class="container" src="pages/contacts.htm"></section> ... </body> 




Everything is clear here except for the non-standard src attribute (for the section element).

We agree that the src attribute will point to the html fragment required to represent this view. Such fragments will be loaded on demand i. only when the user asks to show this view.



Further, we agree with our web designer that the section element whose active class is set will be current and accordingly visible. For this we need only one CSS rule:



 /* section visibility */ body > section:not(.active) { display:none; } 




routing




We agree with the team that the navigation inside our application is done via hyperlinks of the form



Those. activation of such hyperlink should show a section element with id = "section-name".

Using hyperlinks to display parts gives us out of the box support for browsing history in the browser (the forward and back buttons).



In this implementation, I use a ready-made hashchange () jQuery plugin, but if the target browsers are only those that are supported by jQuery2, then a fairly ordinary event handler on the corresponding event.



The structure of the loaded "page" fragment




In our case, the loaded fragment will consist of markup (actually HTML) and the script section - the processor of our page. Here is an example

pages / contact.htm - a card for showing / editing a single contact.



  <form class="form-horizontal" role="form" name="contactDetails"> ... </form> <script> app.handler(function() { //| //| view initialization: //| var $page = $(this); var $firstName = $("[name=firstName]"); var $lastName = $("[name=lastName]"); ... //| //| view presentation: //| return function(param) { var contact = data.contacts[param]; $firstName.val(contact.firstName); $lastName.val(contact.lastName); ... }; }); </script> 




Calling app.handler(function() {...}) in the code above initializes our view and registers the data loader function in view.



Actually that's all. It remains to bring the code of our app framework - the very 60 lines of code that link all this together.



In principle, everything should be clear without any special comments. But if that - whistle, do not be shy.



 // Simple single page application framework // Author: andrew @ terrainformatica.com (function($,window){ var pageHandlers = {}; var currentPage; // show the "page" with optional parameter function show(pageName,param) { // invoke page handler var ph = pageHandlers[pageName]; if( ph ) { var $page = $("section#" + pageName); ph.call( $page.length ? $page[0] : null,param ); // call "page" handler } // activate the page $(".nav li.active").removeClass("active"); $(".nav li a[href=#"+pageName+"]").closest("li").addClass("active"); $(document.body).attr("page",pageName) .find("section").removeClass("active") .filter("section#" + pageName).addClass("active"); } // "page" loader function app(pageName,param) { var $page = $(document.body).find("section#" + pageName); var src = $page.attr("src"); if( src && $page.find(">:first-child").length == 0) { $.get(src, "html") // it has src and is empty - load it .done(function(html){ currentPage = pageName; $page.html(html); show(pageName,param); }) .fail(function(){ $page.html("failed to get:" + src); }); } else show(pageName,param); } // register page handler app.handler = function(handler) { var $page = $(document.body).find("section#" + currentPage); pageHandlers[currentPage] = handler.call($page[0]); } function onhashchange() { var hash = location.hash || "#dashboard"; var re = /#([-0-9A-Za-z]+)(\:(.+))?/; var match = re.exec(hash); hash = match[1]; var param = match[3]; app(hash,param); // navigate to the page } $(window).hashchange( onhashchange ); // attach hashchange handler window.app = app; // setup the app as global object $(function(){ $(window).hashchange() }); // initial state setup })(jQuery,this); 




All of the above is the squeeze of the real framework used in several mobile web applications.

In the mobile case, the app is extended with the app.getData() and app.postData() methods - wrappers over $.ajax() support caching in localStorage and the views switching animation. I leave this functionality to the imagination of readers.

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



All Articles