2011-03-23 10:51:00 +01:00
|
|
|
/**
|
|
|
|
* File: LeftAndMain.js
|
|
|
|
*/
|
|
|
|
(function($) {
|
2011-04-15 05:32:43 +02:00
|
|
|
$.metadata.setType('html5');
|
|
|
|
|
2011-12-17 04:06:56 +01:00
|
|
|
// setup jquery.entwine
|
|
|
|
$.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE;
|
|
|
|
$.entwine('ss', function($) {
|
2011-03-23 10:51:00 +01:00
|
|
|
/**
|
|
|
|
* Position the loading spinner animation below the ss logo
|
|
|
|
*/
|
|
|
|
var positionLoadingSpinner = function() {
|
|
|
|
var offset = 120; // offset from the ss logo
|
|
|
|
var spinner = $('.ss-loading-screen .loading-animation');
|
|
|
|
var top = ($(window).height() - spinner.height()) / 2;
|
|
|
|
spinner.css('top', top + offset);
|
|
|
|
spinner.show();
|
|
|
|
}
|
2011-12-17 00:51:32 +01:00
|
|
|
|
2011-03-23 10:51:00 +01:00
|
|
|
$(window).bind('resize', positionLoadingSpinner).trigger('resize');
|
2011-12-13 11:35:00 +01:00
|
|
|
|
2011-03-23 10:51:00 +01:00
|
|
|
// global ajax error handlers
|
|
|
|
$.ajaxSetup({
|
|
|
|
error: function(xmlhttp, status, error) {
|
|
|
|
var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
|
|
|
|
statusMessage(msg, 'bad');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main LeftAndMain interface with some control panel and an edit form.
|
|
|
|
*
|
|
|
|
* Events:
|
|
|
|
* ajaxsubmit - ...
|
|
|
|
* validate - ...
|
2011-08-22 06:44:41 +02:00
|
|
|
* reloadeditform - ...
|
2011-03-23 10:51:00 +01:00
|
|
|
*/
|
2011-07-05 14:34:31 +02:00
|
|
|
$('.cms-container').entwine({
|
2011-06-09 03:49:52 +02:00
|
|
|
|
|
|
|
CurrentXHR: null,
|
|
|
|
|
2011-03-23 10:51:00 +01:00
|
|
|
/**
|
|
|
|
* Constructor: onmatch
|
|
|
|
*/
|
|
|
|
onmatch: function() {
|
|
|
|
var self = this;
|
2011-06-09 03:49:52 +02:00
|
|
|
|
2011-04-24 02:31:11 +02:00
|
|
|
// Browser detection
|
|
|
|
if($.browser.msie && parseInt($.browser.version, 10) < 7) {
|
|
|
|
$('.ss-loading-screen').append(
|
2011-12-15 16:54:33 +01:00
|
|
|
'<p class="ss-loading-incompat-warning"><span class="notice">' +
|
|
|
|
'Your browser is not compatible with the CMS interface. Please use Internet Explorer 7+, Google Chrome 10+ or Mozilla Firefox 3.5+.' +
|
2011-04-24 02:31:11 +02:00
|
|
|
'</span></p>'
|
2011-12-15 16:54:33 +01:00
|
|
|
).css('z-index', $('.ss-loading-screen').css('z-index')+1);
|
|
|
|
$('.loading-animation').remove();
|
2011-04-24 02:31:11 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-07-21 18:39:26 +02:00
|
|
|
// Initialize layouts
|
2011-05-15 05:43:21 +02:00
|
|
|
this.redraw();
|
2011-07-21 20:14:33 +02:00
|
|
|
|
|
|
|
// Monitor window resizes, panel changes and edit form loads for layout changes.
|
|
|
|
// Also triggers redraw through handleStateChange()
|
2011-10-29 02:36:03 +02:00
|
|
|
$(window).resize(function() {
|
|
|
|
self.redraw();
|
|
|
|
});
|
|
|
|
|
|
|
|
$('.cms-panel').live('toggle', function() {
|
|
|
|
self.redraw();
|
|
|
|
});
|
|
|
|
|
2011-12-20 14:10:39 +01:00
|
|
|
$('.cms-edit-form').live('reloadeditform', function(e, data) {
|
|
|
|
// Simulates a redirect on an ajax response - just exchange the URL without re-requesting it
|
|
|
|
if(window.History.enabled) {
|
|
|
|
var url = data.xmlhttp.getResponseHeader('X-ControllerURL');
|
|
|
|
if(url) window.history.replaceState({}, '', url);
|
|
|
|
}
|
|
|
|
|
2011-10-29 02:36:03 +02:00
|
|
|
self.redraw()
|
|
|
|
});
|
2011-04-15 00:35:09 +02:00
|
|
|
|
|
|
|
// Remove loading screen
|
|
|
|
$('.ss-loading-screen').hide();
|
|
|
|
$('body').removeClass('loading');
|
|
|
|
$(window).unbind('resize', positionLoadingSpinner);
|
2011-06-09 03:49:52 +02:00
|
|
|
|
2011-12-15 12:16:54 +01:00
|
|
|
History.Adapter.bind(window,'statechange',function(){
|
2011-06-09 03:49:52 +02:00
|
|
|
self.handleStateChange();
|
|
|
|
});
|
2011-03-23 10:51:00 +01:00
|
|
|
|
|
|
|
this._super();
|
|
|
|
},
|
2011-05-13 03:30:49 +02:00
|
|
|
|
2011-05-15 05:43:21 +02:00
|
|
|
redraw: function() {
|
2011-07-21 18:39:26 +02:00
|
|
|
// Move from inner to outer layouts. Some of the elements might not exist.
|
2011-12-15 22:04:03 +01:00
|
|
|
// Not all edit forms are layouted, so qualify by their data value.
|
|
|
|
this.find('.cms-edit-form[data-layout]').redraw();
|
2011-07-21 18:39:26 +02:00
|
|
|
this.find('.cms-preview').redraw();
|
2011-12-15 22:04:03 +01:00
|
|
|
// Only redraw the content area if its not the same as the edit form
|
|
|
|
var contentEl = this.find('.cms-content');
|
|
|
|
if(!contentEl.is('.cms-edit-form')) contentEl.redraw();
|
2011-07-21 18:39:26 +02:00
|
|
|
|
|
|
|
this.layout({resize: false});
|
2011-10-29 02:36:03 +02:00
|
|
|
|
|
|
|
this.find('.cms-panel-layout').redraw(); // sidebar panels.
|
2011-06-09 03:49:52 +02:00
|
|
|
},
|
2011-12-15 12:16:54 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Proxy around History.pushState() which handles non-HTML5 fallbacks,
|
|
|
|
* as well as global change tracking. Change tracking needs to be synchronous rather than event/callback
|
|
|
|
* based because the user needs to be able to abort the action completely.
|
|
|
|
*
|
|
|
|
* See handleStateChange() for more details.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* - {String} url
|
|
|
|
* - {String} title New window title
|
|
|
|
* - {Object} data Any additional data passed through to History.pushState()
|
|
|
|
*/
|
|
|
|
loadPanel: function(url, title, data) {
|
2011-12-17 04:06:56 +01:00
|
|
|
var data = data || {};
|
|
|
|
var selector = data.selector || '.cms-content'
|
|
|
|
var contentEl = $(selector);
|
|
|
|
|
2011-12-15 12:23:32 +01:00
|
|
|
// Check change tracking (can't use events as we need a way to cancel the current state change)
|
|
|
|
var trackedEls = contentEl.find(':data(changetracker)').add(contentEl.filter(':data(changetracker)'));
|
2011-12-17 04:06:56 +01:00
|
|
|
|
2011-12-15 12:23:32 +01:00
|
|
|
if(trackedEls.length) {
|
|
|
|
var abort = false;
|
2011-12-17 04:06:56 +01:00
|
|
|
|
2011-12-15 12:23:32 +01:00
|
|
|
trackedEls.each(function() {
|
|
|
|
if(!$(this).confirmUnsavedChanges()) abort = true;
|
|
|
|
});
|
2011-12-17 04:06:56 +01:00
|
|
|
|
2011-12-15 12:23:32 +01:00
|
|
|
if(abort) return;
|
|
|
|
}
|
|
|
|
|
2011-12-15 12:16:54 +01:00
|
|
|
if(window.History.enabled) {
|
|
|
|
// Active menu item is set based on X-Controller ajax header,
|
|
|
|
// which matches one class on the menu
|
|
|
|
window.History.pushState(data, title, url);
|
|
|
|
} else {
|
|
|
|
window.location = url;
|
|
|
|
}
|
|
|
|
},
|
2011-06-09 03:49:52 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles ajax loading of new panels through the window.History object.
|
|
|
|
* To trigger loading, pass a new URL to window.History.pushState().
|
2011-12-15 12:16:54 +01:00
|
|
|
* Use loadPanel() as a pushState() wrapper as it provides some additional functionality
|
|
|
|
* like global changetracking and user aborts.
|
2011-06-09 03:49:52 +02:00
|
|
|
*
|
|
|
|
* 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(<url>).
|
|
|
|
* 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();
|
|
|
|
|
2011-07-15 10:37:46 +02:00
|
|
|
var selector = state.data.selector || '.cms-content', contentEl = $(selector);
|
2011-12-17 04:06:56 +01:00
|
|
|
|
|
|
|
this.trigger('beforestatechange', {
|
|
|
|
state: state, element: contentEl
|
|
|
|
});
|
2011-07-15 10:37:46 +02:00
|
|
|
|
|
|
|
contentEl.addClass('loading');
|
2011-06-09 03:49:52 +02:00
|
|
|
|
|
|
|
var xhr = $.ajax({
|
|
|
|
url: state.url,
|
|
|
|
success: function(data, status, xhr) {
|
2011-06-09 05:47:59 +02:00
|
|
|
// Update title
|
|
|
|
var title = xhr.getResponseHeader('X-Title');
|
|
|
|
if(title) document.title = title;
|
|
|
|
|
2011-06-09 03:49:52 +02:00
|
|
|
// Update panels
|
2011-07-15 10:37:46 +02:00
|
|
|
var newContentEl = $(data);
|
2011-10-29 20:47:11 +02:00
|
|
|
if(newContentEl.find('.cms-container').length) {
|
|
|
|
throw 'Content loaded via ajax is not allowed to contain tags matching the ".cms-container" selector to avoid infinite loops';
|
|
|
|
}
|
|
|
|
|
2011-12-14 22:18:19 +01:00
|
|
|
// Set loading state and store element state
|
2011-07-15 10:37:46 +02:00
|
|
|
newContentEl.addClass('loading');
|
2011-12-17 04:06:56 +01:00
|
|
|
var origStyle = contentEl.attr('style');
|
|
|
|
var layoutClasses = ['east', 'west', 'center', 'north', 'south'];
|
|
|
|
var elemClasses = contentEl.attr('class');
|
|
|
|
|
|
|
|
var origLayoutClasses = $.grep(
|
|
|
|
elemClasses.split(' '),
|
|
|
|
function(val) {
|
|
|
|
return ($.inArray(val, layoutClasses) >= 0);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2011-12-14 22:18:19 +01:00
|
|
|
newContentEl
|
|
|
|
.removeClass(layoutClasses.join(' '))
|
2012-01-09 21:16:35 +01:00
|
|
|
.addClass(origLayoutClasses.join(' '));
|
|
|
|
if(origStyle) newContentEl.attr('style', origStyle)
|
|
|
|
newContentEl.css('visibility', 'hidden');
|
2011-12-14 22:18:19 +01:00
|
|
|
|
2011-12-19 13:33:07 +01:00
|
|
|
// Allow injection of inline styles, as they're not allowed in the document body.
|
|
|
|
// Not handling this through jQuery.ondemand to avoid parsing the DOM twice.
|
|
|
|
var styles = newContentEl.find('style').detach();
|
|
|
|
if(styles.length) $(document).find('head').append(styles);
|
|
|
|
|
2011-12-14 22:18:19 +01:00
|
|
|
// Replace panel completely (we need to override the "layout" attribute, so can't replace the child instead)
|
2011-07-15 10:37:46 +02:00
|
|
|
contentEl.replaceWith(newContentEl);
|
2011-12-14 22:18:19 +01:00
|
|
|
|
|
|
|
// Unset loading and restore element state (to avoid breaking existing panel visibility, e.g. with preview expanded)
|
2011-06-09 03:49:52 +02:00
|
|
|
self.redraw();
|
2011-12-15 20:37:20 +01:00
|
|
|
newContentEl.css('visibility', 'visible');
|
2011-07-15 10:37:46 +02:00
|
|
|
newContentEl.removeClass('loading');
|
2011-12-20 14:10:39 +01:00
|
|
|
|
|
|
|
// Simulates a redirect on an ajax response - just exchange the URL without re-requesting it
|
|
|
|
if(window.History.enabled) {
|
|
|
|
var url = xhr.getResponseHeader('X-ControllerURL');
|
|
|
|
if(url) window.history.replaceState({}, '', url);
|
|
|
|
}
|
2011-06-09 03:49:52 +02:00
|
|
|
|
2011-07-15 10:37:46 +02:00
|
|
|
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: newContentEl});
|
|
|
|
},
|
|
|
|
error: function(xhr, status, e) {
|
|
|
|
contentEl.removeClass('loading');
|
2011-06-09 03:49:52 +02:00
|
|
|
}
|
|
|
|
});
|
2011-12-17 04:06:56 +01:00
|
|
|
|
2011-06-09 03:49:52 +02:00
|
|
|
this.setCurrentXHR(xhr);
|
2011-03-23 10:51:00 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make all buttons "hoverable" with jQuery theming.
|
|
|
|
* Also sets the clicked button on a form submission, making it available through
|
|
|
|
* a new 'clickedButton' property on the form DOM element.
|
|
|
|
*/
|
2012-01-03 09:36:23 +01:00
|
|
|
$('.cms input[type="submit"], .cms button, .cms input[type="reset"]').entwine({
|
2011-03-23 10:51:00 +01:00
|
|
|
onmatch: function() {
|
2011-12-15 22:04:03 +01:00
|
|
|
this.addClass('ss-ui-button');
|
|
|
|
this.redraw();
|
2011-04-15 05:32:43 +02:00
|
|
|
|
2011-03-23 10:51:00 +01:00
|
|
|
this._super();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
2012-02-08 15:34:06 +01:00
|
|
|
* Trigger dialogs with iframe based on the links href attribute (see ssui-core.js).
|
2011-03-23 10:51:00 +01:00
|
|
|
*/
|
2012-02-08 15:34:06 +01:00
|
|
|
$('.cms-container .ss-ui-dialog-link').entwine({
|
|
|
|
UUID: null,
|
2011-03-23 10:51:00 +01:00
|
|
|
onmatch: function() {
|
2012-02-08 15:34:06 +01:00
|
|
|
this._super();
|
|
|
|
this.setUUID(new Date().getTime());
|
2011-12-17 00:51:32 +01:00
|
|
|
},
|
2012-02-08 15:34:06 +01:00
|
|
|
onclick: function() {
|
|
|
|
this._super();
|
2011-12-17 00:51:32 +01:00
|
|
|
|
2012-02-08 15:34:06 +01:00
|
|
|
var self = this, id = 'ss-ui-dialog-' + this.getUUID();
|
2011-03-23 10:51:00 +01:00
|
|
|
|
2012-02-08 15:34:06 +01:00
|
|
|
var dialog = $('#' + id);
|
|
|
|
if(!dialog.length) {
|
|
|
|
dialog = $('<div class="ss-ui-dialog" id="' + id + '" />');
|
|
|
|
$('body').append(dialog);
|
|
|
|
}
|
2011-12-17 00:51:32 +01:00
|
|
|
|
2012-02-08 15:34:06 +01:00
|
|
|
dialog.ssdialog({iframeUrl: this.attr('href'), autoOpen: true});
|
2011-03-23 10:51:00 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
2012-01-23 09:01:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add styling to all contained buttons, and create buttonsets if required.
|
|
|
|
*/
|
|
|
|
$('.cms-container .Actions').entwine({
|
|
|
|
onmatch: function() {
|
|
|
|
this.find('.ss-ui-button').click(function() {
|
|
|
|
var form = this.form;
|
|
|
|
// forms don't natively store the button they've been triggered with
|
|
|
|
if(form) {
|
|
|
|
form.clickedButton = this;
|
|
|
|
// Reset the clicked button shortly after the onsubmit handlers
|
|
|
|
// have fired on the form
|
|
|
|
setTimeout(function() {form.clickedButton = null;}, 10);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.redraw();
|
|
|
|
this._super();
|
|
|
|
},
|
|
|
|
redraw: function() {
|
|
|
|
// Needs to be in the same execution frame as the buttonset logic below,
|
|
|
|
// to avoid re-adding rounded corners (default button styling) after removing them
|
2012-01-23 10:23:49 +01:00
|
|
|
this.find('.ss-ui-button').button();
|
|
|
|
|
|
|
|
// Remove whitespace to avoid gaps with inline elements
|
|
|
|
this.contents().filter(function() {
|
|
|
|
return (this.nodeType == 3 && !/\S/.test(this.nodeValue));
|
|
|
|
}).remove();
|
2012-01-23 09:01:30 +01:00
|
|
|
|
|
|
|
// Emulate jQuery UI buttonsets based on HTML5 data attributes
|
|
|
|
var sets = [], self = this;
|
|
|
|
this.find('.action[buttonset]').each(function() {
|
|
|
|
cl = $(this).attr('buttonset');
|
|
|
|
if($.inArray(cl, sets) == -1) sets.push(cl);
|
|
|
|
});
|
|
|
|
$.each(sets, function(i, set) {
|
2012-01-23 10:23:49 +01:00
|
|
|
// Gather buttons in set until no siblings are matched.
|
|
|
|
// This avoids "split" sets where a new button without a buttonset is inserted somewhere in the middle.
|
|
|
|
self.find('.action[buttonset="' + set + '"]:first')
|
|
|
|
.nextUntil('.action[buttonset!="' + set + '"]').andSelf()
|
|
|
|
.removeClass('ui-corner-all').addClass('buttonset')
|
2012-01-23 09:01:30 +01:00
|
|
|
.first().addClass('ui-corner-left').end()
|
|
|
|
.last().addClass('ui-corner-right');
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
2011-03-23 10:51:00 +01:00
|
|
|
|
2011-04-30 08:34:14 +02:00
|
|
|
/**
|
|
|
|
* Duplicates functionality in DateField.js, but due to using entwine we can match
|
|
|
|
* the DOM element on creation, rather than onclick - which allows us to decorate
|
|
|
|
* the field with a calendar icon
|
|
|
|
*/
|
2011-07-05 14:34:31 +02:00
|
|
|
$('.cms-container .field.date input.text').entwine({
|
2011-04-30 08:34:14 +02:00
|
|
|
onmatch: function() {
|
|
|
|
var holder = $(this).parents('.field.date:first'), config = holder.metadata({type: 'class'});
|
|
|
|
if(!config.showcalendar) return;
|
|
|
|
|
|
|
|
config.showOn = 'button';
|
|
|
|
if(config.locale && $.datepicker.regional[config.locale]) {
|
|
|
|
config = $.extend(config, $.datepicker.regional[config.locale], {});
|
|
|
|
}
|
|
|
|
|
|
|
|
$(this).datepicker(config);
|
|
|
|
// // Unfortunately jQuery UI only allows configuration of icon images, not sprites
|
|
|
|
// this.next('button').button('option', 'icons', {primary : 'ui-icon-calendar'});
|
|
|
|
|
|
|
|
this._super();
|
|
|
|
}
|
2011-08-05 05:46:57 +02:00
|
|
|
});
|
2011-04-30 08:34:14 +02:00
|
|
|
|
2011-08-05 05:46:57 +02:00
|
|
|
/**
|
|
|
|
* Styled dropdown select fields via chosen. Allows things like search and optgroup
|
|
|
|
* selection support. Rather than manually adding classes to selects we want
|
|
|
|
* styled, we style everything but the ones we tell it not to.
|
|
|
|
*
|
|
|
|
* For the CMS we also need to tell the parent div that his has a select so
|
|
|
|
* we can fix the height cropping.
|
|
|
|
*/
|
|
|
|
|
|
|
|
$('.cms-container .field.dropdown').entwine({
|
|
|
|
onmatch: function() {
|
|
|
|
$(this).find("select:not(.no-chzn)").chosen();
|
|
|
|
$(this).addClass("has-chzn");
|
|
|
|
|
|
|
|
this._super();
|
|
|
|
}
|
|
|
|
});
|
2011-10-29 02:01:06 +02:00
|
|
|
|
|
|
|
$(".cms-panel-layout").entwine({
|
|
|
|
redraw: function() {
|
2011-12-17 00:51:32 +01:00
|
|
|
this.layout({
|
|
|
|
resize: false
|
|
|
|
});
|
2011-10-29 02:01:06 +02:00
|
|
|
}
|
|
|
|
});
|
2011-06-10 04:56:55 +02:00
|
|
|
});
|
2011-03-23 10:51:00 +01:00
|
|
|
}(jQuery));
|
|
|
|
|
|
|
|
// Backwards compatibility
|
|
|
|
var statusMessage = function(text, type) {
|
|
|
|
jQuery.noticeAdd({text: text, type: type});
|
|
|
|
};
|
|
|
|
|
|
|
|
var errorMessage = function(text) {
|
|
|
|
jQuery.noticeAdd({text: text, type: 'error'});
|
|
|
|
};
|
|
|
|
|
|
|
|
returnFalse = function() {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find and enable TinyMCE on all htmleditor fields
|
|
|
|
* Pulled in from old tinymce.template.js
|
|
|
|
*/
|
|
|
|
|
|
|
|
function nullConverter(url) {
|
|
|
|
return url;
|
|
|
|
};
|