Skip to content

Commit

Permalink
API Provide a thin alternative to loadPanel/submitForm.
Browse files Browse the repository at this point in the history
This is needed in some situations when we only want to update a
small single component, sometimes even using a different controller to
the one implied in the URL.

An example here is reloading dynamically the subsite dropdown without
reloading the entire page, updating a filter sidebar or suchlike.
  • Loading branch information
mateusz committed Aug 22, 2013
1 parent 4af619e commit 1f8feb5
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 4 deletions.
84 changes: 80 additions & 4 deletions admin/javascript/LeftAndMain.js
Expand Up @@ -115,7 +115,15 @@ jQuery.noConflict();
*/
$('.cms-container').entwine({

CurrentXHR: null,
/**
* Tracks current panel request.
*/
StateChangeXHR: null,

/**
* Tracks current fragment-only parallel PJAX requests.
*/
FragmentXHR: {},

StateChangeCount: 0,

Expand Down Expand Up @@ -419,7 +427,7 @@ jQuery.noConflict();
*/
handleStateChange: function() {
// Don't allow parallel loading to avoid edge cases
if(this.getCurrentXHR()) this.getCurrentXHR().abort();
if(this.getStateChangeXHR()) this.getStateChangeXHR().abort();

var self = this, h = window.History, state = h.getState(),
fragments = state.data.pjax || 'Content', headers = {},
Expand Down Expand Up @@ -455,7 +463,7 @@ jQuery.noConflict();
headers: headers,
url: state.url,
complete: function() {
self.setCurrentXHR(null);
self.setStateChangeXHR(null);
// Remove loading indication from old content els (regardless of which are replaced)
contentEls.removeClass('loading');
},
Expand All @@ -465,7 +473,75 @@ jQuery.noConflict();
}
});

this.setCurrentXHR(xhr);
this.setStateChangeXHR(xhr);
},

/**
* ALternative to loadPanel/submitForm.
*
* Triggers a parallel-fetch of a PJAX fragment, which is a separate request to the
* state change requests. There could be any amount of these fetches going on in the background,
* and they don't register as a HTML5 history states.
*
* This is meant for updating a PJAX areas that are not complete panel/form reloads. These you'd
* normally do via submitForm or loadPanel which have a lot of automation built in.
*
* On receiving successful response, the framework will update the element tagged with appropriate
* data-pjax-fragment attribute (e.g. data-pjax-fragment="<pjax-fragment-name>"). Make sure this element
* is available.
*
* Example usage:
* $('.cms-container').loadFragment('admin/foobar/', 'FragmentName');
*
* @param url string Relative or absolute url of the controller.
* @param pjaxFragments string PJAX fragment(s), comma separated.
*/
loadFragment: function(url, pjaxFragments) {

var self = this,
xhr,
headers = {},
baseUrl = $('base').attr('href'),
fragmentXHR = this.getFragmentXHR();

// Make sure only one XHR for a specific fragment is currently in progress.
if(
typeof fragmentXHR[pjaxFragments]!=='undefined' &&
fragmentXHR[pjaxFragments]!==null
) {
fragmentXHR[pjaxFragments].abort();
fragmentXHR[pjaxFragments] = null;
}

url = $.path.isAbsoluteUrl(url) ? url : $.path.makeUrlAbsolute(url, baseUrl);
headers['X-Pjax'] = pjaxFragments;

xhr = $.ajax({
headers: headers,
url: url,
success: function(data, status, xhr) {
var elements = self.handleAjaxResponse(data, status, xhr, null);

// We are fully done now, make it possible for others to hook in here.
self.trigger('afterloadfragment', { data: data, status: status, xhr: xhr, elements: elements });
},
error: function(xhr, status, error) {
self.trigger('loadfragmenterror', { xhr: xhr, status: status, error: error });
},
complete: function() {
// Reset the current XHR in tracking object.
var fragmentXHR = self.getFragmentXHR();
if(
typeof fragmentXHR[pjaxFragments]!=='undefined' &&
fragmentXHR[pjaxFragments]!==null
) {
fragmentXHR[pjaxFragments] = null;
}
}
});

// Store the fragment request so we can abort later, should we get a duplicate request.
fragmentXHR[pjaxFragments] = xhr;
},

/**
Expand Down
41 changes: 41 additions & 0 deletions docs/en/reference/cms-architecture.md
Expand Up @@ -287,6 +287,47 @@ tracked in the browser history, use the `pjax` attribute on the state data.

$('.cms-container').loadPanel('admin/pages', null, {pjax: 'Content'});

## Loading lightweight PJAX fragments

Normal navigation between URLs in the admin section of the Framework occurs through `loadPanel` and `submitForm`.
These calls make sure the HTML5 history is updated correctly and back and forward buttons work. They also take
care of some automation, for example restoring currently selected tabs.

However there are situations when you would like to only update a small area in the CMS, and when this operation should
not trigger a browser's history pushState. A good example here is reloading a dropdown that relies on backend session
information that could have been updated as part of action elsewhere, updates to sidebar status, or other areas
unrelated to the main flow.

In this case you can use the `loadFragment` call supplied by `LeftAndMain.js`. You can trigger as many of these in
parallel as you want. This will not disturb the main navigation.

$('.cms-container').loadFragment('admin/foobar/', 'Fragment1');
$('.cms-container').loadFragment('admin/foobar/', 'Fragment2');
$('.cms-container').loadFragment('admin/foobar/', 'Fragment3');

The ongoing requests are tracked by the PJAX fragment name (Fragment1, 2, and 3 above) - resubmission will
result in the prior request for this fragment to be aborted. Other parallel requests will continue undisturbed.

You can also load multiple fragments in one request, as long as they are to the same controller (i.e. URL):

$('.cms-container').loadFragment('admin/foobar/', 'Fragment2,Fragment3');

This counts as a separate request type from the perspective of the request tracking, so will not abort the singular
`Fragment2` nor `Fragment3`.

Upon the receipt of the response, the fragment will be injected into DOM where a matching `data-pjax-fragment` attribute
has been found on an element (this element will get completely replaced). Afterwards a `afterloadfragment` event
will be triggered. In case of a request error a `loadfragmenterror` will be raised and DOM will not be touched.

You can hook up a response handler that obtains all the details of the XHR request like this:

'from .cms-container': {
onafterloadfragment: function(e, data) {
// Say 'success'!
alert(data.status);
}
}

## Ajax Redirects

Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,
Expand Down

0 comments on commit 1f8feb5

Please sign in to comment.