On March 26, 2019 we launched new MODX Forums. Please join us at the new MODX Community Forums.
Subscribe: RSS
  • This is now a Transport Package: Get it from Github

    This tutorial will remain to discuss/demonstrate the techniques.

    This plugin makes SEO AJAX a breeze by allowing you to submit AJAX Requests with URL path segments as an ordered unnamed set of parameters to any page on your site. Unused parameters are ignored. Example: The URL http.://sub.domain.tld/buy/ajax/12345/43/3 would submit the values 12345, 43 and 3 to the "buy" page in your site with little or no effort from you. If you change "ajax" to "full", it degrades to a full page template of your choosing.

    See also:



    Updated as of 8/20/2012

    • Split into 3 Plugins
    • Simplified Code Logic
    • Supports all of my Plugins now.
    • Added Graceful Degradation (via Template Switching)
    • Added System Settings for enhanced Compatibility, Configuration, Security and Performance



    Summary

    MODx is an extremely fast and easy platform to develop full-scale web applications, content providers, and even web services. While toying around with Revolution, I found that information on creating AJAX integration was limited. However, my experiece with plugins and php really nipped this one in the bud and made AJAX easier than I've ever seen in any other CMS/CMP, ever. This tutorial will show you how to make an AJAX-enabled backend, such that any Resource, whatsoever, may be AJAX-enabled. Best of all, the actual setup takes less than 30 minutes.

    Note: I am assured that this technique works in both Evolution and Revolution. It was designed for Revolution 2.2+.



    The Process

    This tutorial is made for both AJAX beginners and MODx experts. As a result, some sections may be skipped by some readers, though I don't recommend it. First, for beginners, I discuss AJAX, what it is and its requirements. Then, I delve right into how AJAX is implemented in MODx and the issues it presents. Each issue proposes the solution in a logical way. Next, we get to work by setting up the System Settings. After this, we add the Plugins. Then we create a sample Snippet to demonstrate parameter handling. All of this isn't any good without a way to view it, so we create a sample Resource. Then it is all about testing, testing, testing. Before we conclude, we address Compatibility.

    Ready or not, here we go!



    Intro to AJAX

    This section is targeted to beginners of AJAX using JS/PHP. An AJAX Request is a request to a web-site document (Resources in ModX), using JavaScript. In order to be dynamic, often times this will result in a direct link to a .php/.asp document. This means that for every AJAX component, you must have: a) a URL; and b) a way to render the content.

    Many AJAX Requests require different information based on parameters that must be passed. Often, developers try to use different methods (GET, POST, PUT, DELETE) depending on what they want to accomplish. To further complicate things, SEO-Friendly requests can be difficult to set up, no matter the platform. Search Engines will often avoid caching URLs that are not set in stone (i.e. http://domain.tld/path?parameters).

    To sum up, we need:


    • A URL to retrieve content
    • A way for that URL to receive parameters
    • The ability to process the parameters into dynamic content
    • A simple framework for making the URLs SEO-Friendly should we choose.

    AJAX in ModX Platform

    Currently, much of the AJAX in MODx occurs in the backend. While certain add-ons may allow for some dynamic requests, ultimately much of this is functionality is reserved for connectors. Connectors can be complex PHP classes and requires many steps to debug. We need a way to bypass having to write a Connector in a segregated environment.

    Additionally, aside from placing your PHP file in your Assets directory, PHP Files are not accessible via URL Path. Typical Resources are full HTML documents but have the URL that we desire. This means that running dynamic code can be a pain in the tuckus. Despite these limitations, there are a few easy techniques to overcome them. Each is extensible to allow for greater complexity and better functionality. If we play our cards right we might even improve security handling.


    • Partial HTML - Most use Templates as skeletons for pages. However, Templates only add the HTML or Content you choose. You may either a) create a new Template that does not have an HTML head or body element, but has P, DIV, OL, etc. or b) set every AJAX page to an empty Template (0). Either will allow you to send back only the HTML you would like when a request to that URL occurs. The first option is the best, though, because it allows TVs and MODx parsing. The latter will result in the document not being parsed.
    • Running PHP from a URL - This is actually quite simple. URLs translate to Resources (always). Resources run code via Snippets. Every Snippet call is PHP. Simply include the Snippet call in your Resource. MODx will parse the document accordingly.
    • Accessing Request Parameters - When a Resource points to a Snippet, the Snippet has access to a whole host of variables, including $_REQUEST, $_POST, $_GET, etc. As long as they haven't been changed by another snippet, all is good. Most AJAX is handled via $_GET or $_POST anyway.
    • Retrieving Dynamic Paths - Dynamic Paths don't exist in MODx. Every Resource has a temporarily static Path based on its position in the Resource Tree. However, if you use the Path to a Resource and then append the parameters as segments, we might have something. Since the result would be a URL with no page, this will always result in (404) OnPageNotFound System Event. System Events are handled via plugins!

    That covers about all of the issues, so now we just have to put it all together. The next section goes step by step into setting it all up. In order to process and test that it works, the tutorial builds a small demo for you.



    System Settings

    We have to adjust one already provided System Setting, and then add a couple more. The System Settings we will be using will make the Plugin considerate to other Plugins (particularly our own). Additionally, one in particular, will reduce processing considerably if using Plugins from my other tutorials.

    (Required) AJAX Path Segment

    This setting is the most important and the onGetRequestType Plugin uses this to determine if an AJAX Request was made. Anywhere you see "/ajax/", it is using this setting. If you change it, the segment will be "/something-else/".

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: alias_ajax_page
    • Type: Text
    • Name: Alias: AJAX Request
    • Description: The false URL path alias appended to a URL Request telling MODx that it is rendered as an AJAX Request. This allows any resource to be an AJAX Resource. AJAX URLs follow the form: (resource)/ajax_alias/(parameters)
    • Value: ajax

    Click Save and it is done.

    (Required) Degrade Path Segment

    This setting is the most 2nd important and the onGetRequestType Plugin uses this to determine if the request requires a full page. Anywhere you see "/full/", it is using this setting. If you change it, the segment will be "/something-else/".

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: alias_full_page
    • Type: Text
    • Name: Alias: Force Full Page
    • Description: The false URL path alias appended to a URL Request telling MODx that it should render a full page instead.
    • Value: full

    Click Save and it is done.

    (Required) Degrade To Flag

    This is used to bridge onGetRequestType to onDegradeGracefully. It is where the flag gets set. You should never have to change it.

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: key_degrade
    • Type: Text
    • Name: Degrade Placeholder
    • Description: The name of the key in the $_REQUEST global variable indicating that the template should be switched. Defaults to 'forceFull'
    • Value: forceFull

    Click Save and it is done.

    (Required) Degrade To Template

    This is where you bind the Template to the onDegradeGracefully Plugin. This will use whichever HTML Skeleton you wish. The default value is set to Base, but it really depends on what your multi-purpose default Template is. Note: It has come to my attention that certain versions of MODx do not get Templates in System Settings correctly. If the Plugin will not degrade, simply change this to Text and put in the ID of the Template.

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: id_degrade_to
    • Type: Template
    • Name: Template: Degrade To
    • Description: The Template to degrade gracefully to when a full page is required for an AJAX call.
    • Value: Base

    Click Save and it is done.

    (Required) Key for Corrected URL

    This is used by all of my Plugins to allow AJAX Requests to be Cross-Context or Template-Based. It ensures that all successive Plugins get the real URL and not the fake one we requested, so that they can behave correctly. You may change this at anytime, like if a hacker tries to hijack $_REQUEST.

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: key_url_request
    • Type: Text
    • Name: URL Placeholder
    • Description: The key of the requested URL in the $_REQUEST global variable.
    • Value: toURL

    Click Save and it is done.

    (Required) Key for Parameters

    This is used by Snippets and these Plugin only (so far). This allows other Plugins/Snippets to use the Parameters if they need to, without having to dig through or modify this code. Change this if you are hacked, or it conflicts with another Plugin.

    Go to System Settings in the MODx Manager. Click Create New Setting. Set the options as follows:

    • Key: url_params_name
    • Type: Text
    • Name: URL Parameters Placeholder
    • Description: The name of the array in the $_REQUEST global to store/retrieve URL Parameters. This is used by multiple plugins and snippets. While you can change this on a per context/user basis, do so with caution. This value defaults to 'urlParams'.
    • Value: urlParams

    Click Save and it is done.



    Building the Framework

    The Framework consists of 3 Plugins. Two are fired when a URL is not found in MODx under the OnPageNotFound event. This event is extremely handy because it allows you to intercept the 404 and submit a substitute instead. If a viable substitute is not found, it will default to your 404 page. This makes Custom Aliasing a dream. The last Plugin is fired whenever any page is loaded (even a non AJAX page). It simply looks for the flag to degrade, and if present, switches the Template to one of your choice. Let's get that code in there!

    Plugin: onGetRequestType

    As previously stated, all of our AJAX calls will always fire the OnPageNotFound event. This both: a) gives us something to target, and b) introduces a new problem. While we can handle the event, we don't want to automatically convert every failed request to an AJAX page. So, this Plugin reads the URL. If it finds one of our "fake" aliases, it separates the valid URL from the Parameters we passed. This is so that other Plugins that are URL dependent will be able to get the real URL instead of the broken link.

    Create a new Plugin. Let's name it onGetRequestType. Enter the following code into the plugin, then click Save:

    <?php
    // Get System Settings
        $ajaxAlias = $modx->getOption('alias_ajax_page', null, 'ajax');
        $fullAlias = $modx->getOption('alias_full_page', null, 'full');
        $argsName = $modx->getOption('url_params_name', null, 'urlParams');
        $keyDegrade = $modx->getOption('key_degrade', null, 'forceFull');
        $keyURL = $modx->getOption('key_url_request', null, 'toURL');
    
    // Get the Request URL
        $toURL = !empty($_REQUEST[$keyURL]) 
            ? $_REQUEST[$keyURL]
            : $_SERVER['REQUEST_URI'];
    // Remove GET Parameters
        $toURL = reset(explode('?', trim($toURL, '/')));
    // Find AJAX Alias and Separate from Parameters
        $pieces = explode($ajaxAlias, trim($toURL, '/') . '/');
        
    // Only proceed if Alias was found
        if (count($pieces) > 1)
        {//Flag as AJAX Alias
            $_REQUEST[$keyDegrade] = 'false';
        // Add Parameters to $_REQUEST global variable
            $_REQUEST[$argsName] = $pieces[1];
        // Set the path to search for Aliases
            $_REQUEST[$keyURL] = '/'. $pieces[0] . '/';
        }
        else
        {// Find FULL Alias and Separate from Parameters
            $pieces = explode('/'.$fullAlias.'/', trim($toURL, '/') . '/');
        // Only proceed if Alias was found
            if (count($pieces) > 1)
            {//Flag for Template Switch
                $_REQUEST[$keyDegrade] = 'true';
            // Add Parameters to $_REQUEST global variable
                $_REQUEST[$argsName] = $pieces[1];
            // Set the path to search for Aliases
                $_REQUEST[$keyURL] = '/'. $pieces[0] . '/';
            }
        }
    


    Go to the System Events tab. Scroll down and check 'OnPageNotFound'. Be sure to set a priority of 3 (or higher). Note: If you use my other Plugins, see OnCompatibility near the end of the tutorial.

    Impromptu Test: You'll notice that it shouldn't affect anything that isn't valid. Type a false URL to your site and your 404 Page should pop up correctly.

    Plugin: onParseURLParams

    Knowing we have an AJAX call is no good, unless we can accept parameters. This Plugin gets the segregated Parameters from the previous Plugin and places them into the $_REQUEST array, so they can be used.

    Create a new Plugin. Let's name it onParseURLParams. Enter the following code into the plugin, then click Save:

    <?php
    //Get the System Settings (if we haven't already...)
        $argsName = !empty($argsName) ? $argsName : $modx->getOption('url_params_name', null, 'urlParams');
        
    // Only proceed if a previous plugin has set the URL Params
        if (!empty($_REQUEST[$argsName]))
        {//Split the list of Parameters
            $list = explode('/', trim($_REQUEST[$argsName], '/'));
        // Reset the REQUEST variable
            $_REQUEST[$argsname] = array();
        // Add each of the Params to the array
            $i = 0;
            foreach ($list as $key => $value)
            {   $_REQUEST[$argsName][$i] = strval($value);
                $i++;
            }
        }
    


    Go to the System Events tab. Scroll down and check 'OnPageNotFound'. Be sure to set a priority of 4 (or higher). This will ensure that it fires after onGetRequestType. Note: If you use my other Plugins, see OnCompatibility near the end of the tutorial.

    Plugin: onDegradeGracefully

    Exactly what it says. If a link is sent for a full page request, it returns the full page by switching the Template (temporarily).

    Create a new Plugin. Let's name it onDegradeGracefully. Enter the following code into the plugin, then click Save:

    <?php
    //Get the System Settings (if we haven't already...)
        $keyDegrade = !empty($keyDegrade) ? $keyDegrade : $modx->getOption('key_degrade', null, 'forceFull');
        $idDegradeTo = !empty($idDegradeTo) ? $idDegradeTo : $modx->getOption('id_degrade_to', null, 1);
    
    // Determine if we were told to switch
        $switch = $_REQUEST[$keyDegrade];
        if ($switch == 'true')
        {
            $modx->resource->set('template', $idDegradeTo);
        }
    


    Go to the System Events tab. Scroll down and check 'OnLoadWebDocument'. Be sure to set a priority of 1. This will ensure that it fires before any other Plugin of the same event.



    Using the Framework

    This is where life gets easy. The Framework is straight-forward. If your AJAX document doesn't have a Template, it will return whatever the Content is of the Type. If it does have a Tempate, it will parse it. If you need access to the parameters, you may access them in any Snippet using the following code:

    Getting a URL Parameter

    <?php
    //Get the System Setting (needed only once per Resource)
        $argsName = !empty($argsName) ? $argsName : $modx->getOption('url_params_name', null, 'urlParams');
    
    // Get an ordered Parameter 
        $varName = $_REQUEST[$argsname][#]; // <-- Replace # with the segment index. 
        // For example: The first Parameter would be 0; the second is 1, etc.
    


    Calling the Snippet

    Always use uncached calls. This makes sure the Content is dynamic. For compatibility, add the ? at the end, even if you don't set any properties

        [[!mySnippetName? ]]
    


    It's really that simple!! Let's see it in action!



    Testing

    Setup: Create an AJAX Snippet

    Name: testAJAXRequest:
    <?php
    //Get the System Setting (needed only once per Resource)
        $argsName = !empty($argsName) ? $argsName : $modx->getOption('url_params_name', null, 'urlParams');
    
    // Get an ordered Parameter 
        if (!empty($_REQUEST[$argsname][0]))
            $value = $_REQUEST[$argsname][0]; // <-- Replace # with the segment index. 
        else
            $value = 'Nothing sent';
    
        $output = '<p>' . $value . '</p>';
    
        return $output;
    


    Setup: Create a Template

    Create a Template for processing Snippets. Name it "Partial HTML". The Content will have one line of code:

        [[*content]]
    


    Setup: Create a Resource

    Create a Resource anywhere on your site. Set the Template to the Partial HTML. Set your Alias to anything that is not 'ajax'. Also, set it to a Container. This makes sense as it a group of documents as in it provides different content based on the params. The Content will have one line of code:

        [[!testAJAXRequest]]
    


    Test One: Viewing the Resource

    Right-click on the Resource and choose 'View Resource'. This will, of course, fail and will give you a cute little message. But you have the direct URL to it.

    Test One: Viewing the Resource

    Go to the Address Bar in the Browser. Simply add '/ajax/' and anything else to the end of the current URL. The result should look similar to: http://domain.tld/path_to_resource/ajax/14. Press Enter.

    It should now run the snippet and produce your results. Voila! You have the basics of URL Parameterized AJAX Requests and any of your pages are as AJAX-Enabled as you want them to be!



    Some Notes on Dynamic URLs and AJAX

    First, never-ever-ever use these URLS with unvalidated user input, such as an SQL Query. These are for simple requests that are easily referenceable (like a parent or template id, etc). The basic idea is this: URL Parameters are there for simple pieces that aren't going to change often, like a Resource ID. They are not there for additional arguments that are based on form data. URLs are not secure. Always re-validate using your Snippet.

    You may still utilize GET, POST, PUT and DELETE data. Do so for anything that is either large or unvalidated strings. JSON is great for handling large amounts of data. MODx will still handle ACLs for the Resource Groups. But, this means that you may have to handle security for more sensitive data (checking User permissions, context, etc.). This is also done in your Snippet, if needed.



    On Compatibility

    Supports

    • any Resource by adding the alias at the end of the URL.
    • any Content Type
    • numbers, letters, words, file names, hyphens
    • does not url_decode. You must do this yourself.

    Plugins that use OnPageNotFound (all)

    Other Plugins that use OnPageNotFound should have a priority of 5 (or greater), depending on the Plugin. This allows other plugins to use the Settings and variables set by the Plugins, including the corrected URL and the Paramters.

    This is compatible with Articles when using Template-Based Actions (link in my signature). [ed. note: fuzzicallogic last edited this post 8 years, 2 months ago.]
      Website: Extended Dialog Development Blog: on Extended Dialog
      Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
      Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

      Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
    • Very nice! This takes the simple idea of using a snippet in a resource with a blank template to a new level.

      And since the plugin is only invoked on the "not found" event, it won't add any overhead to the rest of the site's ordinary processing.
        Studying MODX in the desert - http://sottwell.com
        Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
        Join the Slack Community - http://modx.org
      • Susan,

        Thanks! This is all going to be incorporated in the RO.IDEs Addon I am developing, actually. But I figured it would be good to get this out for those who aren't interested in the RO.IDEs project.
          Website: Extended Dialog Development Blog: on Extended Dialog
          Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
          Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

          Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
        • Explanation of Code (Pending)
            Website: Extended Dialog Development Blog: on Extended Dialog
            Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
            Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

            Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
          • The Tutorial has been updated and is in line with all of my other tutorials.

            Updates include:

            • Splitting of Code.
            • Performance Tweaks
            • Graceful Degradation in Back-End.
            • Changes to Parameterization
            • Compatibility Issues
            • System Settings instead of hard-coded values - for Security, Compatibility, Performance, and Custom Configuration



            • Link to top: here
            • Link to Code Explanation: here
            [ed. note: fuzzicallogic last edited this post 8 years, 2 months ago.]
              Website: Extended Dialog Development Blog: on Extended Dialog
              Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
              Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

              Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
            • My first transport package is done and this tutorial has been submitted as an add-on/extra! It's called AJAX Revolution and I'm hoping it will be accepted soon.
                Website: Extended Dialog Development Blog: on Extended Dialog
                Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
                Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

                Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
              • This is now a Transport Package: Get it from Github

                This tutorial will remain to discuss/demonstrate the techniques.
                  Website: Extended Dialog Development Blog: on Extended Dialog
                  Add-ons: AJAX Revolution, RO.IDEs Editor & Framework (in works) Utilities: Plugin Compatibility List
                  Tutorials: Create Cross-Context Resources, Cross-Context AJAX Login, Template-Based Actions, Remove Extensions from URLs

                  Failure is just another word for saying you didn't want to try. "It can't be done" means "I don't know how".
                • This link to Github is dead... :-(
                  • @ottogal This link works for me: https://github.com/nTouchSoftwareLLC/AJAX-Revolution