|
1 /* |
|
2 * :organization: Logilab |
|
3 * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
4 * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
5 * |
|
6 * |
|
7 */ |
|
8 |
|
9 // widget namespace |
|
10 Widgets = {}; |
|
11 |
|
12 |
|
13 /* this function takes a DOM node defining a widget and |
|
14 * instantiates / builds the appropriate widget class |
|
15 */ |
|
16 function buildWidget(wdgnode) { |
|
17 var wdgclass = Widgets[wdgnode.getAttribute('cubicweb:wdgtype')]; |
|
18 if (wdgclass) { |
|
19 var wdg = new wdgclass(wdgnode); |
|
20 } |
|
21 } |
|
22 |
|
23 /* This function is called on load and is in charge to build |
|
24 * JS widgets according to DOM nodes found in the page |
|
25 */ |
|
26 function buildWidgets(root) { |
|
27 root = root || document; |
|
28 jQuery(root).find('.widget').each(function() { |
|
29 if (this.getAttribute('cubicweb:loadtype') == 'auto') { |
|
30 buildWidget(this); |
|
31 } |
|
32 }); |
|
33 } |
|
34 |
|
35 |
|
36 // we need to differenciate cases where initFacetBoxEvents is called |
|
37 // 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 |
|
39 // of his, so we use this small anonymous function instead. |
|
40 jQuery(document).ready(function() {buildWidgets();}); |
|
41 |
|
42 |
|
43 Widgets.SuggestField = defclass('SuggestField', null, { |
|
44 __init__: function(node, options) { |
|
45 var multi = node.getAttribute('cubicweb:multi') || "no"; |
|
46 options = options || {}; |
|
47 options.multiple = (multi == "yes") ? true : false; |
|
48 var dataurl = node.getAttribute('cubicweb:dataurl'); |
|
49 var method = postJSON; |
|
50 if (options.method == 'get'){ |
|
51 method = function(url, data, callback) { |
|
52 // We can't rely on jQuery.getJSON because the server |
|
53 // might set the Content-Type's response header to 'text/plain' |
|
54 jQuery.get(url, data, function(response) { |
|
55 callback(evalJSON(response)); |
|
56 }); |
|
57 }; |
|
58 } |
|
59 var self = this; // closure |
|
60 method(dataurl, null, function(data) { |
|
61 // 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 |
|
63 // value to be displayed |
|
64 if (data.length && data[0].length == 2) { |
|
65 options.formatItem = function(row) { return row[1]; }; |
|
66 self.hideRealValue(node); |
|
67 self.setCurrentValue(node, data); |
|
68 } |
|
69 jQuery(node).autocomplete(data, options); |
|
70 }); |
|
71 }, |
|
72 |
|
73 hideRealValue: function(node) { |
|
74 var hidden = INPUT({'type': "hidden", 'name': node.name, 'value': node.value}); |
|
75 node.parentNode.appendChild(hidden); |
|
76 // remove 'name' attribute from visible input so that it is not submitted |
|
77 // and set correct value in the corresponding hidden field |
|
78 jQuery(node).removeAttr('name').bind('result', function(_, row, _) { |
|
79 hidden.value = row[0]; |
|
80 }); |
|
81 }, |
|
82 |
|
83 setCurrentValue: function(node, data) { |
|
84 // called when the data is loaded to reset the correct displayed |
|
85 // value in the visible input field (typically replacing an eid |
|
86 // by a displayable value) |
|
87 var curvalue = node.value; |
|
88 if (!node.value) { |
|
89 return; |
|
90 } |
|
91 for (var i=0,length=data.length; i<length; i++) { |
|
92 var row = data[i]; |
|
93 if (row[0] == curvalue) { |
|
94 node.value = row[1]; |
|
95 return; |
|
96 } |
|
97 } |
|
98 } |
|
99 }); |
|
100 |
|
101 Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], { |
|
102 |
|
103 __init__ : function(node) { |
|
104 Widgets.SuggestField.__init__(this, node, {method: 'get'}); |
|
105 } |
|
106 |
|
107 }); |
|
108 |
|
109 Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], { |
|
110 |
|
111 __init__ : function(node) { |
|
112 Widgets.SuggestField.__init__(this, node, {mustMatch: true}); |
|
113 } |
|
114 |
|
115 }); |
|
116 |
|
117 |
|
118 /* |
|
119 * suggestform displays a suggest field and associated validate / cancel buttons |
|
120 * constructor's argumemts are the same that BaseSuggestField widget |
|
121 */ |
|
122 Widgets.SuggestForm = defclass("SuggestForm", null, { |
|
123 |
|
124 __init__ : function(inputid, initfunc, varargs, validatefunc, options) { |
|
125 this.validatefunc = validatefunc || noop; |
|
126 this.sgfield = new Widgets.BaseSuggestField(inputid, initfunc, |
|
127 varargs, options); |
|
128 this.oklabel = options.oklabel || 'ok'; |
|
129 this.cancellabel = options.cancellabel || 'cancel'; |
|
130 bindMethods(this); |
|
131 connect(this.sgfield, 'validate', this, this.entryValidated); |
|
132 }, |
|
133 |
|
134 show : function(parentnode) { |
|
135 var sgnode = this.sgfield.builddom(); |
|
136 var buttons = DIV({'class' : "sgformbuttons"}, |
|
137 [A({'href' : "javascript: noop();", |
|
138 'onclick' : this.onValidateClicked}, this.oklabel), |
|
139 ' / ', |
|
140 A({'href' : "javascript: noop();", |
|
141 'onclick' : this.destroy}, escapeHTML(this.cancellabel))]); |
|
142 var formnode = DIV({'class' : "sgform"}, [sgnode, buttons]); |
|
143 appendChildNodes(parentnode, formnode); |
|
144 this.sgfield.textinput.focus(); |
|
145 this.formnode = formnode; |
|
146 return formnode; |
|
147 }, |
|
148 |
|
149 destroy : function() { |
|
150 signal(this, 'destroy'); |
|
151 this.sgfield.destroy(); |
|
152 removeElement(this.formnode); |
|
153 }, |
|
154 |
|
155 onValidateClicked : function() { |
|
156 this.validatefunc(this, this.sgfield.taglist()); |
|
157 }, |
|
158 /* just an indirection to pass the form instead of the sgfield as first parameter */ |
|
159 entryValidated : function(sgfield, taglist) { |
|
160 this.validatefunc(this, taglist); |
|
161 } |
|
162 }); |
|
163 |
|
164 |
|
165 /* called when the use clicks on a tree node |
|
166 * - if the node has a `cubicweb:loadurl` attribute, replace the content of the node |
|
167 * by the url's content. |
|
168 * - else, there's nothing to do, let the jquery plugin handle it. |
|
169 */ |
|
170 function toggleTree(event) { |
|
171 var linode = jQuery(this); |
|
172 var url = linode.attr('cubicweb:loadurl'); |
|
173 linode.find('ul.placeholder').remove(); |
|
174 if (url) { |
|
175 linode.loadxhtml(url, {callback: function(domnode) { |
|
176 linode.removeAttr('cubicweb:loadurl'); |
|
177 jQuery(domnode).treeview({toggle: toggleTree, |
|
178 prerendered: true}); |
|
179 return null; |
|
180 }}, 'post', 'append'); |
|
181 } |
|
182 } |
|
183 |
|
184 Widgets.TreeView = defclass("TreeView", null, { |
|
185 __init__: function(wdgnode) { |
|
186 jQuery(wdgnode).treeview({toggle: toggleTree, |
|
187 prerendered: true |
|
188 }); |
|
189 } |
|
190 }); |
|
191 |
|
192 |
|
193 /* widget based on SIMILE's timeline widget |
|
194 * http://code.google.com/p/simile-widgets/ |
|
195 * |
|
196 * Beware not to mess with SIMILE's Timeline JS namepsace ! |
|
197 */ |
|
198 |
|
199 Widgets.TimelineWidget = defclass("TimelineWidget", null, { |
|
200 __init__: function (wdgnode) { |
|
201 var tldiv = DIV({id: "tl", style: 'height: 200px; border: 1px solid #ccc;'}); |
|
202 wdgnode.appendChild(tldiv); |
|
203 var tlunit = wdgnode.getAttribute('cubicweb:tlunit') || 'YEAR'; |
|
204 var eventSource = new Timeline.DefaultEventSource(); |
|
205 var bandData = { |
|
206 eventPainter: Timeline.CubicWebEventPainter, |
|
207 eventSource: eventSource, |
|
208 width: "100%", |
|
209 intervalUnit: Timeline.DateTime[tlunit.toUpperCase()], |
|
210 intervalPixels: 100 |
|
211 }; |
|
212 var bandInfos = [ Timeline.createBandInfo(bandData) ]; |
|
213 var tl = Timeline.create(tldiv, bandInfos); |
|
214 var loadurl = wdgnode.getAttribute('cubicweb:loadurl'); |
|
215 Timeline.loadJSON(loadurl, function(json, url) { |
|
216 eventSource.loadJSON(json, url); }); |
|
217 |
|
218 } |
|
219 }); |
|
220 |
|
221 Widgets.TemplateTextField = defclass("TemplateTextField", null, { |
|
222 |
|
223 __init__ : function(wdgnode) { |
|
224 this.variables = getNodeAttribute(wdgnode, 'cubicweb:variables').split(','); |
|
225 this.options = {'name' : wdgnode.getAttribute('cubicweb:inputname'), |
|
226 'id' : wdgnode.getAttribute('cubicweb:inputid'), |
|
227 'rows' : wdgnode.getAttribute('cubicweb:rows') || 40, |
|
228 'cols' : wdgnode.getAttribute('cubicweb:cols') || 80 |
|
229 }; |
|
230 // this.variableRegexp = /%\((\w+)\)s/; |
|
231 this.parentnode = wdgnode; |
|
232 }, |
|
233 |
|
234 show : function(parentnode) { |
|
235 parentnode = parentnode || this.parentnode; |
|
236 this.errorField = DIV({'class' : "textfieldErrors"}); |
|
237 this.textField = TEXTAREA(this.options); |
|
238 connect(this.textField, 'onkeyup', this, this.highlightInvalidVariables); |
|
239 appendChildNodes(parentnode, this.textField, this.errorField); |
|
240 appendChildNodes(parentnode, this.textField); |
|
241 }, |
|
242 |
|
243 /* signal callbacks */ |
|
244 |
|
245 highlightInvalidVariables : function() { |
|
246 var text = this.textField.value; |
|
247 var unknownVariables = []; |
|
248 var it=0; |
|
249 var group = null; |
|
250 var variableRegexp = /%\((\w+)\)s/g; |
|
251 // emulates rgx.findAll() |
|
252 while ( group=variableRegexp.exec(text) ) { |
|
253 if ( !this.variables.contains(group[1]) ) { |
|
254 unknownVariables.push(group[1]); |
|
255 } |
|
256 it++; |
|
257 if (it > 5) |
|
258 break; |
|
259 } |
|
260 var errText = ''; |
|
261 if (unknownVariables.length) { |
|
262 errText = "Detected invalid variables : " + ", ".join(unknownVariables); |
|
263 } |
|
264 this.errorField.innerHTML = errText; |
|
265 } |
|
266 |
|
267 }); |
|
268 |
|
269 /* |
|
270 * ComboBox with a textinput : allows to add a new value |
|
271 */ |
|
272 |
|
273 Widgets.AddComboBox = defclass('AddComboBox', null, { |
|
274 __init__ : function(wdgnode) { |
|
275 jQuery("#add_newopt").click(function() { |
|
276 var new_val = jQuery("#newopt").val(); |
|
277 if (!new_val){ |
|
278 return false; |
|
279 } |
|
280 name = wdgnode.getAttribute('name').split(':'); |
|
281 this.rel = name[0]; |
|
282 this.eid_to = name[1]; |
|
283 this.etype_to = wdgnode.getAttribute('cubicweb:etype_to'); |
|
284 this.etype_from = wdgnode.getAttribute('cubicweb:etype_from'); |
|
285 var d = async_remote_exec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val'); |
|
286 d.addCallback(function (eid) { |
|
287 jQuery(wdgnode).find("option[selected]").removeAttr("selected"); |
|
288 var new_option = OPTION({'value':eid, 'selected':'selected'}, value=new_val); |
|
289 wdgnode.appendChild(new_option); |
|
290 }); |
|
291 d.addErrback(function (xxx) { |
|
292 log('xxx =', xxx); |
|
293 }); |
|
294 }); |
|
295 } |
|
296 }); |
|
297 |
|
298 |
|
299 CubicWeb.provide('widgets.js'); |