From a0649efa492e99c614e4ae01e9b2bf4d6e83b6b0 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Thu, 5 Nov 2009 00:13:56 +0000 Subject: [PATCH] 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 --- code/WidgetAreaEditor.php | 90 +++++++++--------- css/WidgetAreaEditor.css | 2 +- javascript/WidgetAreaEditor.js | 168 ++++++++++++++++++++++----------- templates/WidgetAreaEditor.ss | 10 +- 4 files changed, 166 insertions(+), 104 deletions(-) diff --git a/code/WidgetAreaEditor.php b/code/WidgetAreaEditor.php index 14f72eef..dd4a6d1a 100644 --- a/code/WidgetAreaEditor.php +++ b/code/WidgetAreaEditor.php @@ -42,14 +42,14 @@ class WidgetAreaEditor extends FormField { function saveInto(DataObject $record) { $name = $this->name; $idName = $name . "ID"; - + $widgetarea = $record->getComponent($name); $widgetarea->write(); $record->$idName = $widgetarea->ID; - + $widgets = $widgetarea->Widgets(); - + // store the field IDs and delete the missing fields // alternatively, we could delete all the fields and re add them $missingWidgets = array(); @@ -60,48 +60,50 @@ class WidgetAreaEditor extends FormField { } } - // write the new widgets to the database if(isset($_REQUEST['Widget'])) { - foreach(array_keys($_REQUEST['Widget']) as $newWidgetID) { - $newWidgetData = $_REQUEST['Widget'][$newWidgetID]; - - // Sometimes the id is "new-1" or similar, ensure this doesn't get into the query - if(!is_numeric($newWidgetID)) { - $newWidgetID = 0; + foreach(array_keys($_REQUEST['Widget']) as $widgetAreaName) { + if ($widgetAreaName !== $this->name) { + continue; } - - // \"ParentID\" = '0' is for the new page - $widget = DataObject::get_one( - 'Widget', - sprintf( - '("ParentID" = %d OR "ParentID" = 0) AND "Widget"."ID" = %d', - $record->$name()->ID, - (int)$newWidgetID - ) - ); - - // check if we are updating an existing widget - if($widget && isset($missingWidgets[$widget->ID])) { - unset($missingWidgets[$widget->ID]); - } - - // create a new object - if(!$widget && !empty($newWidgetData['Type']) && class_exists($newWidgetData['Type'])) { - $widget = new $newWidgetData['Type'](); - $widget->ID = 0; - $widget->ParentID = $record->$name()->ID; - - if(!is_subclass_of($widget, 'Widget')) { - $widget = null; - } - } - - if($widget) { - if($widget->ParentID == 0) { - $widget->ParentID = $record->$name()->ID; + + foreach(array_keys($_REQUEST['Widget'][$widgetAreaName]) as $newWidgetID) { + $newWidgetData = $_REQUEST['Widget'][$widgetAreaName][$newWidgetID]; + + // 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 + $widget = DataObject::get_one( + 'Widget', + "(\"ParentID\" = '{$record->$name()->ID}' OR \"ParentID\" = '0') AND \"Widget\".\"ID\" = '$newWidgetID'" + ); + + + // check if we are updating an existing widget + if($widget && isset($missingWidgets[$widget->ID])) { + unset($missingWidgets[$widget->ID]); + } + + // create a new object + if(!$widget && !empty($newWidgetData['Type']) && class_exists($newWidgetData['Type'])) { + $widget = new $newWidgetData['Type'](); + $widget->ID = 0; + $widget->ParentID = $record->$name()->ID; + + if(!is_subclass_of($widget, 'Widget')) { + $widget = null; + } + } + + if($widget) { + if($widget->ParentID == 0) { + $widget->ParentID = $record->$name()->ID; + } + // echo "Saving $widget->ID into $name/$widget->ParentID\n
"; + $widget->populateFromPostData($newWidgetData); } - - $widget->populateFromPostData($newWidgetData); } } } @@ -109,7 +111,9 @@ class WidgetAreaEditor extends FormField { // remove the fields not saved if($missingWidgets) { foreach($missingWidgets as $removedWidget) { - if(isset($removedWidget) && is_numeric($removedWidget->ID)) $removedWidget->delete(); + if(isset($removedWidget) && is_numeric($removedWidget->ID)) { + $removedWidget->delete(); + } } } } diff --git a/css/WidgetAreaEditor.css b/css/WidgetAreaEditor.css index 9f2bf54a..9b60ede6 100644 --- a/css/WidgetAreaEditor.css +++ b/css/WidgetAreaEditor.css @@ -15,7 +15,7 @@ div.usedWidgets { border: 1px #CCC dotted; padding: 5px; } -#NoWidgets { +.NoWidgets { padding: 50px; /* Make this nice and big and easily 'droppable' */ } diff --git a/javascript/WidgetAreaEditor.js b/javascript/WidgetAreaEditor.js index 31fdfcf7..02e2deb1 100644 --- a/javascript/WidgetAreaEditor.js +++ b/javascript/WidgetAreaEditor.js @@ -1,53 +1,97 @@ WidgetAreaEditorClass = Class.create(); -WidgetAreaEditorClass.applyTo('div.WidgetAreaEditor'); WidgetAreaEditorClass.prototype = { 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 - var availableWidgets = $('WidgetAreaEditor_availableWidgets').childNodes; + var availableWidgets = $('availableWidgets-'+this.name).childNodes; + for(var i = 0; i < availableWidgets.length; i++) { var widget = availableWidgets[i]; - if(widget.id) - new Draggable(widget.id); + // Don't run on comments, whitespace, etc + 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 - Sortable.create('WidgetAreaEditor_availableWidgets', { + Sortable.create('availableWidgets-'+this.name, { tag: 'li', handle: 'handle', containment: [] }); // Used widgets are sortable - Sortable.create('WidgetAreaEditor_usedWidgets', { + Sortable.create('usedWidgets-'+this.name, { tag: 'div', handle: 'handle', - containment: ['WidgetAreaEditor_availableWidgets', 'WidgetAreaEditor_usedWidgets'], + containment: ['availableWidgets-'+this.name, 'usedWidgets-'+this.name], onUpdate: this.updateWidgets }); // Figure out maxid, this is used when creating new widgets this.maxid = 0; - var usedWidgets = $('WidgetAreaEditor_usedWidgets').childNodes; + var usedWidgets = $('usedWidgets-'+this.name).childNodes; for(var i = 0; i < usedWidgets.length; i++) { var widget = usedWidgets[i]; if(widget.id) { - widgetid = widget.id.match(/Widget\[([0-9]+)\]/i); - if(widgetid && parseInt(widgetid[1]) > this.maxid) - this.maxid = parseInt(widgetid[1]); + widgetid = widget.id.match(/\Widget\[(.+?)\]\[([0-9]+)\]/i); + if(widgetid && parseInt(widgetid[2]) > this.maxid) { + this.maxid = parseInt(widgetid[2]); + } } } - + // Ensure correct sort values are written when page is saved $('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() { // Ensure correct sort values are written when page is saved - var usedWidgets = $('WidgetAreaEditor_usedWidgets'); + var usedWidgets = $('usedWidgets-'+this.name); if(usedWidgets) { 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() { + + // 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. // 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++) { var widget = usedWidgets[i]; - if(widget.id && (widget.id.indexOf("Widget[") != 0) && (widget.id != 'NoWidgets')) { - new Ajax.Request('Widget_Controller/EditableSegment/' + widget.id, { - onSuccess : $('WidgetAreaEditor_usedWidgets').parentNode.parentNode.insertWidgetEditor.bind(this) + if(widget.id && (widget.id.indexOf("Widget[") != 0) && (widget.id != 'NoWidgets-'+this.name)) { + // Need to remove the -$Name part. + 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) { // Remove placeholder text - if($('NoWidgets')) { - $('WidgetAreaEditor_usedWidgets').removeChild($('NoWidgets')); + if($('NoWidgets-'+this.name)) { + $('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 - var usedWidgets = $('WidgetAreaEditor_usedWidgets').childNodes; - for(var i = 0; i < usedWidgets.length; i++) { - var widget = usedWidgets[i]; - if(widget.id && (widget.id.indexOf("Widget[") != 0)) { - // Clone the widget so we can put it back in the available widgets column - clone = widget.cloneNode(true); - - // Give the widget a unique id - 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; - } - } + $('usedWidgets-'+this.name).appendChild(widget.childNodes[0]); + $('usedWidgets-'+this.name).parentNode.parentNode.rewriteWidgetAreaAttributes(); + UsedWidget.applyToChildren($('usedWidgets-'+this.name), 'div.Widget'); + + Sortable.create('usedWidgets-SideBar', { + tag: 'div', + handle: 'handle', + containment: ['availableWidgets-'+this.name, 'usedWidgets-'+this.name], + onUpdate: $('usedWidgets-'+this.name).parentNode.parentNode.updateWidgets + }); }, sortWidgets: function() { // Order the sort by the order the widgets are in the list - var usedWidgets = $('WidgetAreaEditor_usedWidgets'); + var usedWidgets = $('usedWidgets-'+this.name); if(usedWidgets) { widgets = usedWidgets.childNodes; @@ -144,7 +194,8 @@ WidgetAreaEditorClass.prototype = { deleteWidget: function(widgetToRemove) { // 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); + +} + diff --git a/templates/WidgetAreaEditor.ss b/templates/WidgetAreaEditor.ss index 7defcdc5..187e33e4 100644 --- a/templates/WidgetAreaEditor.ss +++ b/templates/WidgetAreaEditor.ss @@ -1,15 +1,15 @@ -
+

<% _t('AVAILABLE', 'Available Widgets') %>

 

-
+
<% if AvailableWidgets %> <% control AvailableWidgets %> $DescriptionSegment <% end_control %> <% else %> -
+

<% _t('NOAVAIL', 'There are currently no widgets available.') %>

<% end_if %> @@ -19,13 +19,13 @@

<% _t('INUSE', 'Widgets currently used') %>

<% _t('TOADD', 'To add widgets, drag them from the left area to here.') %>

-
+
<% if UsedWidgets %> <% control UsedWidgets %> $EditableSegment <% end_control %> <% else %> -
+
<% end_if %>