Skip to main content
Home  ›  Blog

3 Steps to AJAX-Enable DNN Apps, Modules and SPA (200-400)

DNN has been plagued with slow editing experience since DNN 3 - mostly because of the WebForms Postback system. 2sxc has fixed most of this by now, getting all UI actions to flow in less than 1 second; and Content-updates all reload immediately using AJAX. But for most DNN modules and 2sxc-Apps this is more complex but easy. I've lost many sleepless nights getting this to work, and hope that I can help you by sharing this.

The Challenges of Complex-Content (Modules/App)-AJAX

You may be surprised that DNN doesn't do AJAX and that 2sxc only supported AJAX for Content but not for Apps until now. This was because content usually has a simple content+template mechanism. It rarely needs JavaScript - and that's where the Apps are usually different. Apps very often have JavaScript when initializing, and that requires more control. Note that this can also be the case for more advanced content-templates, like to align the heights of items in a row. So here are the core challenges:

  1. Loading Mechanisms
    1. The ones in charge when your module or App is added to the page
    2. The ones in charge of updating the screen when the editor makes changes to content or configuration
  2. JavaScript Resources
    1. Loading JS resources when the app is added normally
    2. Loading javascript resources when the app is added by ajax
    3. Loading Javascript resources when re-loading the app by ajax (so maybe it was already in the page)
    4. Preventing problems with multiple loading of JS resources (like if other modules also had the resources loaded)
  3. JavaScript Initializations
    1. Starting the JS initializer, because document.ondocumentready already happened a while before
    2. Preventing previously initialized items to not double-initialize and do something unexpected

Step 1: Loading Mechanism

The loading mechanism is possibly the most difficult, because there are so many strategies you can use. Basically you need something around your module/app/view/template, which handles the AJAX aspects. We'll look at these cases

  1. Dynamic Content (templated dynamic content-items or content-lists using 2sxc & Razor/Tokens)
  2. Apps (bundles of data / views / logic / web-api in 2sxc using Razor / Tokens)
  3. SPA Apps (JS based Single Page Applications in 2sxc using any JS framework like AngularJS)
  4. DNN SPA Modules (only in DNN 8+)
  5. DNN Modules (mostly WebForms based, possibly with MVC)

1.1 Standard Dynamic Content AJAX

This is the freebie. Because 2sxc since version 7.0 automatically loads all Dynamic Content views/templates using AJAX within fractions of a second.

1.2 Apps AJAX (Token or Razor Based)

This is added in 2sxc 8.4.4 and also a freebie. All you have to do is enable the AJAX switch in the App Configuration and you're good to go (see image).

Note that this setting is disabled by default because many apps need some optimizations / testing. So we wanted to be sure that the stable behavior is the default. 

1.3 SPA Apps AJAX

For standard SPA Apps there are various possibilities. The trivial way is to just enable the AJAX like for Apps. If you SPA was any good to start with, it will automatically support this use case as well, since any good SPA is meant to load properly and initialize the correct state based or URL-Routing.

But there are two cases where you may want something else:

  1. If you want to get your SPA to perform AJAX by itself on content-changes, and not just reload the entire app, then your SPA must register itself in 2sxc to handle the AJAX. This is not yet standardized but will be implemented soon.
  2. If your SPA doesn't behave correctly because it doesn't place its state in the URL then the AJAX-reload will often reload a view which isn't the one you expected. The basic recommendation is to fix your SPA to track its state in the URL - as you should for any modern SPA because of SEO and more. If you don't want to do this, then you too should register in 2sxc to handle the AJAX reloads yourself - which will be added soon.

1.4 DNN 8+ SPA Modules (AngularJS, etc.)

This is fairly easy, as you will usually provide JavaScript based dialogs to edit data, and will be able to provide this with callbacks which reload the data. The exact methods will vary a bit depending on your framework, but if you're already doing SPA, you won't need any additional help for this :). 

1.5 DNN Modules (WebForms / MVC)

This is a LOT of work - sorry :(. DNN itself simply doesn't provide much to get you going quickly. DNN offers some basics, but implementing it can easily take 2-10 days, depending on the scale of your module. Here's what you'll have to do:

  1. Start with a server side service (WebAPI) which delivers the content-parts you want to reload by AJAX. Depending on your solution you'll want to provide rendered HTML or JSON data (if you just want to load the data). You can imitate much what 2sxc does - the HTML-rendering is currently done in the [todo] while the module-level data is provided by the [todo]. Note that this may require a LOT of refactoring under the hood to get this to work reliably, and you'll have to invest some time with security issues. 
  2. Rework your editing / dialog cycle, as the default cycle opens DNN pages (containing server controls) which when complete, will re-load the page. You will probably want to:
    1. …switch to JS based dialogs (also a good thing to prepare for .net Core) - but you can skip this if it's too much work for now
    2. …open the dialogs in a way that doesn't call the page-reload but some of your own code, to then do the AJAX refresh
  3. Create an own JavaScript based refresh-API on the client to load your modules, views etc. This is fairly complex but you can copy much of what 2sxc does from [todo].
  4. Note that re-loading can be very complicated, so do spend some time with the 2sxc implementation. For example, there are cases where only some HTML is replaced, while in other cases everything incl. the module-state in the browser must be flushed

1.5+ Alternatives if you're using WebForms or MVC

  1. You're probably much faster to just convert your module to a SPA-App or a 2sxc App and ride the existing infrastructure. But in case you want to stick to WebForms (not supported by 2sxc) then this manual path is the way to go.
  2. There is also a secret trick to bypass everything using jQuery.load - which allows you to actually load the entire page but then only pick the parts you want and insert it into the existing DOM. It's fairly messy and not recommended, but could be a temporary option 
  3. There is another secret trick where you can load a page with only one module (the one you want to AJAX) and leave away the skin and everything - using an mid=[moduleid] parameter and a bunch of stuff, which looks a bit like this
    [your page]?mid=[your mid]&dnnprintmode=true&SkinSrc=%5bG%5dSkins%2f_default%2fNo+Skin&ContainerSrc=%5bG%5dContainers%2f_default%2fNo+Container
    this can be combined with jQuery.load to build a viable AJAX solution, but it's fairly hacky.

Step 2: JavaScript Resources must Behave

2.1 Getting JavaScript Resources to Load/Behave Correctly with AJAX

The main thing you should cover is that basically all JS things should check if they had already been initialized, and if yes, they should not initialize again. There are many libraries to do things like this, but the very basic aspect is to check if something exists, which the JS creates, and only run the code if this is missing. Something like this (wrapped in an IIFE as best practice):

Snippet to prevent duplicate initialization

(function() {
  if(window.myStuff) return;
  window.myStuff = {
    ...
  }
})();

2.2 Include the JavaScript Resources which are now AJAX-Ready

Now that the external JS will behave correctly, you can include them any way you want to - assuming that your system properly handles the various cases - as 2sxc does. The following recommendation therefor is for 2sxc Apps (and advanced 2sxc Content templates). You'll have to improvise a bit more when using DNN Modules because the AJAX loading isn't standardized.

For 2sxc Apps or advanced content templates, simply use the standard <script src="" > </script> tag. If you want to, you can also use automatic client dependency bundling and zipping, but we have been moving away from that, as it's been one of the biggest causes of support issues and is not really necessary any more if you have a good javascript-developing setup with gulp/grunt.

Step 3: Initialize the Scripts and DOM when using AJAX

In simple JS you can add a line like $(myInit) to your external JS and know that it will be executed once, after the DOM is ready. Not so with AJAX, because your code cannot just assume that it should load again. There are two easy ways to do it. The first method simply does two things

  1. Assume that the external file is added again whenever needed
  2. Always initialize itself again
  3. The code must have no side-effects when re-initialized, or leave previously initialized DOMs untouched

Snippet for this don't-init-twice strategy

(function() {
  if(window.myStuff)
    return; 

  window.myStuff = {
    init: function() {
      // init code with no side-effects
      // or which doesn't touch initialized DOM
    },
    // more code here
  };

  // now initialize
  window.myStuff.init();
})();

Alternative  Initialization Triggers

In some cases you will include the JS in the skin or some other way, so that the resources are not added multiple times. Then the strategy above won't be enough, because the trigger to "re-init" cannot be the loading of the external resource. In this case, your view/template must include a small please-re-init code, approx. like this at the end of every such template file:

Snippet to Re-Init the objects from a just-loaded template

<script>
	// on load, re-execute the init-method
	// this only works if the init was already properly created previously
	$(window.myStuff.init);         
</script>

<!-- alternative: re-call the window.load event with everything that had to be done then - could have side-effects 
-->
<script>$(window).trigger('load');</script>

tl;dr

I hope that this will help you improve your modules / apps to be sexy. Since 2sxc only supports full App-AJAX since 08.04.04 we're only gradually testing / activating existing apps, so as of now, only the new blog app, the QR-code app and image-compare have full AJAX support.

Sleepless programming wishes,
Daniel


Daniel Mettler grew up in the jungles of Indonesia and is founder and CEO of 2sic internet solutions in Switzerland and Liechtenstein, an 20-head web specialist with over 800 DNN projects since 1999. He is also chief architect of 2sxc (see github), an open source module for creating attractive content and DNN Apps.

Read more posts by Daniel Mettler