From 00097a5d5d34a4e1277b2c0f1821a2302d70a04a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 4 Feb 2013 00:44:50 +0100 Subject: [PATCH] NEW Clickable URL preview in CMS - Refactored SiteTreeURLSegmentField to render controls in template rather than JS for better clientside performance, and cleaner behaviour. - Added dynamic ellipsis to start of URL, to retain most relevant part of the URL (the last bits) - Added "suffix" setting to field, which defaults to ?stage=Stage - Removed prefix from edit view to leave more room for URL Thanks to @sunnysideup for getting this started in https://github.com/silverstripe/silverstripe-cms/pull/269 --- code/forms/SiteTreeURLSegmentField.php | 23 +- code/model/SiteTree.php | 5 +- css/screen.css | 7 +- javascript/SiteTreeURLSegmentField.js | 240 +++++++-------------- scss/_CMSMain.scss | 13 +- templates/forms/SiteTreeURLSegmentField.ss | 22 +- 6 files changed, 128 insertions(+), 182 deletions(-) diff --git a/code/forms/SiteTreeURLSegmentField.php b/code/forms/SiteTreeURLSegmentField.php index 3345b77c..0cac40d2 100644 --- a/code/forms/SiteTreeURLSegmentField.php +++ b/code/forms/SiteTreeURLSegmentField.php @@ -15,7 +15,7 @@ class SiteTreeURLSegmentField extends TextField { /** * @var string */ - protected $helpText, $urlPrefix; + protected $helpText, $urlPrefix, $urlSuffix; static $allowed_actions = array( 'suggest' @@ -25,6 +25,16 @@ class SiteTreeURLSegmentField extends TextField { return rawurldecode($this->value); } + public function getAttributes() { + return array_merge( + parent::getAttributes(), + array( + 'data-prefix' => $this->getURLPrefix(), + 'data-suffix' => '?stage=Stage' + ) + ); + } + public function Field($properties = array()) { Requirements::javascript(CMS_DIR . '/javascript/SiteTreeURLSegmentField.js'); Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true); @@ -85,9 +95,20 @@ class SiteTreeURLSegmentField extends TextField { return $this->urlPrefix; } + public function getURLSuffix() { + return $this->urlSuffix; + } + + public function setURLSuffix($suffix) { + $this->urlSuffix = $suffix; + } public function Type() { return 'text urlsegment'; } + public function getURL() { + return Controller::join_links($this->getURLPrefix(), $this->Value(), $this->getURLSuffix()); + } + } diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index 6c838675..9b957ea5 100644 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -1829,11 +1829,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid (self::nested_urls() && $this->ParentID ? $this->Parent()->RelativeLink(true) : null) ); - - - $url = (strlen($baseLink) > 36) ? "..." .substr($baseLink, -32) : $baseLink; $urlsegment = new SiteTreeURLSegmentField("URLSegment", $this->fieldLabel('URLSegment')); - $urlsegment->setURLPrefix($url); + $urlsegment->setURLPrefix($baseLink); $helpText = (self::nested_urls() && count($this->Children())) ? $this->fieldLabel('LinkChangeNote') : ''; if(!URLSegmentFilter::$default_allow_multibyte) { $helpText .= $helpText ? '
' : ''; diff --git a/css/screen.css b/css/screen.css index de64d675..d15591d5 100644 --- a/css/screen.css +++ b/css/screen.css @@ -22,10 +22,11 @@ /** ------------------------------------------------------------------ URLSegment field ----------------------------------------------------------------- */ .field.urlsegment.loading { background: url(../images/loading.gif) no-repeat 162px 8px; } -.field.urlsegment .prefix, .field.urlsegment .preview { padding-top: 8px; display: inline-block; } -.field.urlsegment .prefix { color: #777; } -.field.urlsegment .cancel, .field.urlsegment .update, .field.urlsegment .edit { margin-left: 7px; } +.field.urlsegment .preview { padding-top: 8px; display: inline-block; } +.field.urlsegment input.text { width: 250px; } +.field.urlsegment input.text, .field.urlsegment .cancel, .field.urlsegment .update, .field.urlsegment .edit { margin-right: 8px; } .field.urlsegment .help { margin-left: 0; } +.field.urlsegment .edit-holder { display: none; } #Form_EditForm #Title .update { margin-left: 7px; } diff --git a/javascript/SiteTreeURLSegmentField.js b/javascript/SiteTreeURLSegmentField.js index 7971b934..450612cb 100644 --- a/javascript/SiteTreeURLSegmentField.js +++ b/javascript/SiteTreeURLSegmentField.js @@ -2,215 +2,125 @@ $.entwine('ss', function($) { /** * Class: .field.urlsegment - * - * Input validation on the URLSegment field + * + * Provides enhanced functionality (read-only/edit switch) and + * input validation on the URLSegment field */ $('.field.urlsegment:not(.readonly)').entwine({ - /** - * Constructor: onmatch - */ + // Roughly matches the field width including edit button + MaxPreviewLength: 55, + + Ellipsis: '...', + onmatch : function() { // Only initialize the field if it contains an editable field. // This ensures we don't get bogus previews on readonly fields. - if(this.find(':text').length) { - this._addActions(); // add elements and actions for editing - this.edit(); // toggle - this._autoInputWidth(); // set width of input field - } + if(this.find(':text').length) this.toggleEdit(false); + this.redraw(); this._super(); }, - onunmatch: function() { - this._super(); + + redraw: function() { + var field = this.find(':text'), + url = field.data('prefix') + field.val(), + previewUrl = url; + + // Truncate URL if required (ignoring the suffix, retaining the full value) + if(url.length > this.getMaxPreviewLength()) { + previewUrl = this.getEllipsis() + url.substr(url.length - this.getMaxPreviewLength(), url.length); + } + + // Transfer current value to holder + this.find('.preview').attr('href', url + field.data('suffix')).text(previewUrl); }, - + /** - * Function: edit - * - * Toggles the edit state of the field - * - * Return URLSegemnt val() - * - * Parameters: - * (Bool) auto (optional, triggers a second toggle) + * @param Boolean */ - edit: function(auto) { - - var field = this.find(':text'), - holder = this.find('.preview'), - edit = this.find('.edit'), - update = this.find('.update'), - cancel = this.find('.cancel'), - help = this.find('.help'); - - // transfer current value to holder - holder.text(field.val()); - - // toggle elements - if (field.is(':visible')) { - update.hide(); - cancel.hide(); - field.hide(); - holder.show(); - edit.show(); - help.hide(); - } else { - edit.hide(); - holder.hide(); - field.show(); - update.show(); - cancel.show(); - help.show(); + toggleEdit: function(toggle) { + var field = this.find(':text'); + + this.find('.preview-holder')[toggle ? 'hide' : 'show'](); + this.find('.edit-holder')[toggle ? 'show' : 'hide'](); + + if(toggle) { + field.data("origval", field.val()); //retain current value for cancel + field.focus(); } - - // field updated from another fields value - // reset to original state - if (auto) this.edit(); - - return field.val(); }, /** - * Function: update - * * Commits the change of the URLSegment to the field - * Optional: pass in (String) - * to update the URLSegment + * Optional: pass in (String) to update the URLSegment */ update: function() { - var self = this, field = this.find(':text'), - holder = this.find('.preview'), - currentVal = holder.text(), - updateVal, - title = arguments[0]; - - if (title && title !== "") { - updateVal = title; - } else { - updateVal = field.val(); - } + currentVal = field.data('origval'), + title = arguments[0], + updateVal = (title && title !== "") ? title : field.val(); if (currentVal != updateVal) { - self.addClass('loading'); - self.suggest(updateVal, function(data) { - var newVal = decodeURIComponent(data.value); - field.val(newVal); - self.edit(title); + this.addClass('loading'); + this.suggest(updateVal, function(data) { + field.val(decodeURIComponent(data.value)); + self.toggleEdit(false); self.removeClass('loading'); + self.redraw(); }); } else { - self.edit(); + this.toggleEdit(false); + this.redraw(); } }, /** - * Function: cancel - * * Cancels any changes to the field - * - * Return URLSegemnt val() - * */ cancel: function() { - var field = this.find(':text'), - holder = this.find('.preview'); - field.val(holder.text()); - this.edit(); - - return field.val(); + var field = this.find(':text'); + field.val(field.data("origval")); + this.toggleEdit(false); }, /** - * Function: suggest - * * Return a value matching the criteria. * - * Parameters: - * (String) val - * (Function) callback + * @param (String) + * @param (Function) */ suggest: function(val, callback) { - var field = this.find(':text'), urlParts = $.path.parseUrl(this.closest('form').attr('action')), + var field = this.find(':text'), + urlParts = $.path.parseUrl(this.closest('form').attr('action')), url = urlParts.hrefNoSearch + '/field/' + field.attr('name') + '/suggest/?value=' + encodeURIComponent(val); if(urlParts.search) url += '&' + urlParts.search.replace(/^\?/, ''); - $.get( - url, - function(data) {callback.apply(this, arguments);} - ); - - }, - - /** - * Function: _addActions - * - * Utility to add edit buttons and actions - * - */ - _addActions: function() { - var self = this, - field = this.find(':text'), - preview, - editAction, - updateAction, - cancelAction; - - // element to display non-editable text - preview = $('', { - 'class': 'preview' - }); - - // edit button - editAction = $(' + +
+ + + + <% if HelpText %>

$HelpText

<% end_if %> +
\ No newline at end of file