Skip to content
This repository
Browse code

ENHANCEMENT Declare a single DOM element with multiple Pjax fragments…

…. Unified ajax response handling in CMS, followup from submitForm() refactoring. Removed replacement of arbitrary CSS selectors through Pjax, relies on a fragment now (to keep logic consistent).
  • Loading branch information...
commit 19bfd01a603bb6f7bcae98a917e8c2490414820d 1 parent 1102bbd
Ingo Schommer authored May 30, 2012

Showing 1 changed file with 103 additions and 78 deletions. Show diff stats Hide diff stats

  1. 181  admin/javascript/LeftAndMain.js
181  admin/javascript/LeftAndMain.js
@@ -154,11 +154,10 @@ jQuery.noConflict();
154 154
 			loadPanel: function(url, title, data) {
155 155
 				if(!data) data = {};
156 156
 				if(!title) title = "";
157  
-				if(!data.selector) data.selector = '.cms-content';
158  
-				var contentEl = $(data.selector);
159  
-				
  157
+
160 158
 				// Check change tracking (can't use events as we need a way to cancel the current state change)
161  
-				var trackedEls = contentEl.find(':data(changetracker)').add(contentEl.filter(':data(changetracker)'));
  159
+				var contentEls = this._findFragments(data.pjax ? data.pjax.split(',') : ['Content']);
  160
+				var trackedEls = contentEls.find(':data(changetracker)').add(contentEls.filter(':data(changetracker)'));
162 161
 				
163 162
 				if(trackedEls.length) {
164 163
 					var abort = false;
@@ -299,12 +298,9 @@ jQuery.noConflict();
299 298
 
300 299
 				var self = this, h = window.History, state = h.getState(),
301 300
 					fragments = state.data.pjax || 'Content', headers = {},
302  
-					reduceFn = function(fragment) {return '[data-pjax-fragment="' + fragment + '"]';},
303  
-					contentEls = $($.map(fragments.split(','), reduceFn).join(','));
  301
+					contentEls = this._findFragments(fragments.split(','));
304 302
 				
305  
-				this.trigger('beforestatechange', {
306  
-					state: state, element: contentEls
307  
-				});
  303
+				this.trigger('beforestatechange', {state: state, element: contentEls});
308 304
 
309 305
 				// Set Pjax headers, which can declare a preference for the returned view.
310 306
 				// The actually returned view isn't always decided upon when the request
@@ -315,73 +311,13 @@ jQuery.noConflict();
315 311
 				var xhr = $.ajax({
316 312
 					headers: headers,
317 313
 					url: state.url,
318  
-					success: function(data, status, xhr) {
319  
-						// Pseudo-redirects via X-ControllerURL might return empty data, in which
320  
-						// case we'll ignore the response
321  
-						if(!data) return;
322  
-
323  
-						// Update title
324  
-						var title = xhr.getResponseHeader('X-Title');
325  
-						if(title) document.title = title;
326  
-
  314
+					complete: function() {
327 315
 						// Remove loading indication from old content els (regardless of which are replaced)
328 316
 						contentEls.removeClass('loading');
329  
-
330  
-						var newFragments = {};
331  
-						if(xhr.getResponseHeader('Content-Type') == 'text/json') {
332  
-							newFragments = data;
333  
-						} else {
334  
-							// Fall back to replacing the first fragment only if HTML is returned
335  
-							newFragments[fragments.split(',').pop()] = data;
336  
-						}
337  
-
338  
-						$.each(newFragments, function(newFragment, html) {
339  
-							var contentEl = $('[data-pjax-fragment=' + newFragment + ']'), newContentEl = $(html);
340  
-							
341  
-							// Update panels
342  
-							if(newContentEl.find('.cms-container').length) {
343  
-								throw 'Content loaded via ajax is not allowed to contain tags matching the ".cms-container" selector to avoid infinite loops';
344  
-							}
345  
-							
346  
-							// Set loading state and store element state
347  
-							newContentEl.addClass('loading');
348  
-							var origStyle = contentEl.attr('style');
349  
-							var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
350  
-							var elemClasses = contentEl.attr('class');
351  
-							
352  
-							var origLayoutClasses = [];
353  
-							if(elemClasses) {
354  
-								origLayoutClasses = $.grep(
355  
-									elemClasses.split(' '),
356  
-									function(val) { return ($.inArray(val, layoutClasses) >= 0);}
357  
-								);
358  
-							}
359  
-							
360  
-							newContentEl
361  
-								.removeClass(layoutClasses.join(' '))
362  
-								.addClass(origLayoutClasses.join(' '));
363  
-							if(origStyle) newContentEl.attr('style', origStyle);
364  
-							newContentEl.css('visibility', 'hidden');
365  
-
366  
-							// Allow injection of inline styles, as they're not allowed in the document body.
367  
-							// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
368  
-							var styles = newContentEl.find('style').detach();
369  
-							if(styles.length) $(document).find('head').append(styles);
370  
-
371  
-							// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
372  
-							contentEl.replaceWith(newContentEl);
373  
-
374  
-							// Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
375  
-							self.redraw();
376  
-							newContentEl.css('visibility', 'visible');
377  
-							newContentEl.removeClass('loading');
378  
-						});
379  
-						
380  
-						self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: newContentEl});
381 317
 					},
382  
-					error: function(xhr, status, e) {
383  
-						contentEl.removeClass('loading');
384  
-						errorMessage(e);
  318
+					success: function(data, status, xhr) {
  319
+						var els = self.handleAjaxResponse(data, status, xhr);
  320
+						self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: els});
385 321
 					}
386 322
 				});
387 323
 				
@@ -389,6 +325,98 @@ jQuery.noConflict();
389 325
 			},
390 326
 
391 327
 			/**
  328
+			 * Handles ajax responses containing plain HTML, or mulitple
  329
+			 * PJAX fragments wrapped in JSON (see PjaxResponseNegotiator PHP class).
  330
+			 * Can be hooked into an ajax 'success' callback.
  331
+			 */
  332
+			handleAjaxResponse: function(data, status, xhr) {
  333
+				var self = this;
  334
+
  335
+				// Pseudo-redirects via X-ControllerURL might return empty data, in which
  336
+				// case we'll ignore the response
  337
+				if(!data) return;
  338
+
  339
+				// Update title
  340
+				var title = xhr.getResponseHeader('X-Title');
  341
+				if(title) document.title = title;
  342
+
  343
+				var newFragments = {}, newContentEls = $([]);
  344
+				if(xhr.getResponseHeader('Content-Type') == 'text/json') {
  345
+					newFragments = data;
  346
+				} else {
  347
+					// Fall back to replacing the content fragment if HTML is returned
  348
+					newFragments['Content'] = data;
  349
+				}
  350
+
  351
+				// Replace each fragment individually
  352
+				$.each(newFragments, function(newFragment, html) {
  353
+					var contentEl = $('[data-pjax-fragment]').filter(function() {
  354
+						return $.inArray(newFragment, $(this).data('pjaxFragment').split(' ')) != -1;
  355
+					}), newContentEl = $(html);
  356
+
  357
+					// Add to result collection
  358
+					newContentEls.add(newContentEl);
  359
+					
  360
+					// Update panels
  361
+					if(newContentEl.find('.cms-container').length) {
  362
+						throw 'Content loaded via ajax is not allowed to contain tags matching the ".cms-container" selector to avoid infinite loops';
  363
+					}
  364
+					
  365
+					// Set loading state and store element state
  366
+					var origStyle = contentEl.attr('style');
  367
+					var origVisible = contentEl.is(':visible');
  368
+					var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
  369
+					var elemClasses = contentEl.attr('class');
  370
+					var origLayoutClasses = [];
  371
+					if(elemClasses) {
  372
+						origLayoutClasses = $.grep(
  373
+							elemClasses.split(' '),
  374
+							function(val) { return ($.inArray(val, layoutClasses) >= 0);}
  375
+						);
  376
+					}
  377
+					
  378
+					newContentEl
  379
+						.removeClass(layoutClasses.join(' '))
  380
+						.addClass(origLayoutClasses.join(' '));
  381
+					if(origStyle) newContentEl.attr('style', origStyle);
  382
+					newContentEl.css('visibility', 'hidden');
  383
+
  384
+					// Allow injection of inline styles, as they're not allowed in the document body.
  385
+					// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
  386
+					var styles = newContentEl.find('style').detach();
  387
+					if(styles.length) $(document).find('head').append(styles);
  388
+
  389
+					// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
  390
+					contentEl.replaceWith(newContentEl);
  391
+
  392
+					// Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
  393
+					if(origVisible) newContentEl.css('visibility', 'visible');
  394
+				});
  395
+
  396
+				this.redraw();
  397
+
  398
+				return newContentEls;
  399
+			},
  400
+
  401
+			/**
  402
+			 * 
  403
+			 * 
  404
+			 * Parameters: 
  405
+			 * - fragments {Array}
  406
+			 * Returns: jQuery collection
  407
+			 */
  408
+			_findFragments: function(fragments) {
  409
+				return $('[data-pjax-fragment]').filter(function() {
  410
+					// Allows for more than one fragment per node
  411
+					var i, nodeFragments = $(this).data('pjaxFragment').split(' ');
  412
+					for(i in fragments) {
  413
+						if($.inArray(fragments[i], nodeFragments) != -1) return true;
  414
+					}
  415
+					return false;
  416
+				});
  417
+			},
  418
+
  419
+			/**
392 420
 			 * Function: refresh
393 421
 			 * 
394 422
 			 * Updates the container based on the current url
@@ -440,16 +468,13 @@ jQuery.noConflict();
440 468
 		 * as opposed to triggering a full page reload.
441 469
 		 * Little helper to avoid repetition, and make it easy to
442 470
 		 * "opt in" to panel loading, while by default links still exhibit their default behaviour.
443  
-		 * Same goes for breadcrumbs in the CMS.
  471
+		 * The PJAX target can be specified via a 'data-pjax-target' attribute.
444 472
 		 */
445 473
 		$('.cms .cms-panel-link').entwine({
446 474
 			onclick: function(e) {
447 475
 				var href = this.attr('href'), 
448 476
 					url = (href && !href.match(/^#/)) ? href : this.data('href'),
449  
-					data = {
450  
-						selector: this.data('targetPanel'),
451  
-						pjax: this.data('pjax')
452  
-					};
  477
+					data = {pjax: this.data('pjaxTarget')};
453 478
 
454 479
 				$('.cms-container').loadPanel(url, null, data);
455 480
 				e.preventDefault();

0 notes on commit 19bfd01

Please sign in to comment.
Something went wrong with that request. Please try again.