diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php
index d3548e44c..f0094cec3 100755
--- a/admin/code/LeftAndMain.php
+++ b/admin/code/LeftAndMain.php
@@ -69,6 +69,7 @@ class LeftAndMain extends Controller {
*/
static $allowed_actions = array(
'index',
+ 'save',
'savetreenode',
'getitem',
'getsubtree',
@@ -241,6 +242,9 @@ class LeftAndMain extends Controller {
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js');
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js');
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js');
+ Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js');
+ Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js');
+ Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js');
Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-cookie/jquery.cookie.js');
@@ -300,6 +304,9 @@ class LeftAndMain extends Controller {
SAPPHIRE_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js',
SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js',
SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js',
+ SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js',
+ SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js',
+ SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js',
THIRDPARTY_DIR . '/jstree/jquery.jstree.js',
SAPPHIRE_ADMIN_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js',
SAPPHIRE_DIR . '/javascript/TreeDropdownField.js',
@@ -333,6 +340,16 @@ class LeftAndMain extends Controller {
// TableListField.ss or Form.ss.
SSViewer::set_theme(null);
}
+
+ function handleRequest($request) {
+ $response = parent::handleRequest($request);
+ $response->addHeader('X-Controller', $this->class);
+ return $response;
+ }
+
+ function index($request) {
+ return ($this->isAjax()) ? $this->show($request) : $this->getViewer('index')->process($this);
+ }
/**
diff --git a/admin/javascript/LeftAndMain.Content.js b/admin/javascript/LeftAndMain.Content.js
index 896eda0af..75c0e5095 100644
--- a/admin/javascript/LeftAndMain.Content.js
+++ b/admin/javascript/LeftAndMain.Content.js
@@ -20,11 +20,7 @@
var url = $(node).find('a:first').attr('href');
if(url && url != '#') {
- var xmlhttp = self.loadForm(
- url,
- null,
- function(response) {}
- );
+ window.History.pushState({}, '', url);
} else {
self.removeForm();
}
@@ -33,11 +29,20 @@
this._super();
},
+ onunmatch: function() {
+ this._super();
+ },
+
beforeLoad: function(url) {
this.addClass('loading');
this.cleanup();
},
+ afterLoad: function(data, status, xhr) {
+ this.removeClass('loading');
+ this.replaceWith(data);
+ },
+
cleanup: function() {
this.empty();
},
diff --git a/admin/javascript/LeftAndMain.EditForm.js b/admin/javascript/LeftAndMain.EditForm.js
index eac77c808..6c32940aa 100755
--- a/admin/javascript/LeftAndMain.EditForm.js
+++ b/admin/javascript/LeftAndMain.EditForm.js
@@ -79,6 +79,13 @@
$(this).remove();
});
+ // Remove all TinyMCE instances
+ if((typeof tinymce != 'undefined') && tinymce.editors) {
+ $(tinymce.editors).each(function() {
+ if(typeof(this.remove) == 'function') this.remove();
+ });
+ }
+
this._super();
},
@@ -150,20 +157,6 @@
this.trigger('validate', {isValid: isValid});
return isValid;
- },
-
- /**
- * Function: cleanup
- *
- * Remove all the currently active TinyMCE editors.
- * Note: Everything that calls this externally has an inappropriate coupling to TinyMCE.
- */
- cleanup: function() {
- if((typeof tinymce != 'undefined') && tinymce.editors) {
- $(tinymce.editors).each(function() {
- if(typeof(this.remove) == 'function') this.remove();
- });
- }
}
});
diff --git a/admin/javascript/LeftAndMain.Menu.js b/admin/javascript/LeftAndMain.Menu.js
index e7c09a287..25186a22a 100644
--- a/admin/javascript/LeftAndMain.Menu.js
+++ b/admin/javascript/LeftAndMain.Menu.js
@@ -25,11 +25,18 @@
*/
$('.cms-menu-list').entwine({
onmatch: function() {
+ var self = this;
+
// TODO Fix icon etc.
// this.children('li').each(function() {
// $(this).find('a:first').append('o');
// });
+ $('.LeftAndMain').bind('afterstatechange', function(e, data) {
+ var controller = data.xhr.getResponseHeader('X-Controller');
+ if(controller) self.find('li#Menu-' + controller).select();
+ });
+
// Sync collapsed state with parent panel
this.parents('.cms-panel:first').bind('toggle', function(e) {
self.toggleClass('collapsed', $(this).hasClass('collapsed'));
@@ -92,6 +99,8 @@
if(children.length) {
children.first().find('a').click();
} else {
+ // Active menu item is set based on X-Controller ajax header,
+ // which matches one class on the menu
window.History.pushState({}, '', this.attr('href'));
}
}
diff --git a/admin/javascript/LeftAndMain.Preview.js b/admin/javascript/LeftAndMain.Preview.js
index d4423c8d3..24eeb7790 100755
--- a/admin/javascript/LeftAndMain.Preview.js
+++ b/admin/javascript/LeftAndMain.Preview.js
@@ -36,6 +36,12 @@
var url = $(this).find(':input[name=StageURLSegment]').val();
if(url) self.loadUrl(url + '&cms-preview-disabled=1');
});
+
+ $('.cms-container').bind('afterstatechange', function(e) {
+ // var url = ui.xmlhttp.getResponseHeader('x-frontend-url');
+ var url = $('.cms-edit-form').find(':input[name=StageURLSegment]').val();
+ if(url) self.loadUrl(url + '&cms-preview-disabled=1');
+ });
if(this.hasClass('is-expanded')) this.expand();
else this.collapse();
@@ -51,12 +57,14 @@
var doc = this.find('iframe')[0].contentDocument, container = this.getLayoutContainer();
// Only load if we're in the "edit page" view
- if(!container.hasClass('CMSMain')) return;
+ if(!container.hasClass('CMSMain') || container.hasClass('CMSPagesController')) return;
// Load this page in the admin interface if appropriate
var id = $(doc).find('meta[name=x-page-id]').attr('content'), contentPanel = $('.cms-content');
// TODO Remove hardcoding
- if(id && contentPanel.find(':input[name=ID]').val() != id) contentPanel.loadPanel('admin/page/edit/show/' + id);
+ if(id && contentPanel.find(':input[name=ID]').val() != id) {
+ window.History.pushState({}, '', 'admin/page/edit/show/' + id);
+ }
},
_fixIframeLinks: function() {
@@ -69,7 +77,7 @@
if (href && href.match(/^http:\/\//)) {
links[i].setAttribute('href', 'javascript:false');
} else {
- links[i].setAttribute('href', href + '?cms-preview=1');
+ links[i].setAttribute('href', href + '?cms-preview-disabled=1');
}
}
},
diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js
index 794a46bdf..5da775475 100755
--- a/admin/javascript/LeftAndMain.js
+++ b/admin/javascript/LeftAndMain.js
@@ -41,13 +41,15 @@
* loadnewpage - ...
*/
$('.LeftAndMain').entwine({
-
+
+ CurrentXHR: null,
+
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this;
-
+
// Browser detection
if($.browser.msie && parseInt($.browser.version, 10) < 7) {
$('.ss-loading-screen').append(
@@ -68,6 +70,10 @@
$(window).unbind('resize', positionLoadingSpinner);
$('.cms-edit-form').live('loadnewpage', function() {self.redraw()});
+
+ History.Adapter.bind(window,'statechange',function(){
+ self.handleStateChange();
+ });
this._super();
},
@@ -77,6 +83,53 @@
var editForm = this.find('.cms-edit-form[data-layout]').layout();
this.find('.cms-content').layout();
this.find('.cms-container').layout({resize: false})
+ },
+
+ /**
+ * Handles ajax loading of new panels through the window.History object.
+ * To trigger loading, pass a new URL to window.History.pushState().
+ *
+ * Due to the nature of history management, no callbacks are allowed.
+ * Use the 'beforestatechange' and 'afterstatechange' events instead,
+ * or overwrite the beforeLoad() and afterLoad() methods on the
+ * DOM element you're loading the new content into.
+ * Although you can pass data into pushState(), it shouldn't contain
+ * DOM elements or callback closures.
+ *
+ * The passed URL should allow reconstructing important interface state
+ * without additional parameters, in the following use cases:
+ * - Explicit loading through History.pushState()
+ * - Implicit loading through browser navigation event triggered by the user (forward or back)
+ * - Full window refresh without ajax
+ * For example, a ModelAdmin search event should contain the search terms
+ * as URL parameters, and the result display should automatically appear
+ * if the URL is loaded without ajax.
+ *
+ * Alternatively, you can load new content via $('.cms-content').loadForm().
+ * In this case, the action won't be recorded in the browser history.
+ */
+ handleStateChange: function() {
+ var self = this, h = window.History, state = h.getState();
+
+ // Don't allow parallel loading to avoid edge cases
+ if(this.getCurrentXHR()) this.getCurrentXHR().abort();
+
+ // TODO Support loading into multiple panels
+ var contentEl = $(state.data.selector || '.LeftAndMain .cms-content');
+ this.trigger('beforestatechange', {state: state});
+ contentEl.beforeLoad(state.url);
+
+ var xhr = $.ajax({
+ url: state.url,
+ success: function(data, status, xhr) {
+ // Update panels
+ contentEl.afterLoad(data, status, xhr);
+ self.redraw();
+
+ self.trigger('afterstatechange', {data: data, status: status, xhr: xhr});
+ }
+ });
+ this.setCurrentXHR(xhr);
}
});
diff --git a/admin/javascript/ModelAdmin.History.js b/admin/javascript/ModelAdmin.History.js
index ef249c6a1..177231b72 100755
--- a/admin/javascript/ModelAdmin.History.js
+++ b/admin/javascript/ModelAdmin.History.js
@@ -66,6 +66,8 @@
redraw: function() {
this.find('.historyNav .forward').toggle(Boolean(this.getFuture().length > 0));
this.find('.historyNav .back').toggle(Boolean(this.getHistory().length > 1));
+
+ this._super();
},
/**
@@ -116,7 +118,7 @@
this.trigger('historyGoBack', {url:previousPage});
// load new location
- $('.cms-edit-form').loadForm(previousPage);
+ $('.cms-content').loadForm(previousPage);
this.redraw();
}
@@ -136,7 +138,7 @@
this.trigger('historyGoForward', {url:nextPage});
// load new location
- $('.cms-edit-form').loadForm(nextPage);
+ $('.cms-content').loadForm(nextPage);
this.redraw();
}