⬆️ ⬇️

PHPLego: Unobtrusive AJAX





Hello dear hablochiteli!



Have you ever thought about your site working equally well with JavaScript enabled and without JavaScript? So that if JavaScript is enabled, the site blocks are overloaded with AJAX-catfish, and if JavaScript is not, then just a transition to a new page took place?

')

Hmm ... I think this is an interesting task, and this is what a simple solution I managed to come up with. In this article I will try to describe in general terms the essence of this decision, without going into particular uninteresting details.



For myself, I formulated the task according to the following criteria:





Well, here are all the wishes. So let's get down to implementing ...



To begin with, we will have to come up with a system of modules, and how they will be inserted into each other.

The question is where to store the data on the status of each unit? Where is it better to do this, in cookies, in the $ _SESSION variable, in the $ _GET array or in the database? I chose the address bar, because You can always send a link to a friend and be sure that he will see the same thing as we do.

So, we decided that the state of each module should be reflected in the GET parameters. Those. According to the parameters in the GET line, each module will be able to determine what information it should display on this request.

I called the modules Lego-objects, and decided to arrange them in the form of classes in PHP.



The launch of each Lego object is as follows:



$lego = new MyLego('my_name'); // -      // .   run()   GET-   : $lego->run(); echo $lego->getOutput(); //      Lego- 




Let's think so that each Lego object listens to its variable from the address bar, which will tell it what to do, which class method to execute. And to take from the variable the data he needs, if necessary, because actions are usually done for a reason, and over some data - aydishnikami photos, commentary, etc.



And also let's agree that the address bar of the form:

 // http://example.ru/?my_name[act]=index&my_name[0]=1&my_name[1]=abc 


will call the index () method of a Lego object named my_name, passing two arguments to this method, '1' and 'abc'.



Thus, we will make with you such objects, the methods of which can be called directly from the address bar. For security, of course, we give such methods a prefix in the object, for example action_. This is so that the attacker could not call any system method and harm our site.



In order not to write the same code for each class of a Lego object, let's move it into one base class. Let's call it Lego. And all the classes of modules will inherit from it.



 abstract class Lego{ private $name; private $output; public function __construct($name = false){ //     ,        if(!$name) $name = get_class($this); $this->name = $name; } public function getName(){ return $this->name; } public function getOutput(){ return $this->output; } public function run(){ $action = $this->getAction(); //  , ,       action_ $method_name = 'action_'.$act; if(method_exists($this, $method_name)){ //    ,      GET !!! //  ,    ,    $this->output $this->output = call_user_func_array( array( $this, $method_name ), $this->getActionParams($action)); } else $this->output = "method {$action} does't exists"; //     AJAX.       ajax={_} if($this->_get("ajax") == $this->getName()){ echo $this->output; //        die; //   . } } //      (  my_name[act]=index); public function getAction(){ $lego_params = $this->getLegoParams(); if(!is_array($lego_params)) return; if(isset($lego_params["act"])) return $lego_params["act"]; return "default"; //     ,    GET   } //   ,    Lego    public function getLegoParams($lego_name = false){ if(!$lego_name) $lego_name = $this->getName(); return $this->_get($lego_name, array()); } //      (  &my_name[0]=1&my_name[1]=abc); public function getActionParams($action){ $lego_params = $this->getLegoParams(); if(!isset($lego_params[$action])) return array(); if(!is_array($lego_params[$action])) return array(); return $lego_params[$action]; } //     ,      GET static public function _get($key_name, $default_value = false){ return self::__get_from_array($_GET, $key_name, $default_value); } //  ,  ,    static private function __get_from_array($array, $key_name, $default_value = false){ if(!isset($array[$key_name])) return $default_value; return $array[$key_name]; } } 




Now, any Lego-module will look like this:



 class FotoLego extends Lego{ //      http://example.ru/?{lego_name}[act]=allfotos public function action_allfotos(){ $out = "     "; return $out; } //      http://example.ru/?{lego_name}[act]=onefoto&{lego_name}[0]={id_} public function action_onefoto($foto_id){ $out = "        id=$foto_id"; return $out; } //      http://example.ru/?{lego_name}[act]=bestfotos public function action_bestfotos(){ $out = "      "; return $out; } } 




And also, it turned out that the Lego-object can contain as many child Lego-objects as it wishes:



 class SomeLego extends Lego{ .... .... public function action_someMethod(){ //   Lego- $lego = new SomeSublego("sublego_name1"); $lego->run(); //  return $lego->getOutput(); //     } public function action_someMethodElse(){ //    Lego- $lego = new SomeSublegoElse("sublegoelse_name1"); $lego->run(); // , ,      Lego : Smarty::assign("content", $lego->getOutput()); return Smarty::fetch("some_template.tpl"); //     } .... .... } 




And in order to get the output of only the desired Lego object, we just need to add the ajax parameter to the address bar

 // : // http://example.ru/?LegoSite=fotos&ajax={______} //        Lego::run(),      




Well, you can start building the site, at this stage without any AJAX, but quite workable



We describe the class of the root lego controller itself:

 class LegoSite extends Lego{ //   public function action_default(){ //? Default ,        //        : $lego = new LegoAuth(); $lego->run(); //  Smarty::assign("auth_block", $lego->getOutput()); //  LegoAuth   //   : $lego = new LegoHotNews(); $lego->run(); //  Smarty::assign("hotnews_block", $lego->getOutput()); //  LegoHotNews   //  ,   , : $lego = new LegoArticles(); $lego->run(); //  Smarty::assign("articles_block", $lego->getOutput()); //  LegoArticles   //    -: Smarty::fetch("body.tpl"); //        } //   " ".      public function action_about(){ return Smarty::fetch("about_site.tpl"); //     } } 




Our entire site will consist of a single file - index.php:



 include ".common/autoload.php"; //    $lego = new LegoSite(); //            $lego->run(); //   echo $lego->getOutput();// !      




That's all, the site is ready, it remains only to decorate the templates (you are yourselves) and, most interestingly, add a bit of life-giving JavaScript-a. I designed it as a jQuery plugin.



Jquery.lego.js file:

 // ,      jQuery.fn.lego.load = function(lego_name, url, data, nocache){ jQuery.fn.lego.lastLoadedUrl = urldecode(url); jQuery.fn.lego.loadedUrls[lego_name] = urldecode(url); var lego = $("div.lego[name="+lego_name+"]"); lego.addClass("loading"); var pellicle = $("<div>"); //  ,    pellicle.addClass('pellicle'); $(".lego[name="+lego_name+"]").prepend(pellicle); var no_ajax_url = jQuery.fn.lego.getNoAjaxUrl(url); //  -   ,      if($(".lego[name="+lego_name+"]").length != 1){ document.location = no_ajax_url; return; } location.hash = url; //      //   var from_cache = LegoCache.get(lego_name, url); if(from_cache && data == null && !nocache){ //    $(".lego[name="+lego_name+"]").replaceWith(from_cache); return; } $.ajax({ type: data == null ? "GET" : "POST", url: url, data: data, success: function(received){ $().lego.log(", : "+received.length+"    "+lego_name+"..."); if($(received).hasClass('lego')){ $(".lego[name="+lego_name+"]").replaceWith(received); //   LegoCache.put(lego_name, url, received); } else{ $().lego.log(lego_name+":     Lego: "+url); document.location = no_ajax_url; } }, error: function(x){ $().lego.log("   url: "+url); } }); } jQuery.fn.lego.ajaxEnable = function(selector){ jQuery.fn.lego.startProcessHash(); if(!selector) selector = ""; //    $(selector+":not(.noajax) a:not(.noajax)").live("click.myEvent", function(e){ var href = $(e.currentTarget).attr("href"); //    if(href.match(/^(http(s)?:\/\/)|(javascript)/i)) return true; var name = $(e.currentTarget).lego().attr("name"); var legotarget = $(e.currentTarget).attr("legotarget"); if(typeof legotarget == "undefined") legotarget = name; var ajax_url = jQuery.fn.lego.getAjaxUrl(href, legotarget); jQuery.fn.lego.load(legotarget, ajax_url); return false; }); //    $("form:not(.noajax)").livequery("submit", function(e){ var name = $(this).lego().attr("name"); var legotarget = $(this).attr("legotarget"); if(typeof legotarget == "undefined") legotarget = name; jQuery.fn.lego.load(legotarget, $().lego.getAjaxUrl($(this).attr("action"), legotarget), $(this).serialize()); return false; }); } //  var LegoCache = { cache: {}, put: function(lego_name, url, data){ this.cache[lego_name+url] = data; }, get: function(lego_name, url, data){ if(typeof this.cache[lego_name+url] != 'undefined'){ var ret = $(this.cache[lego_name+url]); var reload_block = $("<a href='javascript:void(0)' onclick='jQuery.fn.lego.reload(this)' />"); reload_block.html("   , "); reload_block.addClass('reload_block'); try{ ret.prepend(reload_block); }catch(e){} return ret; } } } 




Of course, the full version still has a couple of minor functions that I did not include here, I'm afraid the article will become poorly readable because of this. But they are always available in SVN, at your service.

The full version of the PHPLego source code can be viewed in the repository on Google code:

code.google.com/p/phplego

There you can get invites to participate in the development.



You can test how it works in practice here .



Thank you, I hope someone will come in handy. Always yours, Jozhik.



PS Anyone who wants to share their Lego-modules, write to me, I already have something to maintain the exchange :)

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



All Articles