1 /* |
1 /** |
|
2 * Functions dedicated to widgets. |
|
3 * |
2 * :organization: Logilab |
4 * :organization: Logilab |
3 * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
5 * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
4 * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
6 * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
5 * |
7 * |
6 * |
8 * |
7 */ |
9 */ |
8 |
10 |
9 // widget namespace |
11 // widget namespace |
10 Widgets = {}; |
12 Widgets = {}; |
11 |
13 |
12 |
14 /** |
13 /* this function takes a DOM node defining a widget and |
15 * .. function:: buildWidget(wdgnode) |
|
16 * |
|
17 * this function takes a DOM node defining a widget and |
14 * instantiates / builds the appropriate widget class |
18 * instantiates / builds the appropriate widget class |
15 */ |
19 */ |
16 function buildWidget(wdgnode) { |
20 function buildWidget(wdgnode) { |
17 var wdgclass = Widgets[wdgnode.getAttribute('cubicweb:wdgtype')]; |
21 var wdgclass = Widgets[wdgnode.getAttribute('cubicweb:wdgtype')]; |
18 if (wdgclass) { |
22 if (wdgclass) { |
19 var wdg = new wdgclass(wdgnode); |
23 var wdg = new wdgclass(wdgnode); |
20 } |
24 } |
21 } |
25 } |
22 |
26 |
23 /* This function is called on load and is in charge to build |
27 /** |
|
28 * .. function:: buildWidgets(root) |
|
29 * |
|
30 * This function is called on load and is in charge to build |
24 * JS widgets according to DOM nodes found in the page |
31 * JS widgets according to DOM nodes found in the page |
25 */ |
32 */ |
26 function buildWidgets(root) { |
33 function buildWidgets(root) { |
27 root = root || document; |
34 root = root || document; |
28 jQuery(root).find('.widget').each(function() { |
35 jQuery(root).find('.widget').each(function() { |
29 if (this.getAttribute('cubicweb:loadtype') == 'auto') { |
36 if (this.getAttribute('cubicweb:loadtype') == 'auto') { |
30 buildWidget(this); |
37 buildWidget(this); |
31 } |
38 } |
32 }); |
39 }); |
33 } |
40 } |
34 |
|
35 |
41 |
36 // we need to differenciate cases where initFacetBoxEvents is called |
42 // we need to differenciate cases where initFacetBoxEvents is called |
37 // with one argument or without any argument. If we use `initFacetBoxEvents` |
43 // with one argument or without any argument. If we use `initFacetBoxEvents` |
38 // as the direct callback on the jQuery.ready event, jQuery will pass some argument |
44 // as the direct callback on the jQuery.ready event, jQuery will pass some argument |
39 // of his, so we use this small anonymous function instead. |
45 // of his, so we use this small anonymous function instead. |
40 jQuery(document).ready(function() {buildWidgets();}); |
46 jQuery(document).ready(function() { |
41 |
47 buildWidgets(); |
|
48 }); |
|
49 |
|
50 function postJSON(url, data, callback) { |
|
51 return jQuery.post(url, data, callback, 'json'); |
|
52 } |
|
53 |
|
54 function getJSON(url, data, callback) { |
|
55 return jQuery.get(url, data, callback, 'json'); |
|
56 } |
42 |
57 |
43 Widgets.SuggestField = defclass('SuggestField', null, { |
58 Widgets.SuggestField = defclass('SuggestField', null, { |
44 __init__: function(node, options) { |
59 __init__: function(node, options) { |
45 var multi = node.getAttribute('cubicweb:multi') || "no"; |
60 var multi = node.getAttribute('cubicweb:multi') || "no"; |
46 options = options || {}; |
61 options = options || {}; |
47 options.multiple = (multi == "yes") ? true : false; |
62 options.multiple = (multi == "yes") ? true: false; |
48 var dataurl = node.getAttribute('cubicweb:dataurl'); |
63 var dataurl = node.getAttribute('cubicweb:dataurl'); |
49 var method = postJSON; |
64 var method = postJSON; |
50 if (options.method == 'get'){ |
65 if (options.method == 'get') { |
51 method = function(url, data, callback) { |
66 method = function(url, data, callback) { |
52 // We can't rely on jQuery.getJSON because the server |
67 // We can't rely on jQuery.getJSON because the server |
53 // might set the Content-Type's response header to 'text/plain' |
68 // might set the Content-Type's response header to 'text/plain' |
54 jQuery.get(url, data, function(response) { |
69 jQuery.get(url, data, function(response) { |
55 callback(evalJSON(response)); |
70 callback(cw.evalJSON(response)); |
56 }); |
71 }); |
57 }; |
72 }; |
58 } |
73 } |
59 var self = this; // closure |
74 var self = this; // closure |
60 method(dataurl, null, function(data) { |
75 method(dataurl, null, function(data) { |
61 // in case we received a list of couple, we assume that the first |
76 // in case we received a list of couple, we assume that the first |
62 // element is the real value to be sent, and the second one is the |
77 // element is the real value to be sent, and the second one is the |
63 // value to be displayed |
78 // value to be displayed |
64 if (data.length && data[0].length == 2) { |
79 if (data.length && data[0].length == 2) { |
65 options.formatItem = function(row) { return row[1]; }; |
80 options.formatItem = function(row) { |
66 self.hideRealValue(node); |
81 return row[1]; |
67 self.setCurrentValue(node, data); |
82 }; |
68 } |
83 self.hideRealValue(node); |
69 jQuery(node).autocomplete(data, options); |
84 self.setCurrentValue(node, data); |
70 }); |
85 } |
|
86 jQuery(node).autocomplete(data, options); |
|
87 }); |
71 }, |
88 }, |
72 |
89 |
73 hideRealValue: function(node) { |
90 hideRealValue: function(node) { |
74 var hidden = INPUT({'type': "hidden", 'name': node.name, 'value': node.value}); |
91 var hidden = INPUT({ |
75 node.parentNode.appendChild(hidden); |
92 'type': "hidden", |
76 // remove 'name' attribute from visible input so that it is not submitted |
93 'name': node.name, |
77 // and set correct value in the corresponding hidden field |
94 'value': node.value |
78 jQuery(node).removeAttr('name').bind('result', function(_, row, _) { |
95 }); |
79 hidden.value = row[0]; |
96 node.parentNode.appendChild(hidden); |
80 }); |
97 // remove 'name' attribute from visible input so that it is not submitted |
|
98 // and set correct value in the corresponding hidden field |
|
99 jQuery(node).removeAttr('name').bind('result', function(_, row, _) { |
|
100 hidden.value = row[0]; |
|
101 }); |
81 }, |
102 }, |
82 |
103 |
83 setCurrentValue: function(node, data) { |
104 setCurrentValue: function(node, data) { |
84 // called when the data is loaded to reset the correct displayed |
105 // called when the data is loaded to reset the correct displayed |
85 // value in the visible input field (typically replacing an eid |
106 // value in the visible input field (typically replacing an eid |
86 // by a displayable value) |
107 // by a displayable value) |
87 var curvalue = node.value; |
108 var curvalue = node.value; |
88 if (!node.value) { |
109 if (!node.value) { |
89 return; |
110 return; |
90 } |
111 } |
91 for (var i=0,length=data.length; i<length; i++) { |
112 for (var i = 0, length = data.length; i < length; i++) { |
92 var row = data[i]; |
113 var row = data[i]; |
93 if (row[0] == curvalue) { |
114 if (row[0] == curvalue) { |
94 node.value = row[1]; |
115 node.value = row[1]; |
95 return; |
116 return; |
96 } |
117 } |
97 } |
118 } |
98 } |
119 } |
99 }); |
120 }); |
100 |
121 |
101 Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], { |
122 Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], { |
102 |
123 |
103 __init__ : function(node) { |
124 __init__: function(node) { |
104 Widgets.SuggestField.__init__(this, node, {method: 'get'}); |
125 Widgets.SuggestField.__init__(this, node, { |
|
126 method: 'get' |
|
127 }); |
105 } |
128 } |
106 |
129 |
107 }); |
130 }); |
108 |
131 |
109 Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], { |
132 Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], { |
110 |
133 |
111 __init__ : function(node) { |
134 __init__: function(node) { |
112 Widgets.SuggestField.__init__(this, node, {mustMatch: true}); |
135 Widgets.SuggestField.__init__(this, node, { |
|
136 mustMatch: true |
|
137 }); |
113 } |
138 } |
114 |
139 |
115 }); |
140 }); |
116 //remote version of RestrictedSuggestField |
141 //remote version of RestrictedSuggestField |
117 Widgets.LazySuggestField = defclass('LazySuggestField', [Widgets.SuggestField], { |
142 Widgets.LazySuggestField = defclass('LazySuggestField', [Widgets.SuggestField], { |
118 __init__: function(node, options) { |
143 __init__: function(node, options) { |
119 var self = this; |
144 var self = this; |
120 var multi = "no"; |
145 var multi = "no"; |
121 options = options || {}; |
146 options = options || {}; |
122 options.max = 50; |
147 options.max = 50; |
123 options.delay = 50; |
148 options.delay = 50; |
124 options.cacheLength=0; |
149 options.cacheLength = 0; |
125 options.mustMatch = true; |
150 options.mustMatch = true; |
126 // multiple selection not supported yet (still need to formalize correctly |
151 // multiple selection not supported yet (still need to formalize correctly |
127 // initial values / display values) |
152 // initial values / display values) |
128 var initialvalue = evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null'); |
153 var initialvalue = cw.evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null'); |
129 if (!initialvalue) { |
154 if (!initialvalue) { |
130 initialvalue = node.value; |
155 initialvalue = node.value; |
131 } |
156 } |
132 options = jQuery.extend({dataType: 'json', |
157 options = jQuery.extend({ |
133 multiple: (multi == "yes") ? true : false, |
158 dataType: 'json', |
134 parse: this.parseResult |
159 multiple: (multi == "yes") ? true: false, |
135 }, options); |
160 parse: this.parseResult |
|
161 }, |
|
162 options); |
136 var dataurl = node.getAttribute('cubicweb:dataurl'); |
163 var dataurl = node.getAttribute('cubicweb:dataurl'); |
137 // remove 'name' from original input and add the hidden one that will |
164 // remove 'name' from original input and add the hidden one that will |
138 // store the actual value |
165 // store the actual value |
139 var hidden = INPUT({'type': "hidden", 'name': node.name, 'value': initialvalue}); |
166 var hidden = INPUT({ |
|
167 'type': "hidden", |
|
168 'name': node.name, |
|
169 'value': initialvalue |
|
170 }); |
140 node.parentNode.appendChild(hidden); |
171 node.parentNode.appendChild(hidden); |
141 jQuery(node).bind('result', {hinput: hidden, input:node}, self.hideRealValue) |
172 jQuery(node).bind('result', { |
142 .removeAttr('name').autocomplete(dataurl, options); |
173 hinput: hidden, |
143 }, |
174 input: node |
144 |
175 }, |
|
176 self.hideRealValue).removeAttr('name').autocomplete(dataurl, options); |
|
177 }, |
145 |
178 |
146 hideRealValue: function(evt, data, value) { |
179 hideRealValue: function(evt, data, value) { |
147 if (!value){ |
180 if (!value) { |
148 value=""; |
181 value = ""; |
149 } |
182 } |
150 evt.data.hinput.value = value; |
183 evt.data.hinput.value = value; |
151 }, |
184 }, |
152 |
185 |
153 /* |
186 /* |
154 * @param data: a list of couple (value, label) to fill the suggestion list, |
187 * @param data: a list of couple (value, label) to fill the suggestion list, |
155 * (returned by CW through AJAX) |
188 * (returned by CW through AJAX) |
156 */ |
189 */ |
157 parseResult: function(data) { |
190 parseResult: function(data) { |
158 var parsed = []; |
191 var parsed = []; |
159 for (var i=0; i < data.length; i++) { |
192 for (var i = 0; i < data.length; i++) { |
160 var value = ''+data[i][0]; // a string is required later by jquery.autocomplete.js |
193 var value = '' + data[i][0]; // a string is required later by jquery.autocomplete.js |
161 var label = data[i][1]; |
194 var label = data[i][1]; |
162 parsed[parsed.length] = { |
195 parsed[parsed.length] = { |
163 data: [label], |
196 data: [label], |
164 value: value, |
197 value: value, |
165 result: label |
198 result: label |
166 }; |
199 }; |
167 }; |
200 }; |
168 return parsed; |
201 return parsed; |
169 } |
202 } |
170 |
203 |
171 }); |
204 }); |
172 |
205 |
173 /* |
206 /** |
|
207 * .. class:: Widgets.SuggestForm |
|
208 * |
174 * suggestform displays a suggest field and associated validate / cancel buttons |
209 * suggestform displays a suggest field and associated validate / cancel buttons |
175 * constructor's argumemts are the same that BaseSuggestField widget |
210 * constructor's argumemts are the same that BaseSuggestField widget |
176 */ |
211 */ |
177 Widgets.SuggestForm = defclass("SuggestForm", null, { |
212 Widgets.SuggestForm = defclass("SuggestForm", null, { |
178 |
213 |
179 __init__ : function(inputid, initfunc, varargs, validatefunc, options) { |
214 __init__: function(inputid, initfunc, varargs, validatefunc, options) { |
180 this.validatefunc = validatefunc || noop; |
215 this.validatefunc = validatefunc || noop; |
181 this.sgfield = new Widgets.BaseSuggestField(inputid, initfunc, |
216 this.sgfield = new Widgets.BaseSuggestField(inputid, initfunc, varargs, options); |
182 varargs, options); |
217 this.oklabel = options.oklabel || 'ok'; |
183 this.oklabel = options.oklabel || 'ok'; |
218 this.cancellabel = options.cancellabel || 'cancel'; |
184 this.cancellabel = options.cancellabel || 'cancel'; |
219 bindMethods(this); |
185 bindMethods(this); |
220 connect(this.sgfield, 'validate', this, this.entryValidated); |
186 connect(this.sgfield, 'validate', this, this.entryValidated); |
221 }, |
187 }, |
222 |
188 |
223 show: function(parentnode) { |
189 show : function(parentnode) { |
224 var sgnode = this.sgfield.builddom(); |
190 var sgnode = this.sgfield.builddom(); |
225 var buttons = DIV({ |
191 var buttons = DIV({'class' : "sgformbuttons"}, |
226 'class': "sgformbuttons" |
192 [A({'href' : "javascript: noop();", |
227 }, |
193 'onclick' : this.onValidateClicked}, this.oklabel), |
228 [A({ |
194 ' / ', |
229 'href': "javascript: noop();", |
195 A({'href' : "javascript: noop();", |
230 'onclick': this.onValidateClicked |
196 'onclick' : this.destroy}, escapeHTML(this.cancellabel))]); |
231 }, |
197 var formnode = DIV({'class' : "sgform"}, [sgnode, buttons]); |
232 this.oklabel), ' / ', A({ |
198 appendChildNodes(parentnode, formnode); |
233 'href': "javascript: noop();", |
199 this.sgfield.textinput.focus(); |
234 'onclick': this.destroy |
200 this.formnode = formnode; |
235 }, |
201 return formnode; |
236 escapeHTML(this.cancellabel))]); |
202 }, |
237 var formnode = DIV({ |
203 |
238 'class': "sgform" |
204 destroy : function() { |
239 }, |
205 signal(this, 'destroy'); |
240 [sgnode, buttons]); |
206 this.sgfield.destroy(); |
241 appendChildNodes(parentnode, formnode); |
207 removeElement(this.formnode); |
242 this.sgfield.textinput.focus(); |
208 }, |
243 this.formnode = formnode; |
209 |
244 return formnode; |
210 onValidateClicked : function() { |
245 }, |
211 this.validatefunc(this, this.sgfield.taglist()); |
246 |
|
247 destroy: function() { |
|
248 signal(this, 'destroy'); |
|
249 this.sgfield.destroy(); |
|
250 removeElement(this.formnode); |
|
251 }, |
|
252 |
|
253 onValidateClicked: function() { |
|
254 this.validatefunc(this, this.sgfield.taglist()); |
212 }, |
255 }, |
213 /* just an indirection to pass the form instead of the sgfield as first parameter */ |
256 /* just an indirection to pass the form instead of the sgfield as first parameter */ |
214 entryValidated : function(sgfield, taglist) { |
257 entryValidated: function(sgfield, taglist) { |
215 this.validatefunc(this, taglist); |
258 this.validatefunc(this, taglist); |
216 } |
259 } |
217 }); |
260 }); |
218 |
261 |
219 |
262 /** |
220 /* called when the use clicks on a tree node |
263 * .. function:: toggleTree(event) |
|
264 * |
|
265 * called when the use clicks on a tree node |
221 * - if the node has a `cubicweb:loadurl` attribute, replace the content of the node |
266 * - if the node has a `cubicweb:loadurl` attribute, replace the content of the node |
222 * by the url's content. |
267 * by the url's content. |
223 * - else, there's nothing to do, let the jquery plugin handle it. |
268 * - else, there's nothing to do, let the jquery plugin handle it. |
224 */ |
269 */ |
225 function toggleTree(event) { |
270 function toggleTree(event) { |
226 var linode = jQuery(this); |
271 var linode = jQuery(this); |
227 var url = linode.attr('cubicweb:loadurl'); |
272 var url = linode.attr('cubicweb:loadurl'); |
228 if (url) { |
273 if (url) { |
229 linode.find('ul.placeholder').remove(); |
274 linode.find('ul.placeholder').remove(); |
230 linode.loadxhtml(url, {callback: function(domnode) { |
275 linode.loadxhtml(url, { |
231 linode.removeAttr('cubicweb:loadurl'); |
276 callback: function(domnode) { |
232 jQuery(domnode).treeview({toggle: toggleTree, |
277 linode.removeAttr('cubicweb:loadurl'); |
233 prerendered: true}); |
278 jQuery(domnode).treeview({ |
234 return null; |
279 toggle: toggleTree, |
235 }}, 'post', 'append'); |
280 prerendered: true |
|
281 }); |
|
282 return null; |
|
283 } |
|
284 }, |
|
285 'post', 'append'); |
236 } |
286 } |
237 } |
287 } |
238 |
288 |
239 |
289 /** |
240 /* widget based on SIMILE's timeline widget |
290 * .. class:: Widgets.TimelineWidget |
|
291 * |
|
292 * widget based on SIMILE's timeline widget |
241 * http://code.google.com/p/simile-widgets/ |
293 * http://code.google.com/p/simile-widgets/ |
242 * |
294 * |
243 * Beware not to mess with SIMILE's Timeline JS namepsace ! |
295 * Beware not to mess with SIMILE's Timeline JS namepsace ! |
244 */ |
296 */ |
245 |
297 |
246 Widgets.TimelineWidget = defclass("TimelineWidget", null, { |
298 Widgets.TimelineWidget = defclass("TimelineWidget", null, { |
247 __init__: function (wdgnode) { |
299 __init__: function(wdgnode) { |
248 var tldiv = DIV({id: "tl", style: 'height: 200px; border: 1px solid #ccc;'}); |
300 var tldiv = DIV({ |
249 wdgnode.appendChild(tldiv); |
301 id: "tl", |
250 var tlunit = wdgnode.getAttribute('cubicweb:tlunit') || 'YEAR'; |
302 style: 'height: 200px; border: 1px solid #ccc;' |
251 var eventSource = new Timeline.DefaultEventSource(); |
303 }); |
252 var bandData = { |
304 wdgnode.appendChild(tldiv); |
253 eventPainter: Timeline.CubicWebEventPainter, |
305 var tlunit = wdgnode.getAttribute('cubicweb:tlunit') || 'YEAR'; |
254 eventSource: eventSource, |
306 var eventSource = new Timeline.DefaultEventSource(); |
255 width: "100%", |
307 var bandData = { |
256 intervalUnit: Timeline.DateTime[tlunit.toUpperCase()], |
308 eventPainter: Timeline.CubicWebEventPainter, |
257 intervalPixels: 100 |
309 eventSource: eventSource, |
258 }; |
310 width: "100%", |
259 var bandInfos = [ Timeline.createBandInfo(bandData) ]; |
311 intervalUnit: Timeline.DateTime[tlunit.toUpperCase()], |
260 var tl = Timeline.create(tldiv, bandInfos); |
312 intervalPixels: 100 |
261 var loadurl = wdgnode.getAttribute('cubicweb:loadurl'); |
313 }; |
262 Timeline.loadJSON(loadurl, function(json, url) { |
314 var bandInfos = [Timeline.createBandInfo(bandData)]; |
263 eventSource.loadJSON(json, url); }); |
315 var tl = Timeline.create(tldiv, bandInfos); |
|
316 var loadurl = wdgnode.getAttribute('cubicweb:loadurl'); |
|
317 Timeline.loadJSON(loadurl, function(json, url) { |
|
318 eventSource.loadJSON(json, url); |
|
319 }); |
264 |
320 |
265 } |
321 } |
266 }); |
322 }); |
267 |
323 |
268 Widgets.TemplateTextField = defclass("TemplateTextField", null, { |
324 Widgets.TemplateTextField = defclass("TemplateTextField", null, { |
269 |
325 |
270 __init__ : function(wdgnode) { |
326 __init__: function(wdgnode) { |
271 this.variables = getNodeAttribute(wdgnode, 'cubicweb:variables').split(','); |
327 this.variables = jQuery(wdgnode).attr('cubicweb:variables').split(','); |
272 this.options = {'name' : wdgnode.getAttribute('cubicweb:inputid'), |
328 this.options = { |
273 'rows' : wdgnode.getAttribute('cubicweb:rows') || 40, |
329 name: wdgnode.getAttribute('cubicweb:inputid'), |
274 'cols' : wdgnode.getAttribute('cubicweb:cols') || 80 |
330 rows: wdgnode.getAttribute('cubicweb:rows') || 40, |
275 }; |
331 cols: wdgnode.getAttribute('cubicweb:cols') || 80 |
276 // this.variableRegexp = /%\((\w+)\)s/; |
332 }; |
277 this.errorField = DIV({'class' : "errorMessage"}); |
333 // this.variableRegexp = /%\((\w+)\)s/; |
278 this.textField = TEXTAREA(this.options); |
334 this.errorField = DIV({ |
279 jQuery(this.textField).bind('keyup', {'self': this}, this.highlightInvalidVariables); |
335 'class': "errorMessage" |
280 jQuery('#substitutions').prepend(this.errorField); |
336 }); |
281 jQuery('#substitutions .errorMessage').hide(); |
337 this.textField = TEXTAREA(this.options); |
282 wdgnode.appendChild(this.textField); |
338 jQuery(this.textField).bind('keyup', { |
|
339 'self': this |
|
340 }, |
|
341 this.highlightInvalidVariables); |
|
342 jQuery('#substitutions').prepend(this.errorField); |
|
343 jQuery('#substitutions .errorMessage').hide(); |
|
344 wdgnode.appendChild(this.textField); |
283 }, |
345 }, |
284 |
346 |
285 /* signal callbacks */ |
347 /* signal callbacks */ |
286 |
348 |
287 highlightInvalidVariables : function(event) { |
349 highlightInvalidVariables: function(event) { |
288 var self = event.data.self; |
350 var self = event.data.self; |
289 var text = self.textField.value; |
351 var text = self.textField.value; |
290 var unknownVariables = []; |
352 var unknownVariables = []; |
291 var it = 0; |
353 var it = 0; |
292 var group = null; |
354 var group = null; |
293 var variableRegexp = /%\((\w+)\)s/g; |
355 var variableRegexp = /%\((\w+)\)s/g; |
294 // emulates rgx.findAll() |
356 // emulates rgx.findAll() |
295 while ( group=variableRegexp.exec(text) ) { |
357 while (group = variableRegexp.exec(text)) { |
296 if ( !self.variables.contains(group[1]) ) { |
358 if (!self.variables.contains(group[1])) { |
297 unknownVariables.push(group[1]); |
359 unknownVariables.push(group[1]); |
298 } |
360 } |
299 it++; |
361 it++; |
300 if (it > 5) { |
362 if (it > 5) { |
301 break; |
363 break; |
302 } |
364 } |
303 } |
365 } |
304 var errText = ''; |
366 var errText = ''; |
305 if (unknownVariables.length) { |
367 if (unknownVariables.length) { |
306 errText = "Detected invalid variables : " + ", ".join(unknownVariables); |
368 errText = "Detected invalid variables : " + unknownVariables.join(', '); |
307 jQuery('#substitutions .errorMessage').show(); |
369 jQuery('#substitutions .errorMessage').show(); |
308 } else { |
370 } else { |
309 jQuery('#substitutions .errorMessage').hide(); |
371 jQuery('#substitutions .errorMessage').hide(); |
310 } |
372 } |
311 self.errorField.innerHTML = errText; |
373 self.errorField.innerHTML = errText; |
312 } |
374 } |
313 |
375 |
314 }); |
376 }); |
315 |
377 |
316 |
378 cw.widgets = { |
317 CubicWeb.provide('widgets.js'); |
379 /** |
|
380 * .. function:: insertText(text, areaId) |
|
381 * |
|
382 * inspects textarea with id `areaId` and replaces the current selected text |
|
383 * with `text`. Cursor is then set at the end of the inserted text. |
|
384 */ |
|
385 insertText: function (text, areaId) { |
|
386 var textarea = jQuery('#' + areaId); |
|
387 if (document.selection) { // IE |
|
388 var selLength; |
|
389 textarea.focus(); |
|
390 var sel = document.selection.createRange(); |
|
391 selLength = sel.text.length; |
|
392 sel.text = text; |
|
393 sel.moveStart('character', selLength - text.length); |
|
394 sel.select(); |
|
395 } else if (textarea.selectionStart || textarea.selectionStart == '0') { // mozilla |
|
396 var startPos = textarea.selectionStart; |
|
397 var endPos = textarea.selectionEnd; |
|
398 // insert text so that it replaces the [startPos, endPos] part |
|
399 textarea.value = textarea.value.substring(0, startPos) + text + textarea.value.substring(endPos, textarea.value.length); |
|
400 // set cursor pos at the end of the inserted text |
|
401 textarea.selectionStart = textarea.selectionEnd = startPos + text.length; |
|
402 textarea.focus(); |
|
403 } else { // safety belt for other browsers |
|
404 textarea.value += text; |
|
405 } |
|
406 } |
|
407 }; |