We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 22213
    • 52 Posts
    hi folks

    I’ve been upgrading my own site to use modx, after building my first client site using it. One objective I had was to integrate more AJAX functions into my site: specifically, I wanted the user to be able to browse posts without reloading the whole page, and without a lot of scrolling.

    Ditto offers lots of great functionality, but not so much for AJAX approaches. Where I needed this was in pagination: I wanted to have Ditto create pagination links that would trigger a javascript function that would then send the httpRequest I use to replace the content on part of the page.

    The following is a hack I’ve put together to allow it to do so. Installed, it does these things:

    - it creates javascript pagination links in addition to the conventional pagination links;
    - it allows you to control a broad range of parameters related to ajax links, like function called, event trigger, etc.
    - it allows you to specify POST and GET variables sent when requesting the AJAX data for re-use as javascript arguments
    - it provides a relatively simple CHUNK based control of these functions without delving into code;
    - it provides basic debugging features to allow you to determine what output is being generated when things go wrong.

    Specifically, it creates links that look like this:

    <a href="" onclick="javascript:getExtras('music',3); return false;" id="myPrevAjaxID" class="myPrevAjaxClass">Later</a>
    


    If you see why this is useful, read on.
    PLEASE NOTE: this is alpha-quality at the moment. Don’t use this in a live site! You’ve been warned!

    ALSO: make a duplicate backup of the ditto.class.inc.php file we modify in the next step if you want to do this...

    This code is inserted into ditto.class.inc.php. It’s put into the paginate function. For clarity, I’ve included the existing code immediately preceding and following: what is required is the stuff between the comment blocks:

    		$modx->setPlaceholder('next', $nextplaceholder);
    		$modx->setPlaceholder('previous', $previousplaceholder);
    		$modx->setPlaceholder('splitter', $split);
    		$modx->setPlaceholder('start', $start +1);
    		$modx->setPlaceholder('stop', $limiter);
    		$modx->setPlaceholder('total', $total);
    		$modx->setPlaceholder('pages', $pages);
    		$modx->setPlaceholder('totalpages', $totalpages);
    
    
    		// ---------------------------------------------------
    		// Additional code to create AJAX-friendly navigation
    		// Written by Dan Donaldson: use at your own risk
    		// ---------------------------------------------------
    			
    		if ($extras['ajax']) {
    				
    			$ajaxVals = array();
    			$lookup = explode (";",$modx->getChunk($extras['ajax']));
    			foreach ($lookup as $vals) {
    			  $val = explode(":",trim($vals));
    			  $ajaxVals[$val[0]] = $val[1];
    			}			
    			// construct ajax links out of existing values
    
    			if (isset($ajaxVals['function']) && isset($ajaxVals['event'])) {
    				$mode = (isset($ajaxVals['mode'])) ? $ajaxVals['mode'] : "link" ;
    				switch ($mode) {
    					case 0:
    					// add any other modes here as cases
    					case "link" :
    					default:
    						$ajaxLink = "<a href='". $ajaxVals['link']."' ";
    						$ajaxLink.=$ajaxVals['event']."=\"javascript:".$ajaxVals['function'];
    						$orderedArgs = array();
    						// get values from arguments passed						
    						if (isset($ajaxVals['httpArgs'])) {
    							$argvals = $ajaxVals['httpArgs'];
    							$tmp = explode("|",$argvals);							
    							foreach ($tmp as $httpItem) {
    								$httpTmp = explode("=",$httpItem);
    								$args = explode(",",$httpTmp[1]);							
    								foreach($args as $a) {
    									$src = strtoupper($httpTmp[0]);
    									if ($src == "POST") {
    										$orderedArgs[] = "'".$_POST[$a]."'";
    									} else if ($src == "GET") {
    										$orderedArgs[] = "'".$_GET[$a]."'";
    									}
    								}
    							}						
    						}
    						
    						if ($previous > -1) {
    							$ajaxPrevArgs = $orderedArgs;
    							$ajaxPrevArgs[] = $previous;
    							$ajaxPrev = $ajaxLink."(".implode(",",$ajaxPrevArgs)."); return false;\"";
    							$ajaxPrev.= (isset($ajaxVals['prevClass'])) ? " class='".$ajaxVals['prevClass']."' " : "" ;
    							$ajaxPrev.= (isset($ajaxVals['prevID'])) ? " id='". $ajaxVals['prevID']."' " : "";
    							$ajaxPrev.=">";
    							$ajaxPrev.= (isset($ajaxVals['prevDisplay'])) ? $ajaxVals['prevDisplay'] : "<Previous";
    							$ajaxPrev.="</a>";
    							
    						} else {
    							if ($paginateAlwaysShowLinks) {
    								$ajaxPrev = "<span class='".$ajaxVals['prevID']."'>";
    								$ajaxPrev.= (isset($ajaxVals['prevDisplay'])) ? $ajaxVals['prevDisplay'] : "&Previous";
    								$ajaxPrev.= "</span>";
    							} else {
    								$ajaxPrev = "<span id='".$ajaxVals['prevID']."'>";
    								$ajaxPrev.= (isset($ajaxVals['noPrev'])) ? $ajaxVals['noPrev'] : "";
    								$ajaxPrev.="</span>";
    							}
    						}
    						
    						if ($next < $total) {
    							$ajaxNextArgs = $orderedArgs;
    							$ajaxNextArgs[] = $next;
    							$ajaxNext = $ajaxLink."(".implode(",",$ajaxNextArgs)."); return false;\"";
    							$ajaxNext.= (isset($ajaxVals['nextClass'])) ? " class='".$ajaxVals['nextClass']."' " : "" ;
    							$ajaxNext.= (isset($ajaxVals['nextID'])) ? " id='". $ajaxVals['nextID']."' " : "";
    							$ajaxNext.=">";
    							$ajaxNext.= (isset($ajaxVals['nextDisplay'])) ? $ajaxVals['nextDisplay'] : "Next>";
    							$ajaxNext.="</a>";
    						} else {
    							if ($paginateAlwaysShowLinks) {
    								$ajaxNext = "<span class='ditto_off'>";
    								$ajaxNext.= (isset($ajaxVals['nextDisplay'])) ? $ajaxVals['nextDisplay'] : "Next>";
    								$ajaxNext.= "</span>";
    							} else {
    								$ajaxNext = (isset($ajaxVals['noNext'])) ? $ajaxVals['noNext'] : "";
    							}
    						}
    				}
    			}		
    																		
    			$modx->setPlaceholder('ajaxnext', $ajaxNext);
    			$modx->setPlaceholder('ajaxprev', $ajaxPrev);
    		
    			$ajaxNext = htmlentities($ajaxNext);
    			$ajaxPrev = htmlentities($ajaxPrev);																				
    			$modx->setPlaceholder('ajaxnextent', $ajaxNext);
    			$modx->setPlaceholder('ajaxprevent', $ajaxPrev);
    		}		
    		// ---------------------------------------------------
    		// End additional code to create AJAX-friendly navigation
    		// ---------------------------------------------------
    		
    		if ($start < $total)
    			$stop = $limten;
    		$this->start = $start;
    		$this->stop = $stop;
    
    


    Next, modify the Ditto code in the Snippets tab under Resources.

    PLEASE NOTE: Duplicate the Ditto snippet code and save it as a backup before you get started.

    OK, that said, I add this bit just before the RSS Parameters area. This gets Ditto to look for a new parameter in your Ditto call.

    // add a bit of code to support omnivore's ajax ditto abilities
    
    $ajaxData = (isset($ajaxData)) ? trim($ajaxData) : "" ;
    $paginationExtras['ajax'] = $ajaxData;
    
    // ---------------------------------------------------
    // RSS Parameters
    // ---------------------------------------------------
    


    With that done, you need to make another modification to the Ditto snippet code in the Resources>Snippets area. This is down in the area marked with a comment like this:

     // ---------------------------------------------------
      // Pagination
      // ---------------------------------------------------
    
    


    Replace the lines up to the next comment that follow with this:

     // ---------------------------------------------------
      // Pagination
      // ---------------------------------------------------
    
    	if ($paginate == 1 && $format != "rss") {
    		$ditto->paginate($start, $stop, $total, $summarize, $tplArchiveNext, $tplArchivePrevious, $paginateAlwaysShowLinks, $paginateSplitterCharacter, $paginationExtras );
    		$stop = $ditto->stop;
    		$start = $ditto->start;
    	}
    


    This just adds the $paginationExtras argument to the paginate call. We’re going to see what that looks like in a second. Briefly, though, $paginationExtras is refers to a Chunk that you will create to control the way the AJAX pagination links work.

    In the Resources>Chunks area, create a chunk called ’ajaxExtras’ with the following text:
    function:getExtras;
    event:onclick;
    prevClass:myPrevAjaxClass;
    prevID:myPrevAjaxID;
    prevDisplay:Later;
    noPrev:Showing Most Recent;
    nextClass:myNextAjaxClass;
    nextDisplay:Previous;
    nextID:myNextAjaxID;
    noNext:Showing Last;
    httpArgs:GET=tags;
    
    


    Buried in the code added at the beginning, the big chunk added to the ditto.class.inc.php file, there is a simple parser that will disassemble this into an array of values that it will use to control the AJAX pagination. First, a quick explanation of what this does.

    function:getExtras; This says that you want your ajax pagination links to call the javascript function ’getExtras’

    event:onclick; This says that the function above will be called on the ’onclick’ event

    prevClass:myPrevAjaxClass; This says that the link to previous items will have the class ’myPrevAjaxClass’

    prevID:myPrevAjaxID; This says that the link to previous items will have the id ’myPrevAjaxID’

    prevDisplay:Later; This says that the text that will appear for active previous links will read ’Later’

    noPrev:Showing Most Recent; This says that the text that will appear when no previous link is available will read ’Showing Most Recent’

    The next four items work the same way the corresponding 4 previous items did, but pertain to later items. The nomenclature here is set up for items sorted by date: but the point is you can change it to suit your needs.

    Before talking about the last item in the ajaxExtras chunk, a word about the format of the ajaxExtras chunk: The parser I am using is very simple: it uses formatting similar to that used in css: semicolons [;] are used to divide items from each other, and within items a colon [:] is used to divide the name of the item from its value, name on the left, value on the right. Obviously this means you can’t use colons or semicolons in your names or values -- for most uses this is not a limitation, but I invite anyone with a better idea to add it, and let me know.

    Now the last item.

    httpArgs:GET=tags; This is the way I use to declare what GET and POST values I need to include, and pass to my javascript. In this case, it’s simple: this says that the value of ’tag’, a GET value (one that might appear as www.mysite.com?tag=music) is to be added to the arguments passed to the javascript. In the case of the ajaxExtras chunk here, the ajax pagination will create a link like this:

    <a href="" onclick="javascript:getExtras('music',3); return false;" id="myPrevAjaxID" class="myPrevAjaxClass">Later</a>
    


    Most of this should be comprehensible, but there are a couple of things to notice. First, notice that the argument that we got from the GET variable ’tags’ ie the value ’music’ is in single quotes. All arguments captured using the httpArgs line in the ajaxExtras chunk are quoted, so when writing your javascript function, remember this.

    The other thing is the question, "where did that number three at the end of the getExtras function call come from? What that is is the start number that is passed to the Ditto snippet: it says which item of the items returned by ditto it should start displaying with. This will be the same value that a conventional Ditto pagination call produces as the ’start’ GET variable. Here, its included so that your javascript can include it as a start GET value when it initiates an httpRequest. Note that this value is not quoted, and that however many arguments are passed to the javascript, it will always be the last one.

    A bit more about httpArgs: you can capture multiple GET and POST values: just list them in order, separated by pipes, like so:

    httpArgs:get=tags|post=userage|post=city|get=lastpageviewed In this case, if your last request passed the GET tag value ’politics’, the GET lastpageviewed value ’67’, and you POSTed city=’toronto’ and userage=31, then the call we saw before would look like:

    <a href="" onclick="javascript:getExtras('politics','31','toronto','67',3); return false;" id="myPrevAjaxID" class="myPrevAjaxClass">Later</a>
    


    Notice that the order they appear in the arguments to getExtras is the order they appear in the httpArgs line.

    This is the only part of the ajaxExtras chunk where the order matters: you can list the various elements in any order, but GET and POST values are parsed in the order they appear within the line.

    OK, if you’re still following this, lets take a look at the Ditto snippet call:

    [[Ditto? &startID=`5` &sortBy=`pub_date` &truncLen=`80` &truncChars=`1` &summarize=`4` &displayArchive=`0` &tpl=`ditto_extra_text` &dateFormat=`%A, %B %d,%Y` &paginate=`1` &paginateAlwaysShowLinks=`0` &tagData=`tvcategories` &tagDelimiter=`,` &tagMode=`onlyTags` &ajaxData=`ajaxExtras`]]
    


    The addition is just the last item: &ajaxData. This points to ’ajaxExtras’. As you’ve probably realized, this is the chunk name, so this can have any value you need it to have, and you can have different configurations for different ditto calls, by referencing different chunks. ’ajaxExtras’ is not a magic name, in other words.

    Finally, how do you get this to output onto your page? This adds a small number of new replaceable elements in a page. They are:

    [+ajaxprev+], [+ajaxnext+], [+ajaxprevent+] and [+ajaxnextent+]

    These do the following:

    [+ajaxprev+] outputs the previous ajax link that calls the designated javascript function

    [+ajaxnext+] the same, but next ajax link

    [+ajaxprevent+] outputs the same as [+ajaxprev+], but as escaped htmlentities. This means that instead of outputting working code to your page, it will output a display of what is being created. I’ll talk about why in a moment.

    [+ajaxnextent+] as you’d expect, its the entities version of the [+ajaxnext+] insert.

    Working with AJAX can be frustrating, because when you update a page using httpRequest, the code you see in your browser’s ’view source’ menu option doesn’t reflect what you’ve downloaded: instead, it shows the page as it was originally when the server first provided it. In this situation, you can’t look at source to find out why your ajax link is not working as expected. Use the [+ajaxnextent+] and [+ajaxprevent+] inserts to see what’s going on, and make adjustments.

    For the simple AJAX replacements that I’m doing, I’m not using XML: I have modx create a chunk of html, usually a div with a bunch of stuff in it, that I then drop into the appropriate place in my page. But for those who want to, this will allow an XML approach as well.

    For a discussion of the business of using AJAX, you could do worse than to look at the "BrainJar.com: Getting Started with AJAX" page at http://www.brainjar.com/dhtml/ajax/default.asp .

    Good Luck, and I hope that this code is useful to someone.
      Web Designer
      PHP Programmer
      Cocoa Developer
      Boulevardier & Arriviste
      • 16885
      • 255 Posts
      Looks like a fabulous guide, thanks a lot. One thing I seemed to miss at the first sight is the javascript you’re using, do you have plans on releasing those as well?
        • 36624
        • 535 Posts
        is this always the best way to get ajax with ditto ?
          CTRL+SHIFT+U - Clear Cache
          CTRL+SHIFT+H - Hiding Heft Panel
          CTRL+SHIFT+N - Fast Create Resource
          CTRL+ALT+P - Preview Recource (in edit resorce window)
          CTRL+ALT+S - Save
          • 33968
          • 863 Posts
          Has anyone been using this snippet and found it to be safe to use on a live site?

          It looks like a great way to do ’facebook-style’ pagination, placing a ’more’ link at the bottom of an article/list/comment section in order to load the additional content into the same page. I’m going to try it out...

          *edit
          On second thoughts, it seems like this hack is not compatible with newer Ditto versions. Has anyone been able to achieve something similar?

          Cheers

          Luke