Moved widgets into core

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@39562 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Andrew O'Neil 2007-08-02 22:07:11 +00:00
parent c52da55f62
commit 112d7ed1ad
7 changed files with 353 additions and 3 deletions

View File

@ -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.

98
code/WidgetAreaEditor.php Normal file
View File

@ -0,0 +1,98 @@
<?php
class WidgetAreaEditor extends FormField {
function FieldHolder() {
return $this->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();
}
}
}
?>

52
css/WidgetAreaEditor.css Normal file
View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,25 @@
<div class="WidgetAreaEditor" id="WidgetAreaEditor" name="$Name">
<input type="hidden" id="$Name" name="$IdxField" value="$Value" />
<div class="availableWidgetsHolder">
<h2>Available Widgets</h2>
<div class="availableWidgets" id="WidgetAreaEditor_availableWidgets">
<% control AvailableWidgets %>
$DescriptionSegment
<% end_control %>
</div>
</div>
<div class="usedWidgetsHolder">
<h2>Used Widgets</h2>
<div class="usedWidgets" id="WidgetAreaEditor_usedWidgets">
<% if UsedWidgets %>
<% control UsedWidgets %>
$EditableSegment
<% end_control %>
<% else %>
<div id="NoWidgets">
<p>To add widgets, drag them from the left area to here.</p>
</div>
<% end_if %>
</div>
</div>
</div>

View File

@ -0,0 +1,6 @@
<div class="Widget" id="$ClassName">
<h3>$CMSTitle</h3>
<div class="widgetDescription">
<p>$Description</p>
</div>
</div>

12
templates/WidgetEditor.ss Normal file
View File

@ -0,0 +1,12 @@
<div class="$ClassName Widget" id="$Name">
<h3 class="handle">$CMSTitle</h3>
<div class="widgetDescription">
<p>$Description</p>
</div>
<div class="widgetFields">
$CMSEditor
<input type="hidden" name="$Name[Type]" value="$ClassName" />
<input type="hidden" name="$Name[Sort]" value="$Sort" />
</div>
<p class="deleteWidget"><span class="widgetDelete">Delete</span></p>
</div>