Skip to content
This repository
Browse code

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
#269
  • Loading branch information...
commit 00097a5d5d34a4e1277b2c0f1821a2302d70a04a 1 parent 931b726
Ingo Schommer authored February 04, 2013
23  code/forms/SiteTreeURLSegmentField.php
@@ -15,7 +15,7 @@ class SiteTreeURLSegmentField extends TextField {
15 15
 	/** 
16 16
 	 * @var string 
17 17
 	 */
18  
-	protected $helpText, $urlPrefix;
  18
+	protected $helpText, $urlPrefix, $urlSuffix;
19 19
 	
20 20
 	static $allowed_actions = array(
21 21
 		'suggest'
@@ -25,6 +25,16 @@ public function Value() {
25 25
 		return rawurldecode($this->value);
26 26
 	}
27 27
 
  28
+	public function getAttributes() {
  29
+		return array_merge(
  30
+			parent::getAttributes(),
  31
+			array(
  32
+				'data-prefix' => $this->getURLPrefix(),
  33
+				'data-suffix' => '?stage=Stage'
  34
+			)
  35
+		);
  36
+	}
  37
+
28 38
 	public function Field($properties = array()) {
29 39
 		Requirements::javascript(CMS_DIR . '/javascript/SiteTreeURLSegmentField.js');
30 40
 		Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true);
@@ -85,9 +95,20 @@ public function getURLPrefix(){
85 95
 		return $this->urlPrefix;
86 96
 	}
87 97
 	
  98
+	public function getURLSuffix() {
  99
+		return $this->urlSuffix;
  100
+	}
  101
+
  102
+	public function setURLSuffix($suffix) {
  103
+		$this->urlSuffix = $suffix;
  104
+	}
88 105
 
89 106
 	public function Type() {
90 107
 		return 'text urlsegment';
91 108
 	}
92 109
 
  110
+	public function getURL() {
  111
+		return Controller::join_links($this->getURLPrefix(), $this->Value(), $this->getURLSuffix());
  112
+	}
  113
+
93 114
 }
5  code/model/SiteTree.php
@@ -1829,11 +1829,8 @@ public function getCMSFields() {
1829 1829
 			(self::nested_urls() && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
1830 1830
 		);
1831 1831
 		
1832  
-		
1833  
-		
1834  
-		$url = (strlen($baseLink) > 36) ? "..." .substr($baseLink, -32) : $baseLink;
1835 1832
 		$urlsegment = new SiteTreeURLSegmentField("URLSegment", $this->fieldLabel('URLSegment'));
1836  
-		$urlsegment->setURLPrefix($url);
  1833
+		$urlsegment->setURLPrefix($baseLink);
1837 1834
 		$helpText = (self::nested_urls() && count($this->Children())) ? $this->fieldLabel('LinkChangeNote') : '';
1838 1835
 		if(!URLSegmentFilter::$default_allow_multibyte) {
1839 1836
 			$helpText .= $helpText ? '<br />' : '';
7  css/screen.css
@@ -22,10 +22,11 @@
22 22
 
23 23
 /** ------------------------------------------------------------------ URLSegment field ----------------------------------------------------------------- */
24 24
 .field.urlsegment.loading { background: url(../images/loading.gif) no-repeat 162px 8px; }
25  
-.field.urlsegment .prefix, .field.urlsegment .preview { padding-top: 8px; display: inline-block; }
26  
-.field.urlsegment .prefix { color: #777; }
27  
-.field.urlsegment .cancel, .field.urlsegment .update, .field.urlsegment .edit { margin-left: 7px; }
  25
+.field.urlsegment .preview { padding-top: 8px; display: inline-block; }
  26
+.field.urlsegment input.text { width: 250px; }
  27
+.field.urlsegment input.text, .field.urlsegment .cancel, .field.urlsegment .update, .field.urlsegment .edit { margin-right: 8px; }
28 28
 .field.urlsegment .help { margin-left: 0; }
  29
+.field.urlsegment .edit-holder { display: none; }
29 30
 
30 31
 #Form_EditForm #Title .update { margin-left: 7px; }
31 32
 
240  javascript/SiteTreeURLSegmentField.js
@@ -2,215 +2,125 @@
2 2
 	$.entwine('ss', function($) {
3 3
 		/**
4 4
 		 * Class: .field.urlsegment
5  
-		 * 
6  
-		 * Input validation on the URLSegment field
  5
+		 *
  6
+		 * Provides enhanced functionality (read-only/edit switch) and
  7
+		 * input validation on the URLSegment field
7 8
 		 */
8 9
 		$('.field.urlsegment:not(.readonly)').entwine({
9 10
 	
10  
-			/**
11  
-			 * Constructor: onmatch
12  
-			 */
  11
+			// Roughly matches the field width including edit button
  12
+			MaxPreviewLength: 55,
  13
+
  14
+			Ellipsis: '...',
  15
+
13 16
 			onmatch : function() {
14 17
 				// Only initialize the field if it contains an editable field.
15 18
 				// This ensures we don't get bogus previews on readonly fields.
16  
-				if(this.find(':text').length) {
17  
-					this._addActions(); // add elements and actions for editing
18  
-					this.edit(); // toggle
19  
-					this._autoInputWidth(); // set width of input field
20  
-				}
  19
+				if(this.find(':text').length) this.toggleEdit(false);
  20
+				this.redraw();
21 21
 				
22 22
 				this._super();
23 23
 			},
24  
-			onunmatch: function() {
25  
-				this._super();
  24
+
  25
+			redraw: function() {
  26
+				var field = this.find(':text'), 
  27
+					url = field.data('prefix') + field.val(),
  28
+					previewUrl = url;
  29
+
  30
+				// Truncate URL if required (ignoring the suffix, retaining the full value)
  31
+				if(url.length > this.getMaxPreviewLength()) {
  32
+					previewUrl = this.getEllipsis() + url.substr(url.length - this.getMaxPreviewLength(), url.length);
  33
+				}
  34
+
  35
+				// Transfer current value to holder
  36
+				this.find('.preview').attr('href', url + field.data('suffix')).text(previewUrl);
26 37
 			},
27  
-			
  38
+
28 39
 			/**
29  
-			 * Function: edit
30  
-			 *  
31  
-			 * Toggles the edit state of the field
32  
-			 * 
33  
-			 * Return URLSegemnt val()
34  
-			 * 
35  
-			 * Parameters:
36  
-			 *  (Bool) auto (optional, triggers a second toggle)
  40
+			 * @param Boolean
37 41
 			 */
38  
-			edit: function(auto) {
39  
-				
40  
-				var field = this.find(':text'),
41  
-					holder = this.find('.preview'),
42  
-					edit = this.find('.edit'),
43  
-					update = this.find('.update'),
44  
-					cancel = this.find('.cancel'),
45  
-					help = this.find('.help');
46  
-				
47  
-				// transfer current value to holder
48  
-				holder.text(field.val());
49  
-				
50  
-				// toggle elements
51  
-				if (field.is(':visible')) {
52  
-					update.hide();
53  
-					cancel.hide();
54  
-					field.hide();
55  
-					holder.show();
56  
-					edit.show();
57  
-					help.hide();
58  
-				} else {
59  
-					edit.hide();
60  
-					holder.hide();
61  
-					field.show();
62  
-					update.show();
63  
-					cancel.show();
64  
-					help.show();
  42
+			toggleEdit: function(toggle) {
  43
+				var field = this.find(':text');
  44
+
  45
+				this.find('.preview-holder')[toggle ? 'hide' : 'show']();
  46
+				this.find('.edit-holder')[toggle ? 'show' : 'hide']();
  47
+
  48
+				if(toggle) {
  49
+					field.data("origval", field.val()); //retain current value for cancel
  50
+					field.focus();
65 51
 				}
66  
-				
67  
-				// field updated from another fields value
68  
-				// reset to original state
69  
-				if (auto) this.edit();
70  
-				
71  
-				return field.val();
72 52
 			},
73 53
 			
74 54
 			/**
75  
-			 * Function: update
76  
-			 *  
77 55
 			 * Commits the change of the URLSegment to the field
78  
-			 * Optional: pass in (String)
79  
-			 * to update the URLSegment
  56
+			 * Optional: pass in (String) to update the URLSegment
80 57
 			 */
81 58
 			update: function() {
82  
-				
83 59
 				var self = this,
84 60
 					field = this.find(':text'),
85  
-					holder = this.find('.preview'),
86  
-					currentVal = holder.text(),
87  
-					updateVal,
88  
-					title = arguments[0];
89  
-				
90  
-				if (title && title !== "") {
91  
-					updateVal = title;
92  
-				} else {
93  
-					updateVal = field.val();
94  
-				}
  61
+					currentVal = field.data('origval'),
  62
+					title = arguments[0],
  63
+					updateVal = (title && title !== "") ? title : field.val();
95 64
 				
96 65
 				if (currentVal != updateVal) {
97  
-					self.addClass('loading');
98  
-					self.suggest(updateVal, function(data) {
99  
-						var newVal = decodeURIComponent(data.value);
100  
-						field.val(newVal);
101  
-						self.edit(title);
  66
+					this.addClass('loading');
  67
+					this.suggest(updateVal, function(data) {
  68
+						field.val(decodeURIComponent(data.value));
  69
+						self.toggleEdit(false);
102 70
 						self.removeClass('loading');
  71
+						self.redraw();
103 72
 					});
104 73
 				} else {
105  
-					self.edit();
  74
+					this.toggleEdit(false);
  75
+					this.redraw();
106 76
 				}
107 77
 			},
108 78
 			
109 79
 			/**
110  
-			 * Function: cancel
111  
-			 *  
112 80
 			 * Cancels any changes to the field
113  
-			 *
114  
-			 * Return URLSegemnt val()
115  
-			 *
116 81
 			 */
117 82
 			cancel: function() {
118  
-				var field = this.find(':text'),
119  
-					holder = this.find('.preview');
120  
-					field.val(holder.text());
121  
-					this.edit();
122  
-					
123  
-				return field.val();
  83
+				var field = this.find(':text');
  84
+				field.val(field.data("origval"));
  85
+				this.toggleEdit(false);
124 86
 			},
125 87
 	
126 88
 			/**
127  
-			 * Function: suggest
128  
-			 *  
129 89
 			 * Return a value matching the criteria.
130 90
 			 * 
131  
-			 * Parameters:
132  
-			 *  (String) val
133  
-			 *  (Function) callback
  91
+			 * @param (String)
  92
+			 * @param (Function)
134 93
 			 */
135 94
 			suggest: function(val, callback) {
136  
-				var field = this.find(':text'), urlParts = $.path.parseUrl(this.closest('form').attr('action')),
  95
+				var field = this.find(':text'),
  96
+					urlParts = $.path.parseUrl(this.closest('form').attr('action')),
137 97
 					url = urlParts.hrefNoSearch + '/field/' + field.attr('name') + '/suggest/?value=' + encodeURIComponent(val);
138 98
 				if(urlParts.search) url += '&' + urlParts.search.replace(/^\?/, '');
139 99
 
140  
-				$.get(
141  
-					url,
142  
-					function(data) {callback.apply(this, arguments);}
143  
-				);
144  
-				
145  
-			},
146  
-			
147  
-			/**
148  
-			 * Function: _addActions
149  
-			 *  
150  
-			 * Utility to add edit buttons and actions
151  
-			 * 
152  
-			 */
153  
-			_addActions: function() {
154  
-				var self = this,
155  
-					field = this.find(':text'),
156  
-					preview,
157  
-					editAction,
158  
-					updateAction,
159  
-					cancelAction;
160  
-					
161  
-				// element to display non-editable text
162  
-				preview = $('<span />', {
163  
-					'class': 'preview'
164  
-				});
165  
-				
166  
-				// edit button
167  
-				editAction = $('<button />', {
168  
-					'class': 'ss-ui-button ss-ui-button-small edit',
169  
-					'text': ss.i18n._t('URLSEGMENT.Edit', 'Edit'),
170  
-					'click': function(e) {
171  
-						e.preventDefault();
172  
-						self.edit();
173  
-						self.find(':text').focus();
174  
-					}
175  
-				});
176  
-				
177  
-				// update button
178  
-				updateAction = $('<button />', {
179  
-					'class': 'update ss-ui-button-small',
180  
-					'text': ss.i18n._t('URLSEGMENT.OK', 'OK'),
181  
-					'click': function(e) {
182  
-						e.preventDefault();
183  
-						self.update();
184  
-					}
185  
-				});
186  
-				
187  
-				// cancel button
188  
-				cancelAction = $('<button />', {
189  
-					'class': 'cancel ss-ui-action-minor ss-ui-button-small',
190  
-					'href': '#',
191  
-					'text':  ss.i18n._t('URLSEGMENT.Cancel', 'Cancel'),
192  
-					'click': function(e) {
193  
-						e.preventDefault();
194  
-						self.cancel();
195  
-					}
196  
-				});
197  
-				
198  
-				// insert elements
199  
-				preview.insertAfter('.prefix');
200  
-				editAction.insertAfter(field);
201  
-				cancelAction.insertAfter(field);
202  
-				updateAction.insertAfter(field);
203  
-			},
204  
-			
205  
-			/**
206  
-			 * Function: _autoInputWidth
207  
-			 *
208  
-			 * Sets the width of input so it lines up with the other fields
209  
-			 */
210  
-			_autoInputWidth: function() {
211  
-				var field = this.find(':text');
212  
-				field.width((field.width() + 15) - this.find('.prefix').width());
  100
+				$.get(url, function(data) {callback.apply(this, arguments);});
  101
+			}
  102
+		});
  103
+
  104
+		$('.field.urlsegment .edit').entwine({
  105
+			onclick: function(e) {
  106
+				e.preventDefault();
  107
+				this.closest('.field').toggleEdit(true);
  108
+			}
  109
+		});
  110
+
  111
+		$('.field.urlsegment .update').entwine({
  112
+			onclick: function(e) {
  113
+				e.preventDefault();
  114
+				this.closest('.field').update();
  115
+			}
  116
+		});
  117
+
  118
+		$('.field.urlsegment .cancel').entwine({
  119
+			onclick: function(e) {
  120
+				e.preventDefault();
  121
+				this.closest('.field').cancel();
213 122
 			}
214 123
 		});
215 124
 	});
  125
+
216 126
 }(jQuery));
13  scss/_CMSMain.scss
@@ -98,23 +98,26 @@
98 98
 		background: url(../images/loading.gif) no-repeat 162px 8px;
99 99
 	}
100 100
 
101  
-	.prefix,
102 101
 	.preview {
103 102
 		padding-top: 8px;
104 103
 		display: inline-block;
105 104
 	}
106 105
 
107  
-	.prefix {
108  
-		color: #777;
  106
+	input.text {
  107
+		width: 250px; // ensure there's enough room for buttons
109 108
 	}
110 109
 
111  
-	.cancel, .update, .edit {
112  
-		margin-left: 7px;
  110
+	input.text, .cancel, .update, .edit {
  111
+		margin-right: 8px;
113 112
 	}
114 113
 
115 114
 	.help {
116 115
 		margin-left: 0;
117 116
 	}
  117
+
  118
+	.edit-holder {
  119
+		display: none;
  120
+	}
118 121
 }
119 122
 
120 123
 #Form_EditForm #Title .update {
22  templates/forms/SiteTreeURLSegmentField.ss
... ...
@@ -1,4 +1,18 @@
1  
-<span class="prefix">$URLPrefix</span><input $AttributesHTML />
2  
-<% if HelpText %>
3  
-<p class="help">$HelpText</p>
4  
-<% end_if %>
  1
+<div class="preview-holder">
  2
+	<a class="preview" href="$URL" target="_blank">
  3
+		$URL
  4
+	</a>
  5
+	<button class="ss-ui-button ss-ui-button-small edit">
  6
+		<% _t('URLSegmentField.Edit', 'Edit') %>
  7
+	</button>
  8
+</div>
  9
+<div class="edit-holder">
  10
+	<input $AttributesHTML />	
  11
+	<button class="update ss-ui-button-small">
  12
+		<% _t('URLSegmentField.OK', 'OK') %>
  13
+	</button>
  14
+	<button class="cancel ss-ui-button-small ss-ui-action-minor">
  15
+		<% _t('URLSegmentField.Cancel', 'Cancel') %>
  16
+	</button>
  17
+	<% if HelpText %><p class="help">$HelpText</p><% end_if %>
  18
+</div>

0 notes on commit 00097a5

Please sign in to comment.
Something went wrong with that request. Please try again.