From 112d7ed1ad4b0864b49972b767db82804802568d Mon Sep 17 00:00:00 2001 From: Andrew O'Neil Date: Thu, 2 Aug 2007 22:07:11 +0000 Subject: [PATCH] Moved widgets into core git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@39562 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- code/CMSMain.php | 6 +- code/WidgetAreaEditor.php | 98 ++++++++++++++++++++ css/WidgetAreaEditor.css | 52 +++++++++++ javascript/WidgetAreaEditor.js | 157 +++++++++++++++++++++++++++++++++ templates/WidgetAreaEditor.ss | 25 ++++++ templates/WidgetDescription.ss | 6 ++ templates/WidgetEditor.ss | 12 +++ 7 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 code/WidgetAreaEditor.php create mode 100644 css/WidgetAreaEditor.css create mode 100644 javascript/WidgetAreaEditor.js create mode 100644 templates/WidgetAreaEditor.ss create mode 100644 templates/WidgetDescription.ss create mode 100644 templates/WidgetEditor.ss diff --git a/code/CMSMain.php b/code/CMSMain.php index e3424915..4bb85a1c 100644 --- a/code/CMSMain.php +++ b/code/CMSMain.php @@ -66,9 +66,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr Requirements::themedCSS('typography'); - // For Blog - Requirements::css("blog/css/WidgetAreaEditor.css"); - Requirements::javascript("blog/javascript/WidgetAreaEditor.js"); + // For Widgets + Requirements::css("cms/css/WidgetAreaEditor.css"); + Requirements::javascript("cms/javascript/WidgetAreaEditor.js"); // HACK ALERT: // We need a better way of including all of the CSS that *might* be used by this application. diff --git a/code/WidgetAreaEditor.php b/code/WidgetAreaEditor.php new file mode 100644 index 00000000..79aaa13b --- /dev/null +++ b/code/WidgetAreaEditor.php @@ -0,0 +1,98 @@ +renderWith("WidgetAreaEditor"); + } + + function AvailableWidgets() { + $classes = ClassInfo::subclassesFor('Widget'); + array_shift($classes); + $widgets= new DataObjectSet(); + + foreach($classes as $class) { + $widgets->push(singleton($class)); + } + + return $widgets; + } + + function UsedWidgets() { + $relationName = $this->name; + + $widgets = $this->form->getRecord()->$relationName()->Widgets(); + + return $widgets; + } + + function IdxField() { + return $this->id() . 'ID'; + } + + function Value() { + $relationName = $this->name; + return $this->form->getRecord()->$relationName()->ID; + } + + function saveInto(DataObject $record) { + $name = $this->name; + $idName = $name . "ID"; + + + $widgetarea = $record->$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(); + + foreach($widgets as $existingWidget){ + $missingWidgets[$existingWidget->ID] = $existingWidget; + } + + // write the new widgets to the database + if(isset($_REQUEST['Widget'])){ + + foreach(array_keys( $_REQUEST['Widget'] ) as $newWidgetID ) { + $newWidgetData = $_REQUEST['Widget'][$newWidgetID]; + + // `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; + } + $widget->populateFromPostData($newWidgetData); + //$editable->write(); + } + } + } + + // remove the fields not saved + foreach($missingWidgets as $removedWidget) { + if(isset($removedWidget) && is_numeric($removedWidget->ID)) $removedWidget->delete(); + } + } +} + +?> \ No newline at end of file diff --git a/css/WidgetAreaEditor.css b/css/WidgetAreaEditor.css new file mode 100644 index 00000000..edbc7e2a --- /dev/null +++ b/css/WidgetAreaEditor.css @@ -0,0 +1,52 @@ +div.availableWidgetsHolder, +div.usedWidgetsHolder { + width: 49%; +} + +div.availableWidgetsHolder { + float: left; +} + +div.usedWidgetsHolder { + float: right; +} + +div.usedWidgets { + min-height: 100px; +} + +div.usedWidgets div.Widget, +div.availableWidgets div.Widget { + width: 90%; + border: 1px solid #ddd; + border-top: none; + margin-bottom: 5px; +} +div.widgetDescription p, +div.widgetFields { + margin-left: 8px; +} + +p.deleteWidget { + margin: 0; + height: 2.2em; + line-height: 2.2em; + font-size: 1.2em; +} +span.widgetDelete { + padding-left: 20px; + margin-right: 8px; + float: right; + background: url(../images/delete2.png) no-repeat left center; + cursor: pointer; +} +div.usedWidgets div.Widget h3, +div.availableWidgets div.Widget h3 { + font-size: 1.3em; + height: 1.5em; + color: #fff; + line-height: 1.5em; + text-indent: 5px; + background: #7ab7ec url(../images/blogTitleBg.gif); + margin: 0; +} diff --git a/javascript/WidgetAreaEditor.js b/javascript/WidgetAreaEditor.js new file mode 100644 index 00000000..41edbf67 --- /dev/null +++ b/javascript/WidgetAreaEditor.js @@ -0,0 +1,157 @@ +WidgetAreaEditor = Class.create(); +WidgetAreaEditor.applyTo('div.WidgetAreaEditor'); + +WidgetAreaEditor.prototype = { + initialize: function() { + UsedWidget.applyToChildren($('WidgetAreaEditor_usedWidgets'), 'div.Widget'); + + // Make available widgets draggable + var availableWidgets = $('WidgetAreaEditor_availableWidgets').childNodes; + for(var i = 0; i < availableWidgets.length; i++) { + var widget = availableWidgets[i]; + if(widget.id) + new Draggable(widget.id); + } + + // Create dummy sortable to prevent javascript errors + Sortable.create('WidgetAreaEditor_availableWidgets', {tag: 'li', handle: 'handle', containment: []}); + // Used widgets are sortable + Sortable.create('WidgetAreaEditor_usedWidgets', {tag: 'div', handle: 'handle', containment: ['WidgetAreaEditor_availableWidgets', 'WidgetAreaEditor_usedWidgets'], onUpdate: this.updateWidgets}); + + // Figure out maxid, this is used when creating new widgets + this.maxid = 0; + + var usedWidgets = $('WidgetAreaEditor_usedWidgets').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]); + } + } + + // Ensure correct sort values are written when page is saved + $('Form_EditForm').observeMethod('BeforeSave', this.beforeSave.bind(this)); + }, + + beforeSave: function() { + // Ensure correct sort values are written when page is saved + var usedWidgets = $('WidgetAreaEditor_usedWidgets'); + + if(usedWidgets) { + this.sortWidgets(); + + var children = usedWidgets.childNodes; + + for( var i = 0; i < children.length; ++i ) { + var child = children[i]; + + if(child.beforeSave) { + child.beforeSave(); + } + } + } + }, + + updateWidgets: function() { + // 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; + for(var i = 0; i < usedWidgets.length; i++) { + var widget = usedWidgets[i]; + if(widget.id && (widget.id.indexOf("Widget[") != 0)) { + new Ajax.Request(widget.id + "/EditableSegment", {onSuccess : $('WidgetAreaEditor_usedWidgets').parentNode.parentNode.insertWidgetEditor.bind(this)}); + } + } + }, + + insertWidgetEditor: function(response) { + // Remove placeholder text + if($('NoWidgets')) { + $('WidgetAreaEditor_usedWidgets').removeChild($('NoWidgets')); + } + + // 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.childNodes[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() { + // Order the sort by the order the widgets are in the list + var usedWidgets = $('WidgetAreaEditor_usedWidgets'); + + if (usedWidgets) { + widgets = usedWidgets.childNodes; + + for( i = 0; div = widgets[i]; i++ ) { + var fields = div.getElementsByTagName('input'); + for( j = 0; field = fields.item(j); j++ ) { + if( field.name == div.id + '[Sort]' ) { + field.value = i; + } + } + } + } + }, + + deleteWidget: function(widgetToRemove) { + // Remove a widget from the used widgets column + $('WidgetAreaEditor_usedWidgets').removeChild(widgetToRemove); + } +} + +UsedWidget = Class.create(); + +UsedWidget.prototype = { + initialize: function() { + // Call deleteWidget when delete button is pushed + this.deleteButton = this.findDescendant('span', 'widgetDelete'); + if(this.deleteButton) + this.deleteButton.onclick = this.deleteWidget.bind(this); + }, + + // Taken from FieldEditor + findDescendant: function(tag, clsName, element) { + if(!element) + element = this; + + var descendants = element.getElementsByTagName(tag); + + for(var i = 0; i < descendants.length; i++) { + var el = descendants[i]; + + if(tag.toUpperCase() == el.tagName && el.className.indexOf( clsName ) != -1) + return el; + } + + return null; + }, + + deleteWidget: function() { + this.parentNode.parentNode.parentNode.deleteWidget(this); + } +} + diff --git a/templates/WidgetAreaEditor.ss b/templates/WidgetAreaEditor.ss new file mode 100644 index 00000000..12eaabad --- /dev/null +++ b/templates/WidgetAreaEditor.ss @@ -0,0 +1,25 @@ +
+ +
+

Available Widgets

+
+ <% control AvailableWidgets %> + $DescriptionSegment + <% end_control %> +
+
+
+

Used Widgets

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

To add widgets, drag them from the left area to here.

+
+ <% end_if %> +
+
+
\ No newline at end of file diff --git a/templates/WidgetDescription.ss b/templates/WidgetDescription.ss new file mode 100644 index 00000000..dc3530ea --- /dev/null +++ b/templates/WidgetDescription.ss @@ -0,0 +1,6 @@ +
+

$CMSTitle

+
+

$Description

+
+
\ No newline at end of file diff --git a/templates/WidgetEditor.ss b/templates/WidgetEditor.ss new file mode 100644 index 00000000..e51f0c97 --- /dev/null +++ b/templates/WidgetEditor.ss @@ -0,0 +1,12 @@ +
+

$CMSTitle

+
+

$Description

+
+
+ $CMSEditor + + +
+

Delete

+
\ No newline at end of file