BUGFIX WidgetArea now works. Can have multiple areas on a page, and has unit tests

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@90831 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Tom Rix 2009-11-05 00:13:56 +00:00
parent c59911dce2
commit a0649efa49
4 changed files with 166 additions and 104 deletions

View File

@ -42,14 +42,14 @@ class WidgetAreaEditor extends FormField {
function saveInto(DataObject $record) { function saveInto(DataObject $record) {
$name = $this->name; $name = $this->name;
$idName = $name . "ID"; $idName = $name . "ID";
$widgetarea = $record->getComponent($name); $widgetarea = $record->getComponent($name);
$widgetarea->write(); $widgetarea->write();
$record->$idName = $widgetarea->ID; $record->$idName = $widgetarea->ID;
$widgets = $widgetarea->Widgets(); $widgets = $widgetarea->Widgets();
// store the field IDs and delete the missing fields // store the field IDs and delete the missing fields
// alternatively, we could delete all the fields and re add them // alternatively, we could delete all the fields and re add them
$missingWidgets = array(); $missingWidgets = array();
@ -60,48 +60,50 @@ class WidgetAreaEditor extends FormField {
} }
} }
// write the new widgets to the database
if(isset($_REQUEST['Widget'])) { if(isset($_REQUEST['Widget'])) {
foreach(array_keys($_REQUEST['Widget']) as $newWidgetID) { foreach(array_keys($_REQUEST['Widget']) as $widgetAreaName) {
$newWidgetData = $_REQUEST['Widget'][$newWidgetID]; if ($widgetAreaName !== $this->name) {
continue;
// Sometimes the id is "new-1" or similar, ensure this doesn't get into the query
if(!is_numeric($newWidgetID)) {
$newWidgetID = 0;
} }
// \"ParentID\" = '0' is for the new page foreach(array_keys($_REQUEST['Widget'][$widgetAreaName]) as $newWidgetID) {
$widget = DataObject::get_one( $newWidgetData = $_REQUEST['Widget'][$widgetAreaName][$newWidgetID];
'Widget',
sprintf( // Sometimes the id is "new-1" or similar, ensure this doesn't get into the query
'("ParentID" = %d OR "ParentID" = 0) AND "Widget"."ID" = %d', if(!is_numeric($newWidgetID)) {
$record->$name()->ID, $newWidgetID = 0;
(int)$newWidgetID }
)
); // \"ParentID\" = '0' is for the new page
$widget = DataObject::get_one(
// check if we are updating an existing widget 'Widget',
if($widget && isset($missingWidgets[$widget->ID])) { "(\"ParentID\" = '{$record->$name()->ID}' OR \"ParentID\" = '0') AND \"Widget\".\"ID\" = '$newWidgetID'"
unset($missingWidgets[$widget->ID]); );
}
// create a new object // check if we are updating an existing widget
if(!$widget && !empty($newWidgetData['Type']) && class_exists($newWidgetData['Type'])) { if($widget && isset($missingWidgets[$widget->ID])) {
$widget = new $newWidgetData['Type'](); unset($missingWidgets[$widget->ID]);
$widget->ID = 0; }
$widget->ParentID = $record->$name()->ID;
// create a new object
if(!is_subclass_of($widget, 'Widget')) { if(!$widget && !empty($newWidgetData['Type']) && class_exists($newWidgetData['Type'])) {
$widget = null; $widget = new $newWidgetData['Type']();
} $widget->ID = 0;
} $widget->ParentID = $record->$name()->ID;
if($widget) { if(!is_subclass_of($widget, 'Widget')) {
if($widget->ParentID == 0) { $widget = null;
$widget->ParentID = $record->$name()->ID; }
}
if($widget) {
if($widget->ParentID == 0) {
$widget->ParentID = $record->$name()->ID;
}
// echo "Saving $widget->ID into $name/$widget->ParentID\n<br/>";
$widget->populateFromPostData($newWidgetData);
} }
$widget->populateFromPostData($newWidgetData);
} }
} }
} }
@ -109,7 +111,9 @@ class WidgetAreaEditor extends FormField {
// remove the fields not saved // remove the fields not saved
if($missingWidgets) { if($missingWidgets) {
foreach($missingWidgets as $removedWidget) { foreach($missingWidgets as $removedWidget) {
if(isset($removedWidget) && is_numeric($removedWidget->ID)) $removedWidget->delete(); if(isset($removedWidget) && is_numeric($removedWidget->ID)) {
$removedWidget->delete();
}
} }
} }
} }

View File

@ -15,7 +15,7 @@ div.usedWidgets {
border: 1px #CCC dotted; border: 1px #CCC dotted;
padding: 5px; padding: 5px;
} }
#NoWidgets { .NoWidgets {
padding: 50px; /* Make this nice and big and easily 'droppable' */ padding: 50px; /* Make this nice and big and easily 'droppable' */
} }

View File

@ -1,53 +1,97 @@
WidgetAreaEditorClass = Class.create(); WidgetAreaEditorClass = Class.create();
WidgetAreaEditorClass.applyTo('div.WidgetAreaEditor');
WidgetAreaEditorClass.prototype = { WidgetAreaEditorClass.prototype = {
initialize: function() { initialize: function() {
UsedWidget.applyToChildren($('WidgetAreaEditor_usedWidgets'), 'div.Widget'); this.name = this.getAttribute('name');
this.rewriteWidgetAreaAttributes();
UsedWidget.applyToChildren($('usedWidgets-'+this.name), 'div.Widget');
// Make available widgets draggable // Make available widgets draggable
var availableWidgets = $('WidgetAreaEditor_availableWidgets').childNodes; var availableWidgets = $('availableWidgets-'+this.name).childNodes;
for(var i = 0; i < availableWidgets.length; i++) { for(var i = 0; i < availableWidgets.length; i++) {
var widget = availableWidgets[i]; var widget = availableWidgets[i];
if(widget.id) // Don't run on comments, whitespace, etc
new Draggable(widget.id); if (widget.nodeType == 1) {
// Gotta change their ID's because otherwise we get clashes between two tabs
widget.id = widget.id + '-'+this.name;
if(widget.id) {
widget.onclick = function(event) {
parts = event.currentTarget.id.split('-');
var widgetArea = parts.pop();
var className = parts.pop();
$('WidgetAreaEditor-'+widgetArea).addWidget(className, widgetArea);
}
}
}
} }
// Create dummy sortable to prevent javascript errors // Create dummy sortable to prevent javascript errors
Sortable.create('WidgetAreaEditor_availableWidgets', { Sortable.create('availableWidgets-'+this.name, {
tag: 'li', tag: 'li',
handle: 'handle', handle: 'handle',
containment: [] containment: []
}); });
// Used widgets are sortable // Used widgets are sortable
Sortable.create('WidgetAreaEditor_usedWidgets', { Sortable.create('usedWidgets-'+this.name, {
tag: 'div', tag: 'div',
handle: 'handle', handle: 'handle',
containment: ['WidgetAreaEditor_availableWidgets', 'WidgetAreaEditor_usedWidgets'], containment: ['availableWidgets-'+this.name, 'usedWidgets-'+this.name],
onUpdate: this.updateWidgets onUpdate: this.updateWidgets
}); });
// Figure out maxid, this is used when creating new widgets // Figure out maxid, this is used when creating new widgets
this.maxid = 0; this.maxid = 0;
var usedWidgets = $('WidgetAreaEditor_usedWidgets').childNodes; var usedWidgets = $('usedWidgets-'+this.name).childNodes;
for(var i = 0; i < usedWidgets.length; i++) { for(var i = 0; i < usedWidgets.length; i++) {
var widget = usedWidgets[i]; var widget = usedWidgets[i];
if(widget.id) { if(widget.id) {
widgetid = widget.id.match(/Widget\[([0-9]+)\]/i); widgetid = widget.id.match(/\Widget\[(.+?)\]\[([0-9]+)\]/i);
if(widgetid && parseInt(widgetid[1]) > this.maxid) if(widgetid && parseInt(widgetid[2]) > this.maxid) {
this.maxid = parseInt(widgetid[1]); this.maxid = parseInt(widgetid[2]);
}
} }
} }
// Ensure correct sort values are written when page is saved // Ensure correct sort values are written when page is saved
$('Form_EditForm').observeMethod('BeforeSave', this.beforeSave.bind(this)); $('Form_EditForm').observeMethod('BeforeSave', this.beforeSave.bind(this));
}, },
rewriteWidgetAreaAttributes: function() {
this.name = this.getAttribute('name');
var monkeyWith = function(widgets, name) {
for(var i = 0; i < widgets.length; i++) {
widget = widgets[i];
if (!widget.getAttribute('rewritten') && (widget.id || widget.name)) {
if (widget.id && widget.id.indexOf('Widget[') === 0) {
var newValue = widget.id.replace(/Widget\[/, 'Widget['+name+'][');
//console.log('Renaming '+widget.tagName+' ID '+widget.id+' to '+newValue);
widget.id = newValue;
}
if (widget.name && widget.name.indexOf('Widget[') === 0) {
var newValue = widget.name.replace(/Widget\[/, 'Widget['+name+'][');
//console.log('Renaming '+widget.tagName+' Name '+widget.name+' to '+newValue);
widget.name = newValue;
}
widget.setAttribute('rewritten', 'yes');
}
else {
//console.log('Skipping '+(widget.id ? widget.id : (widget.name ? widget.name : 'unknown '+widget.tagName)));
}
}
}
monkeyWith($$('#WidgetAreaEditor-'+this.name+' .Widget'), this.name);
monkeyWith($$('#WidgetAreaEditor-'+this.name+' .Widget *'), this.name);
},
beforeSave: function() { beforeSave: function() {
// Ensure correct sort values are written when page is saved // Ensure correct sort values are written when page is saved
var usedWidgets = $('WidgetAreaEditor_usedWidgets'); var usedWidgets = $('usedWidgets-'+this.name);
if(usedWidgets) { if(usedWidgets) {
this.sortWidgets(); this.sortWidgets();
@ -64,15 +108,37 @@ WidgetAreaEditorClass.prototype = {
} }
}, },
addWidget: function(className, holder) {
this.name = holder;
new Ajax.Request('Widget_Controller/EditableSegment/' + className, {
onSuccess : $('usedWidgets-'+holder).parentNode.parentNode.insertWidgetEditor.bind(this)
});
},
updateWidgets: function() { updateWidgets: function() {
// Gotta get the name of the current dohickey based off the ID
this.name = this.element.id.split('-').pop();
// alert(this.name);
// Gotta get the name of the current dohickey based off the ID
this.name = this.element.id.split('-').pop();
// This is called when an available widgets is dragged over to used widgets. // This is called when an available widgets is dragged over to used widgets.
// It inserts the editor form into the new used widget // It inserts the editor form into the new used widget
var usedWidgets = $('WidgetAreaEditor_usedWidgets').childNodes;
var usedWidgets = $('usedWidgets-'+this.name).childNodes;
for(var i = 0; i < usedWidgets.length; i++) { for(var i = 0; i < usedWidgets.length; i++) {
var widget = usedWidgets[i]; var widget = usedWidgets[i];
if(widget.id && (widget.id.indexOf("Widget[") != 0) && (widget.id != 'NoWidgets')) { if(widget.id && (widget.id.indexOf("Widget[") != 0) && (widget.id != 'NoWidgets-'+this.name)) {
new Ajax.Request('Widget_Controller/EditableSegment/' + widget.id, { // Need to remove the -$Name part.
onSuccess : $('WidgetAreaEditor_usedWidgets').parentNode.parentNode.insertWidgetEditor.bind(this) var wIdArray = widget.id.split('-');
wIdArray.pop();
new Ajax.Request('Widget_Controller/EditableSegment/' + wIdArray.join('-'), {
onSuccess : $('usedWidgets-'+this.name).parentNode.parentNode.insertWidgetEditor.bind(this)
}); });
} }
} }
@ -80,47 +146,31 @@ WidgetAreaEditorClass.prototype = {
insertWidgetEditor: function(response) { insertWidgetEditor: function(response) {
// Remove placeholder text // Remove placeholder text
if($('NoWidgets')) { if($('NoWidgets-'+this.name)) {
$('WidgetAreaEditor_usedWidgets').removeChild($('NoWidgets')); $('usedWidgets-'+this.name).removeChild($('NoWidgets-'+this.name));
} }
var usedWidgets = $('usedWidgets-'+this.name).childNodes;
// Give the widget a unique id
widget = document.createElement('div');
widget.innerHTML = response.responseText.replace(/Widget\[0\]/gi, "Widget[new-" + (++$('usedWidgets-'+this.name).parentNode.parentNode.maxid) + "]");
// Find the new widget $('usedWidgets-'+this.name).appendChild(widget.childNodes[0]);
var usedWidgets = $('WidgetAreaEditor_usedWidgets').childNodes; $('usedWidgets-'+this.name).parentNode.parentNode.rewriteWidgetAreaAttributes();
for(var i = 0; i < usedWidgets.length; i++) { UsedWidget.applyToChildren($('usedWidgets-'+this.name), 'div.Widget');
var widget = usedWidgets[i];
if(widget.id && (widget.id.indexOf("Widget[") != 0)) { Sortable.create('usedWidgets-SideBar', {
// Clone the widget so we can put it back in the available widgets column tag: 'div',
clone = widget.cloneNode(true); handle: 'handle',
containment: ['availableWidgets-'+this.name, 'usedWidgets-'+this.name],
// Give the widget a unique id onUpdate: $('usedWidgets-'+this.name).parentNode.parentNode.updateWidgets
widget.innerHTML = response.responseText.replace(/Widget\[0\]/gi, "Widget[new-" + (++$('WidgetAreaEditor_usedWidgets').parentNode.parentNode.maxid) + "]"); });
// Replace the available widget with the used widget with editor form
widget.parentNode.insertBefore($(widget).getElementsByClassName('Widget')[0], widget);
widget.parentNode.removeChild(widget);
// Put the clone into the available widgets column
$('WidgetAreaEditor_availableWidgets').appendChild(clone);
// Reapply behaviour
new Draggable(clone.id);
Sortable.create('WidgetAreaEditor_usedWidgets', {
tag: 'div',
handle: 'handle',
containment: ['WidgetAreaEditor_availableWidgets', 'WidgetAreaEditor_usedWidgets'],
onUpdate: $('WidgetAreaEditor_usedWidgets').parentNode.parentNode.updateWidgets
});
UsedWidget.applyToChildren($('WidgetAreaEditor_usedWidgets'), 'div.Widget');
return;
}
}
}, },
sortWidgets: function() { sortWidgets: function() {
// Order the sort by the order the widgets are in the list // Order the sort by the order the widgets are in the list
var usedWidgets = $('WidgetAreaEditor_usedWidgets'); var usedWidgets = $('usedWidgets-'+this.name);
if(usedWidgets) { if(usedWidgets) {
widgets = usedWidgets.childNodes; widgets = usedWidgets.childNodes;
@ -144,7 +194,8 @@ WidgetAreaEditorClass.prototype = {
deleteWidget: function(widgetToRemove) { deleteWidget: function(widgetToRemove) {
// Remove a widget from the used widgets column // Remove a widget from the used widgets column
$('WidgetAreaEditor_usedWidgets').removeChild(widgetToRemove); $('usedWidgets-'+this.name).removeChild(widgetToRemove);
// TODO ... re-create NoWidgets div?
} }
} }
@ -180,3 +231,10 @@ UsedWidget.prototype = {
} }
} }
// Loop over all WidgetAreas and fire 'em up
var wAs = $$('.WidgetAreaEditor');
for(var i = 0; i < wAs.length; i++) {
WidgetAreaEditorClass.applyTo('div#'+wAs[i].id);
}

View File

@ -1,15 +1,15 @@
<div class="WidgetAreaEditor" id="WidgetAreaEditor" name="$Name"> <div class="WidgetAreaEditor" id="WidgetAreaEditor-$Name" name="$Name">
<input type="hidden" id="$Name" name="$IdxField" value="$Value" /> <input type="hidden" id="$Name" name="$IdxField" value="$Value" />
<div class="availableWidgetsHolder"> <div class="availableWidgetsHolder">
<h2><% _t('AVAILABLE', 'Available Widgets') %></h2> <h2><% _t('AVAILABLE', 'Available Widgets') %></h2>
<p>&nbsp;</p> <p>&nbsp;</p>
<div class="availableWidgets" id="WidgetAreaEditor_availableWidgets"> <div class="availableWidgets" id="availableWidgets-$Name">
<% if AvailableWidgets %> <% if AvailableWidgets %>
<% control AvailableWidgets %> <% control AvailableWidgets %>
$DescriptionSegment $DescriptionSegment
<% end_control %> <% end_control %>
<% else %> <% else %>
<div id="NoWidgets"> <div class="NoWidgets" id="NoWidgets-$Name">
<p><% _t('NOAVAIL', 'There are currently no widgets available.') %></p> <p><% _t('NOAVAIL', 'There are currently no widgets available.') %></p>
</div> </div>
<% end_if %> <% end_if %>
@ -19,13 +19,13 @@
<h2><% _t('INUSE', 'Widgets currently used') %></h2> <h2><% _t('INUSE', 'Widgets currently used') %></h2>
<p><% _t('TOADD', 'To add widgets, drag them from the left area to here.') %></p> <p><% _t('TOADD', 'To add widgets, drag them from the left area to here.') %></p>
<div class="usedWidgets" id="WidgetAreaEditor_usedWidgets"> <div class="usedWidgets" id="usedWidgets-$Name">
<% if UsedWidgets %> <% if UsedWidgets %>
<% control UsedWidgets %> <% control UsedWidgets %>
$EditableSegment $EditableSegment
<% end_control %> <% end_control %>
<% else %> <% else %>
<div id="NoWidgets"></div> <div class="NoWidgets" id="NoWidgets-$Name"></div>
<% end_if %> <% end_if %>
</div> </div>
</div> </div>