mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
BUGFIX: Use new jQuery.Entwine event capturing, onadd and onremove features to plug some memory leaks
This commit is contained in:
parent
bbd1bb7495
commit
b86a787521
@ -14,12 +14,6 @@
|
||||
* .cms-edit-form
|
||||
*/
|
||||
$('.cms-edit-form.cms-add-form').entwine({
|
||||
/**
|
||||
* Variable: Tree
|
||||
* (DOMElement)
|
||||
*/
|
||||
Tree: null,
|
||||
|
||||
/**
|
||||
* Variable: OrigOptions
|
||||
* (Array) Map of <option> values to an object of "title" and "value"
|
||||
@ -32,17 +26,23 @@
|
||||
*/
|
||||
NewPages: [],
|
||||
|
||||
getTree: function() {
|
||||
return $('.cms-tree');
|
||||
},
|
||||
|
||||
fromTree: {
|
||||
onselect_node: function(e, data){
|
||||
self.refresh(data.rslt.obj);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor: onmatch
|
||||
*/
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
var self = this, typeDropdown = this.find(':input[name=PageType]');
|
||||
|
||||
var tree = $('.cms-tree');
|
||||
this.setTree(tree);
|
||||
|
||||
// Event bindings
|
||||
$(tree).bind('select_node.jstree', function(e, data) {self.refresh(data.rslt.obj);});
|
||||
typeDropdown.bind('change', function(e) {self.refresh();});
|
||||
// TODO Bind on tree initialization to set dropdown for selected node
|
||||
|
||||
@ -56,9 +56,7 @@
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function: onsubmit
|
||||
*
|
||||
|
@ -18,12 +18,6 @@
|
||||
*/
|
||||
$('#Form_BatchActionsForm').entwine({
|
||||
|
||||
/**
|
||||
* Variable: Tree
|
||||
* (DOMElement)
|
||||
*/
|
||||
Tree: null,
|
||||
|
||||
/**
|
||||
* Variable: Actions
|
||||
* (Array) Stores all actions that can be performed on the collected IDs as
|
||||
@ -32,37 +26,39 @@
|
||||
*/
|
||||
Actions: [],
|
||||
|
||||
getTree: function() {
|
||||
return $('.cms-tree');
|
||||
},
|
||||
|
||||
fromTree: {
|
||||
oncheck_node: function(e, data){
|
||||
this.serializeFromTree();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor: onmatch
|
||||
*/
|
||||
onmatch: function() {
|
||||
var self = this, tree = $('.cms-tree');
|
||||
onadd: function() {
|
||||
this._updateStateFromViewMode();
|
||||
this._super();
|
||||
},
|
||||
|
||||
this.setTree(tree);
|
||||
'from .cms-tree-view-modes :input[name=view-mode]': {
|
||||
onclick: function(e){
|
||||
var val = $(e.target).val(), dropdown = this.find(':input[name=Action]'), tree = this.getTree();
|
||||
|
||||
tree.bind('check_node.jstree', function(e, data) {
|
||||
self.serializeFromTree();
|
||||
});
|
||||
|
||||
$('.cms-tree-view-modes :input[name=view-mode]').bind('click', function(e) {
|
||||
var val = $(e.target).val(), dropdown = self.find(':input[name=Action]');
|
||||
if(val == 'multiselect') {
|
||||
tree.addClass('multiple');
|
||||
self.serializeFromTree();
|
||||
this.serializeFromTree();
|
||||
} else {
|
||||
tree.removeClass('multiple');
|
||||
}
|
||||
|
||||
self._updateStateFromViewMode();
|
||||
});
|
||||
|
||||
self._updateStateFromViewMode();
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
this._updateStateFromViewMode();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the select box state according to the current view mode.
|
||||
*/
|
||||
|
@ -10,7 +10,7 @@
|
||||
*/
|
||||
$('.cms-content').entwine({
|
||||
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
var self = this;
|
||||
|
||||
// Force initialization of certain UI elements to avoid layout glitches
|
||||
@ -19,9 +19,6 @@
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
},
|
||||
|
||||
redraw: function() {
|
||||
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
||||
@ -39,7 +36,7 @@
|
||||
* Load edit form for the selected node when its clicked.
|
||||
*/
|
||||
$('.cms-content .cms-tree').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
var self = this;
|
||||
|
||||
this._super();
|
||||
@ -80,9 +77,6 @@
|
||||
self.removeForm();
|
||||
}
|
||||
});
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -50,7 +50,7 @@
|
||||
/**
|
||||
* Constructor: onmatch
|
||||
*/
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
var self = this;
|
||||
|
||||
// Turn off autocomplete to fix the access tab randomly switching radio buttons in Firefox
|
||||
@ -106,7 +106,7 @@
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
onremove: function() {
|
||||
this.changetracker('destroy');
|
||||
this._super();
|
||||
},
|
||||
|
@ -78,23 +78,6 @@
|
||||
onmatch: function() {
|
||||
var self = this;
|
||||
|
||||
var updateMenuFromResponse = function(xhr) {
|
||||
var controller = xhr.getResponseHeader('X-Controller');
|
||||
if(controller) {
|
||||
var item = self.find('li#Menu-' + controller);
|
||||
if(!item.hasClass('current')) item.select();
|
||||
}
|
||||
self.updateItems();
|
||||
};
|
||||
$('.cms-container').live('afterstatechange aftersubmitform', function(e, data) {
|
||||
updateMenuFromResponse(data.xhr);
|
||||
});
|
||||
|
||||
// Sync collapsed state with parent panel
|
||||
this.parents('.cms-panel:first').bind('toggle', function(e) {
|
||||
self.toggleClass('collapsed', $(this).hasClass('collapsed'));
|
||||
});
|
||||
|
||||
// Select default element (which might reveal children in hidden parents)
|
||||
this.find('li.current').select();
|
||||
|
||||
@ -105,6 +88,41 @@
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
},
|
||||
|
||||
updateMenuFromResponse: function(xhr) {
|
||||
var controller = xhr.getResponseHeader('X-Controller');
|
||||
if(controller) {
|
||||
var item = this.find('li#Menu-' + controller);
|
||||
if(!item.hasClass('current')) item.select();
|
||||
}
|
||||
this.updateItems();
|
||||
},
|
||||
|
||||
'from .cms-container': {
|
||||
onafterstatechange: function(e, data){
|
||||
this.updateMenuFromResponse(data.xhr);
|
||||
},
|
||||
onaftersubmitform: function(e, data){
|
||||
this.updateMenuFromResponse(data.xhr);
|
||||
}
|
||||
},
|
||||
|
||||
'from .cms-edit-form': {
|
||||
onrelodeditform: function(e, data){
|
||||
this.updateMenuFromResponse(data.xmlhttp);
|
||||
}
|
||||
},
|
||||
|
||||
getContainingPanel: function(){
|
||||
return this.closest('.cms-panel');
|
||||
},
|
||||
|
||||
fromContainingPanel: {
|
||||
ontoggle: function(e){
|
||||
this.toggleClass('collapsed', $(e.target).hasClass('collapsed'));
|
||||
}
|
||||
},
|
||||
|
||||
updateItems: function() {
|
||||
// Hide "edit page" commands unless the section is activated
|
||||
var editPageItem = this.find('#Menu-CMSMain');
|
||||
|
@ -11,15 +11,11 @@
|
||||
*/
|
||||
PingIntervalSeconds: 5*60,
|
||||
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
this._setupPinging();
|
||||
this._super();
|
||||
},
|
||||
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function: _setupPinging
|
||||
*
|
||||
|
@ -43,34 +43,6 @@
|
||||
if(!self.is('.is-collapsed')) self.loadCurrentPage();
|
||||
});
|
||||
|
||||
var updateAfterXhr = function() {
|
||||
$('.cms-preview-toggle-link')[self.canPreview() ? 'show' : 'hide']();
|
||||
|
||||
// Only load when panel is visible (see details in iframe load event handler).
|
||||
if(self.is('.is-collapsed')) return;
|
||||
|
||||
// var url = ui.xmlhttp.getResponseHeader('x-frontend-url');
|
||||
var url = $('.cms-edit-form').find(':input[name=StageURLSegment]').val();
|
||||
if(url) {
|
||||
self.loadUrl(url);
|
||||
self.unblock();
|
||||
} else {
|
||||
self.block();
|
||||
}
|
||||
};
|
||||
|
||||
// Listen to history state changes
|
||||
$('.cms-container').bind('afterstatechange aftersubmitform', function(e) {
|
||||
updateAfterXhr();
|
||||
});
|
||||
|
||||
// Toggle preview when new menu entry is selected.
|
||||
// Only do this when preview is actually shown,
|
||||
// to avoid auto-expanding the menu in normal CMS mode
|
||||
$('.cms-menu-list li').bind('select', function(e) {
|
||||
if(!self.hasClass('is-collapsed')) self.collapse();
|
||||
});
|
||||
|
||||
if(this.hasClass('is-expanded')) this.expand();
|
||||
else this.collapse();
|
||||
this.data('cms-preview-initialized', true);
|
||||
@ -91,6 +63,40 @@
|
||||
this.find('iframe').attr('src', url);
|
||||
},
|
||||
|
||||
updateAfterXhr: function(){
|
||||
$('.cms-preview-toggle-link')[this.canPreview() ? 'show' : 'hide']();
|
||||
|
||||
// Only load when panel is visible (see details in iframe load event handler).
|
||||
if(this.is('.is-collapsed')) return;
|
||||
|
||||
// var url = ui.xmlhttp.getResponseHeader('x-frontend-url');
|
||||
var url = $('.cms-edit-form').find(':input[name=StageURLSegment]').val();
|
||||
if(url) {
|
||||
this.loadUrl(url);
|
||||
this.unblock();
|
||||
} else {
|
||||
this.block();
|
||||
}
|
||||
},
|
||||
|
||||
'from .cms-container': {
|
||||
onaftersubmitform: function(){
|
||||
this.updateAfterXhr();
|
||||
},
|
||||
onafterstatechange: function(){
|
||||
this.updateAfterXhr();
|
||||
}
|
||||
},
|
||||
|
||||
// Toggle preview when new menu entry is selected.
|
||||
// Only do this when preview is actually shown,
|
||||
// to avoid auto-expanding the menu in normal CMS mode
|
||||
'from .cms-menu-list li': {
|
||||
onselect: function(){
|
||||
if(!this.hasClass('is-collapsed')) this.collapse();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the matching edit form for a page viewed in the preview iframe,
|
||||
* based on metadata sent along with this document.
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
Hints: null,
|
||||
|
||||
onmatch: function() {
|
||||
onadd: function(){
|
||||
this._super();
|
||||
|
||||
// Don't reapply (expensive) tree behaviour if already present
|
||||
@ -96,16 +96,22 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.cms-container').bind('afterstatechange.tree aftersubmitform.tree', function(e, data) {
|
||||
self.updateFromEditForm(e.origData);
|
||||
});
|
||||
},
|
||||
onunmatch: function() {
|
||||
$('.cms-container').unbind('afterstatechange.tree aftersubmitform.tree');
|
||||
onremove: function(){
|
||||
this.jstree('destroy');
|
||||
this._super();
|
||||
},
|
||||
|
||||
'from .cms-container': {
|
||||
onafterstatechange: function(e){
|
||||
this.updateFromEditForm(e.origData);
|
||||
},
|
||||
|
||||
onaftersubmitform: function(e){
|
||||
this.updateFromEditForm(e.origData);
|
||||
}
|
||||
},
|
||||
|
||||
getTreeConfig: function() {
|
||||
var self = this;
|
||||
return {
|
||||
|
@ -73,7 +73,7 @@ jQuery.noConflict();
|
||||
/**
|
||||
* Constructor: onmatch
|
||||
*/
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
var self = this;
|
||||
|
||||
// Browser detection
|
||||
@ -92,32 +92,25 @@ jQuery.noConflict();
|
||||
// Initialize layouts
|
||||
this.redraw();
|
||||
|
||||
// Monitor window resizes, panel changes and edit form loads for layout changes.
|
||||
// Also triggers redraw through handleStateChange()
|
||||
$(window).resize(function() {
|
||||
self.redraw();
|
||||
});
|
||||
|
||||
$('.cms-panel').live('toggle', function() {
|
||||
self.redraw();
|
||||
});
|
||||
|
||||
// Remove loading screen
|
||||
$('.ss-loading-screen').hide();
|
||||
$('body').removeClass('loading');
|
||||
$(window).unbind('resize', positionLoadingSpinner);
|
||||
|
||||
History.Adapter.bind(window,'statechange',function(){
|
||||
self.handleStateChange();
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
|
||||
this._super();
|
||||
fromWindow: {
|
||||
onstatechange: function(){ this.handleStateChange(); },
|
||||
onresize: function(){ this.redraw(); }
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
|
||||
'from .cms-panel': {
|
||||
ontoggle: function(){ this.redraw(); }
|
||||
},
|
||||
onaftersubmitform: function() {
|
||||
this.redraw();
|
||||
|
||||
'from .cms-container': {
|
||||
onaftersubmitform: function(){ this.redraw(); }
|
||||
},
|
||||
|
||||
redraw: function() {
|
||||
@ -457,12 +450,13 @@ jQuery.noConflict();
|
||||
});
|
||||
|
||||
$('.cms .ss-ui-button').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
if(!this.data('button')) this.button();
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
onremove: function() {
|
||||
this.button('destroy');
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
@ -744,11 +738,12 @@ jQuery.noConflict();
|
||||
*/
|
||||
window._panelDeferredCache = {};
|
||||
$('.cms-panel-deferred').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
this._super();
|
||||
this.redraw();
|
||||
},
|
||||
onunmatch: function() {
|
||||
onremove: function() {
|
||||
console.log('saving', this.data('url'), this);
|
||||
// Save the HTML state at the last possible moment.
|
||||
// Don't store the DOM to avoid memory leaks.
|
||||
if(!this.data('deferredNoCache')) window._panelDeferredCache[this.data('url')] = this.html();
|
||||
@ -790,12 +785,13 @@ jQuery.noConflict();
|
||||
* for forms inside the CMS layout.
|
||||
*/
|
||||
$('.cms-tabset').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
// Can't name redraw() as it clashes with other CMS entwine classes
|
||||
this.redrawTabs();
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
onremove: function() {
|
||||
this.tabs('destroy');
|
||||
this._super();
|
||||
},
|
||||
redrawTabs: function() {
|
||||
|
@ -5,13 +5,8 @@
|
||||
* Creates a jQuery UI tab navigation bar, detached from the container DOM structure.
|
||||
*/
|
||||
$('.ss-ui-tabs-nav').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
this.redraw();
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
},
|
||||
redraw: function() {
|
||||
this.addClass('ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-panel ui-corner-bottom');
|
||||
|
@ -202,40 +202,43 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
|
||||
/**
|
||||
* Constructor: onmatch
|
||||
*/
|
||||
onmatch : function() {
|
||||
var self = this, edClass = this.data('editor') || ss.editorWrappers['default'], ed = edClass();
|
||||
onadd: function() {
|
||||
var edClass = this.data('editor') || ss.editorWrappers['default'], ed = edClass();
|
||||
this.setEditor(ed);
|
||||
this.closest('form').bind('beforesubmitform', function() {
|
||||
// TinyMCE modifies input, so change tracking might get false
|
||||
// positives when comparing string values - don't save if the editor doesn't think its dirty.
|
||||
if(self.isChanged()) {
|
||||
ed.save();
|
||||
|
||||
// TinyMCE assigns value attr directly, which doesn't trigger change event
|
||||
self.trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
// Using a global config (generated through HTMLEditorConfig PHP logic).
|
||||
// Depending on browser cache load behaviour, entwine's DOMMaybeChanged
|
||||
// can be called before the bottom-most inline script tag is executed,
|
||||
// which defines the global. If that's the case, wait for the window load.
|
||||
if(typeof ssTinyMceConfig != 'undefined') {
|
||||
this.redraw();
|
||||
} else {
|
||||
$(window).bind('load', function() {
|
||||
self.redraw();
|
||||
});
|
||||
}
|
||||
if(typeof ssTinyMceConfig != 'undefined') this.redraw();
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
// TODO Throws exceptions in Firefox, most likely due to the element being removed from the DOM at this point
|
||||
// var ed = tinyMCE.get(this.attr('id'));
|
||||
// if(ed) ed.remove();
|
||||
onremove: function() {
|
||||
var ed = tinyMCE.get(this.attr('id'));
|
||||
if (ed) ed.remove();
|
||||
this._super();
|
||||
},
|
||||
|
||||
getContainingForm: function(){
|
||||
return this.closest('form');
|
||||
},
|
||||
|
||||
fromContainingForm: {
|
||||
onbeforesave: function(){
|
||||
if(this.isChanged()) {
|
||||
this.getEditor().save();
|
||||
this.trigger('change'); // TinyMCE assigns value attr directly, which doesn't trigger change event
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fromWindow: {
|
||||
onload: function(){
|
||||
this.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
redraw: function() {
|
||||
// Using a global config (generated through HTMLEditorConfig PHP logic)
|
||||
var config = ssTinyMceConfig, self = this, ed = this.getEditor();
|
||||
@ -253,16 +256,18 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
|
||||
self.css('visibility', 'visible');
|
||||
});
|
||||
|
||||
// Handle editor de-registration by hooking into state changes.
|
||||
// TODO Move to onunmatch for less coupling (once we figure out how to work with detached DOM nodes in TinyMCE)
|
||||
$('.cms-container').bind('beforestatechange', function() {
|
||||
self.css('visibility', 'hidden');
|
||||
var container = ed.getInstance() ? ed.getContainer() : null;
|
||||
if(container && container.length) container.remove();
|
||||
});
|
||||
|
||||
this._super();
|
||||
},
|
||||
|
||||
'from .cms-container': {
|
||||
onbeforestatechange: function(){
|
||||
this.css('visibility', 'hidden');
|
||||
|
||||
var ed = this.getEditor(), container = ed.getInstance() ? ed.getContainer() : null;
|
||||
if(container && container.length) container.remove();
|
||||
}
|
||||
},
|
||||
|
||||
isChanged: function() {
|
||||
var ed = this.getEditor();
|
||||
return (ed && ed.getInstance() && ed.isDirty());
|
||||
@ -1103,16 +1108,20 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
|
||||
});
|
||||
|
||||
$('div.ss-assetuploadfield .ss-uploadfield-item-edit, div.ss-assetuploadfield .ss-uploadfield-item-name').entwine({
|
||||
onclick: function(e) {
|
||||
var editForm = this.closest('.ss-uploadfield-item').find('.ss-uploadfield-item-editform');
|
||||
getEditForm: function() {
|
||||
return this.closest('.ss-uploadfield-item').find('.ss-uploadfield-item-editform');
|
||||
},
|
||||
|
||||
// Mark the row as changed if any of its form fields are edited
|
||||
editForm.ready(function() {
|
||||
editForm.find(':input').bind('change', function(e){
|
||||
editForm.removeClass('edited'); //so edited class is only there once
|
||||
editForm.addClass('edited');
|
||||
});
|
||||
});
|
||||
fromEditForm: {
|
||||
onchange: function(e){
|
||||
var form = $(e.target);
|
||||
form.removeClass('edited'); //so edited class is only there once
|
||||
form.addClass('edited');
|
||||
}
|
||||
},
|
||||
|
||||
onclick: function(e) {
|
||||
var editForm = this.getEditForm();
|
||||
|
||||
editForm.parent('.ss-uploadfield-item').removeClass('ui-state-warning');
|
||||
|
||||
@ -1153,7 +1162,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
|
||||
|
||||
|
||||
$('form.htmleditorfield-mediaform #ParentID .TreeDropdownField').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
this._super();
|
||||
|
||||
// TODO Custom event doesn't fire in IE if registered through object literal
|
||||
@ -1163,9 +1172,6 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
|
||||
fileList.setState('ParentID', self.getValue());
|
||||
fileList.reload();
|
||||
});
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4,12 +4,13 @@
|
||||
* Lightweight wrapper around jQuery UI tabs.
|
||||
*/
|
||||
$('.ss-tabset').entwine({
|
||||
onmatch: function() {
|
||||
onadd: function() {
|
||||
// Can't name redraw() as it clashes with other CMS entwine classes
|
||||
this.redrawTabs();
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
onremove: function() {
|
||||
this.tabs('destroy');
|
||||
this._super();
|
||||
},
|
||||
redrawTabs: function() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user