#92 MoreLessField: Switch to ToggleField and remove

FEATURE Refactored MoreLessField->ToggleField
FEATURE Refactored TogglePanel->ToggleCompositeField
FEATURE Degrading gracefully (javascript), using behaviour+classes+prototype, partially i18ned, improved markup

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@43660 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2007-10-21 23:05:46 +00:00
parent ba85ce7a0b
commit 35fb0cd0c5
11 changed files with 228 additions and 133 deletions

View File

@ -1003,7 +1003,7 @@ class SiteTree extends DataObject {
new TextField("MetaTitle", "Title"),
new TextareaField("MetaDescription", "Description"),
new TextareaField("MetaKeywords", "Keywords"),
new TogglePanel("Advanced Options...",array(
new ToggleCompositeField("Advanced Options...",array(
new TextareaField("ExtraMeta","Custom Meta Tags"),
new LiteralField("", "<p>Manually specify a Priority for this page: (valid values are from 0 to 1, a zero will remove this page from the index)</p>"),
new NumericField("Priority","Page Priority")),

View File

@ -619,15 +619,20 @@ class Translatable extends DataObjectDecorator {
$tasks = array(
'dup' => array(),
);
foreach ($fields as $field) {
foreach($fields as $field) {
if ($field->isComposite()) {
$innertasks = $this->duplicateOrReplaceFields($field->FieldSet());
$tasks['dup'] = array_merge($tasks['dup'],$innertasks['dup']);
}
else if (($fieldname = $field->Name()) && array_key_exists($fieldname,$this->original_values)) {
else if(($fieldname = $field->Name()) && array_key_exists($fieldname,$this->original_values)) {
// Get a copy of the original field to show the untranslated value
if (is_subclass_of($field->class,'TextareaField')) $nonEditableField = new MoreLessField($fieldname,$field->Title(),'','+','-');
else $nonEditableField = $field->performDisabledTransformation();
if(is_subclass_of($field->class,'TextareaField')) {
$nonEditableField = new ToggleField($fieldname,$field->Title(),'','+','-');
$nonEditableField->labelMore = '+';
$nonEditableField->labelLess = '-';
} else {
$nonEditableField = $field->performDisabledTransformation();
}
$nonEditableField_holder = new CompositeField($nonEditableField);
$nonEditableField_holder->setName($fieldname.'_holder');
@ -736,3 +741,4 @@ class Translatable extends DataObjectDecorator {
}
}
?>

View File

@ -1,60 +0,0 @@
<?php
/**
* A field that shows only a part of its contents.
* Using the 'more' and 'less' links you can switch to the complete or to the partial text, respectively
*/
class MoreLessField extends ReadonlyField {
protected $moreText;
protected $lessText;
protected $charNum;
/**
* Creates a new More/Less field.
* @param name The field name
* @param title The field title
* @param value The current value
* @param moreText Text shown as a link to see the full content of the field
* @param lessText Text shown as a link to see the partial view of the field content
* @param chars Number of chars to preview. If zero it'll show the first line or sentence.
*/
function __construct($name, $title = "", $value = "", $moreText = 'more', $lessText = 'less', $chars = 0) {
$this->moreText = $moreText;
$this->lessText = $lessText;
$this->charNum = $chars;
parent::__construct($name, $title, $value);
}
function Field() {
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
$rawInput = Convert::html2raw($valforInput);
if ($this->charNum) $reducedVal = substr($rawInput,0,$this->charNum);
else $reducedVal = ereg_replace('([^\.]\.)[[:space:]].*','\\1',$rawInput);
if (strlen($reducedVal) < strlen($rawInput)) {
return <<<HTML
<div class="readonly typography" id="{$this->id()}_reduced" style="display: inline;">$reducedVal
<a onclick="\$('{$this->id()}_reduced').style.display='none'; \$('{$this->id()}').style.display='inline'; return false;" href="#"> $this->moreText</a>
</div>
<div class="readonly typography" id="{$this->id()}" style="display: none;">$this->value
<a onclick="\$('{$this->id()}').style.display='none'; \$('{$this->id()}_reduced').style.display='inline'; return false;" href="#"> $this->lessText</a>
</div>
<br /><input type="hidden" name="$this->name" value="$valforInput" />
HTML;
} else {
$this->dontEscape = true;
return parent::Field();
}
}
function setMoreText($moreText) {
$this->moreText = $moreText;
}
function setLessText($lessText) {
$this->lessText = $lessText;
}
}
?>

51
forms/ToggleCompositeField.php Executable file
View File

@ -0,0 +1,51 @@
<?php
/**
* Allows visibility of a group of fields to be toggled using '+' and '-' icons
*/
class ToggleCompositeField extends CompositeField {
/**
* @var $headingLevel int
*/
public $headingLevel = 2;
function __construct($title, $children) {
$this->title = $title;
$this->name = ereg_replace('[^A-Za-z0-9]','',$this->title);
$this->startClosed(true);
parent::__construct($children);
}
public function FieldHolder() {
Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("sapphire/javascript/ToggleCompositeField.js");
return $this->renderWith("ToggleCompositeField");
}
/**
* Determines if the field should render open or closed by default.
*
* @param boolean
*/
public function startClosed($bool) {
($bool) ? $this->addExtraClass('startClosed') : $this->removeExtraClass('startClosed');
}
/**
* @return String
*/
public function HeadingLevel() {
return $this->headingLevel;
}
public function Type() {
return ' toggleCompositeField';
}
}
?>

99
forms/ToggleField.php Executable file
View File

@ -0,0 +1,99 @@
<?php
/**
* ReadonlyField with added toggle-capabilities - will preview the first sentence of the contained text-value,
* and show the full content by a javascript-switch.
*
* Caution: Strips HTML-encoding for the preview.
*/
class ToggleField extends ReadonlyField {
/**
* @var $labelMore string Text shown as a link to see the full content of the field
*/
public $labelMore;
/**
* @var $labelLess string Text shown as a link to see the partial view of the field content
*/
public $labelLess;
/**
* @var $truncateMethod string (FirstSentence|FirstParagraph) @see {Text}
*/
public $truncateMethod = 'FirstSentence';
/**
* @var $truncateChars int Number of chars to preview (optional).
* Truncating will be applied with $truncateMethod by default.
*/
public $truncateChars;
/**
* @param name The field name
* @param title The field title
* @param value The current value
*/
function __construct($name, $title = "", $value = "") {
$this->labelMore = _t('ToggleField.MORE', 'more');
$this->labelLess = _t('ToggleField.LESS', 'less');
$this->startClosed(true);
parent::__construct($name, $title, $value);
}
function Field() {
$content = '';
Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("sapphire/javascript/ToggleField.js");
if($this->startClosed) $this->addExtraClass('startClosed');
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
$rawInput = Convert::html2raw($valforInput);
if($this->charNum) $reducedVal = substr($rawInput,0,$this->charNum);
else $reducedVal = DBField::create('Text',$rawInput)->{$this->truncateMethod}();
// only create togglefield if the truncated content is shorter
if(strlen($reducedVal) < strlen($rawInput)) {
$content = <<<HTML
<div class="readonly typography contentLess" style="display: none">
$reducedVal
&nbsp;<a href="#" class="triggerMore">$this->labelMore</a>
</div>
<div class="readonly typography contentMore">
$this->value
&nbsp;<a href="#" class="triggerLess">$this->labelLess</a>
</div>
<br />
<input type="hidden" name="$this->name" value="$valforInput" />
HTML;
} else {
$this->dontEscape = true;
$content = parent::Field();
}
return $content;
}
/**
* Determines if the field should render open or closed by default.
*
* @param boolean
*/
public function startClosed($bool) {
($bool) ? $this->addExtraClass('startClosed') : $this->removeExtraClass('startClosed');
}
function Type() {
return "toggleField";
}
}
?>

View File

@ -1,41 +0,0 @@
<?php
/**
* Allows visibility of a group of fields to be toggled using + and - icons
*/
class TogglePanel extends CompositeField {
protected $closed = false;
function __construct($title, $children, $startClosed = false) {
$this->title = $title;
$this->closed = $startClosed;
$this->name = ereg_replace('[^A-Za-z0-9]','',$this->title);
parent::__construct($children);
}
public function FieldHolder() {
Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("sapphire/javascript/TogglePanel.js");
return $this->renderWith("TogglePanel");
}
public function setClosed($closed) {
$this->closed = $closed;
}
public function getClosed() {
return $this->closed;
}
public function ClosedClass() {
if($this->closed) return " closed";
}
public function ClosedStyle() {
if($this->closed) return "style=\"display: none\"";
}
}
?>

View File

@ -0,0 +1,26 @@
var ToggleCompositeField = Class.create();
ToggleCompositeField.prototype = {
initialize: function() {
var rules = {};
rules['#' + this.id + ' .trigger'] = {
onclick: function(e) {
this.toggle();
Event.stop(e); return false;
}.bind(this)
};
Behaviour.register(rules);
// close content by default
if(Element.hasClassName(this, 'startClosed')) {
Element.toggle($$('#' + this.id + ' .contentMore')[0]);
}
Element.toggle($$('#' + this.id + ' .triggerClosed')[0]);
},
toggle: function() {
Element.toggle($$('#' + this.id + ' .contentMore')[0]);
Element.toggle($$('#' + this.id + ' .triggerClosed')[0]);
Element.toggle($$('#' + this.id + ' .triggerOpened')[0]);
}
}
ToggleCompositeField.applyTo('div.toggleCompositeField');

29
javascript/ToggleField.js Normal file
View File

@ -0,0 +1,29 @@
var ToggleField = Class.create();
ToggleField.prototype = {
initialize: function() {
var rules = {};
rules['#' + this.id + ' .triggerMore'] = {
onclick: function(e) {
this.toggle();
Event.stop(e); return false;
}.bind(this)
};
rules['#' + this.id + ' .triggerLess'] = {
onclick: function(e) {
this.toggle();
Event.stop(e); return false;
}.bind(this)
};
Behaviour.register(rules);
if(Element.hasClassName(this, 'startClosed')) {
this.toggle();
}
},
toggle: function() {
Element.toggle($$('#' + this.id + ' .contentLess')[0]);
Element.toggle($$('#' + this.id + ' .contentMore')[0]);
}
}
ToggleField.applyTo('div.toggleField');

View File

@ -1,17 +0,0 @@
Behaviour.register({
'h2.TogglePanelHeader' : {
onclick : function() {
var contentDiv = $('panel_' + this.id);
var toggleID = this.id.replace('panel_','') + '_toggle';
Element.toggle(toggleID + '_closed');
Element.toggle(toggleID + '_open');
if(contentDiv.style.display == 'none') {
contentDiv.style.display = '';
Element.removeClassName(this, 'closed');
} else {
contentDiv.style.display = 'none';
Element.addClassName(this, 'closed');
}
}
}
});

View File

@ -0,0 +1,12 @@
<div id="$Name" class="$Type $extraClass">
<h$HeadingLevel style="cursor: pointer;" class="trigger$ClosedClass">
<img class="triggerClosed" src="sapphire/images/toggle-closed.gif" alt="+" style="display:none;" title="Show" />
<img class="triggerOpened" src="sapphire/images/toggle-open.gif" alt="-" style="display:none;" title="Hide" />
$Title
</h$HeadingLevel>
<div class="contentMore">
<% control FieldSet %>
$FieldHolder
<% end_control %>
</div>
</div>

View File

@ -1,10 +0,0 @@
<h2 id="$id" style="cursor: pointer;" class="TogglePanelHeader$ClosedClass">
<img id="{$id}_toggle_closed" src="sapphire/images/toggle-closed.gif" alt="+" title="Show" />
<img id="{$id}_toggle_open" src="sapphire/images/toggle-open.gif" alt="-" style="display:none;" title="Hide" />
$Title
</h2>
<div id="panel_$id" $ClosedStyle>
<% control FieldSet %>
$FieldHolder
<% end_control %>
</div>