web/data/cubicweb.widgets.js
changeset 5658 7b9553a9db65
parent 5453 d0c8076e298b
child 5699 f4f6ee3af50b
--- a/web/data/cubicweb.widgets.js	Thu Jun 03 10:17:44 2010 +0200
+++ b/web/data/cubicweb.widgets.js	Thu Jun 03 14:51:42 2010 +0200
@@ -1,6 +1,8 @@
-/*
+/**
+ * Functions dedicated to widgets.
+ *
  *  :organization: Logilab
- *  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ *  :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  *
  *
@@ -9,144 +11,175 @@
 // widget namespace
 Widgets = {};
 
-
-/* this function takes a DOM node defining a widget and
+/**
+ * .. function:: buildWidget(wdgnode)
+ *
+ * this function takes a DOM node defining a widget and
  * instantiates / builds the appropriate widget class
  */
 function buildWidget(wdgnode) {
     var wdgclass = Widgets[wdgnode.getAttribute('cubicweb:wdgtype')];
     if (wdgclass) {
-	var wdg = new wdgclass(wdgnode);
+        var wdg = new wdgclass(wdgnode);
     }
 }
 
-/* This function is called on load and is in charge to build
+/**
+ * .. function:: buildWidgets(root)
+ *
+ * This function is called on load and is in charge to build
  * JS widgets according to DOM nodes found in the page
  */
 function buildWidgets(root) {
     root = root || document;
     jQuery(root).find('.widget').each(function() {
-	if (this.getAttribute('cubicweb:loadtype') == 'auto') {
-	    buildWidget(this);
-	}
+        if (this.getAttribute('cubicweb:loadtype') == 'auto') {
+            buildWidget(this);
+        }
     });
 }
 
-
 // we need to differenciate cases where initFacetBoxEvents is called
 // with one argument or without any argument. If we use `initFacetBoxEvents`
 // as the direct callback on the jQuery.ready event, jQuery will pass some argument
 // of his, so we use this small anonymous function instead.
-jQuery(document).ready(function() {buildWidgets();});
+jQuery(document).ready(function() {
+    buildWidgets();
+});
 
+function postJSON(url, data, callback) {
+    return jQuery.post(url, data, callback, 'json');
+}
+
+function getJSON(url, data, callback) {
+    return jQuery.get(url, data, callback, 'json');
+}
 
 Widgets.SuggestField = defclass('SuggestField', null, {
     __init__: function(node, options) {
-	var multi = node.getAttribute('cubicweb:multi') || "no";
-	options = options || {};
-	options.multiple = (multi == "yes") ? true : false;
-	var dataurl = node.getAttribute('cubicweb:dataurl');
+        var multi = node.getAttribute('cubicweb:multi') || "no";
+        options = options || {};
+        options.multiple = (multi == "yes") ? true: false;
+        var dataurl = node.getAttribute('cubicweb:dataurl');
         var method = postJSON;
-	if (options.method == 'get'){
-	  method = function(url, data, callback) {
-	    // We can't rely on jQuery.getJSON because the server
-	    // might set the Content-Type's response header to 'text/plain'
-	    jQuery.get(url, data, function(response) {
-	      callback(evalJSON(response));
-	    });
-	  };
-	}
-	var self = this; // closure
-	method(dataurl, null, function(data) {
-	    // in case we received a list of couple, we assume that the first
-	    // element is the real value to be sent, and the second one is the
-	    // value to be displayed
-	    if (data.length && data[0].length == 2) {
-		options.formatItem = function(row) { return row[1]; };
-		self.hideRealValue(node);
-		self.setCurrentValue(node, data);
-	    }
-	    jQuery(node).autocomplete(data, options);
-	});
+        if (options.method == 'get') {
+            method = function(url, data, callback) {
+                // We can't rely on jQuery.getJSON because the server
+                // might set the Content-Type's response header to 'text/plain'
+                jQuery.get(url, data, function(response) {
+                    callback(cw.evalJSON(response));
+                });
+            };
+        }
+        var self = this; // closure
+        method(dataurl, null, function(data) {
+            // in case we received a list of couple, we assume that the first
+            // element is the real value to be sent, and the second one is the
+            // value to be displayed
+            if (data.length && data[0].length == 2) {
+                options.formatItem = function(row) {
+                    return row[1];
+                };
+                self.hideRealValue(node);
+                self.setCurrentValue(node, data);
+            }
+            jQuery(node).autocomplete(data, options);
+        });
     },
 
     hideRealValue: function(node) {
-	var hidden = INPUT({'type': "hidden", 'name': node.name, 'value': node.value});
-	node.parentNode.appendChild(hidden);
-	// remove 'name' attribute from visible input so that it is not submitted
-	// and set correct value in the corresponding hidden field
-	jQuery(node).removeAttr('name').bind('result', function(_, row, _) {
-	    hidden.value = row[0];
-	});
+        var hidden = INPUT({
+            'type': "hidden",
+            'name': node.name,
+            'value': node.value
+        });
+        node.parentNode.appendChild(hidden);
+        // remove 'name' attribute from visible input so that it is not submitted
+        // and set correct value in the corresponding hidden field
+        jQuery(node).removeAttr('name').bind('result', function(_, row, _) {
+            hidden.value = row[0];
+        });
     },
 
     setCurrentValue: function(node, data) {
-	// called when the data is loaded to reset the correct displayed
-	// value in the visible input field (typically replacing an eid
-	// by a displayable value)
-	var curvalue = node.value;
-	if (!node.value) {
-	    return;
-	}
-	for (var i=0,length=data.length; i<length; i++) {
-	    var row = data[i];
-	    if (row[0] == curvalue) {
-		node.value = row[1];
-		return;
-	    }
-	}
+        // called when the data is loaded to reset the correct displayed
+        // value in the visible input field (typically replacing an eid
+        // by a displayable value)
+        var curvalue = node.value;
+        if (!node.value) {
+            return;
+        }
+        for (var i = 0, length = data.length; i < length; i++) {
+            var row = data[i];
+            if (row[0] == curvalue) {
+                node.value = row[1];
+                return;
+            }
+        }
     }
 });
 
 Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], {
 
-    __init__ : function(node) {
-	Widgets.SuggestField.__init__(this, node, {method: 'get'});
+    __init__: function(node) {
+        Widgets.SuggestField.__init__(this, node, {
+            method: 'get'
+        });
     }
 
 });
 
 Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], {
 
-    __init__ : function(node) {
-	Widgets.SuggestField.__init__(this, node, {mustMatch: true});
+    __init__: function(node) {
+        Widgets.SuggestField.__init__(this, node, {
+            mustMatch: true
+        });
     }
 
 });
 //remote version of RestrictedSuggestField
 Widgets.LazySuggestField = defclass('LazySuggestField', [Widgets.SuggestField], {
     __init__: function(node, options) {
-	var self = this;
-	var multi = "no";
-	options = options || {};
-	options.max = 50;
-	options.delay = 50;
-	options.cacheLength=0;
-	options.mustMatch = true;
+        var self = this;
+        var multi = "no";
+        options = options || {};
+        options.max = 50;
+        options.delay = 50;
+        options.cacheLength = 0;
+        options.mustMatch = true;
         // multiple selection not supported yet (still need to formalize correctly
         // initial values / display values)
-        var initialvalue = evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null');
+        var initialvalue = cw.evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null');
         if (!initialvalue) {
             initialvalue = node.value;
         }
-        options = jQuery.extend({dataType: 'json',
-                                 multiple: (multi == "yes") ? true : false,
-                                 parse: this.parseResult
-                                }, options);
+        options = jQuery.extend({
+            dataType: 'json',
+            multiple: (multi == "yes") ? true: false,
+            parse: this.parseResult
+        },
+        options);
         var dataurl = node.getAttribute('cubicweb:dataurl');
         // remove 'name' from original input and add the hidden one that will
         // store the actual value
-        var hidden = INPUT({'type': "hidden", 'name': node.name, 'value': initialvalue});
+        var hidden = INPUT({
+            'type': "hidden",
+            'name': node.name,
+            'value': initialvalue
+        });
         node.parentNode.appendChild(hidden);
-        jQuery(node).bind('result', {hinput: hidden, input:node}, self.hideRealValue)
-            .removeAttr('name').autocomplete(dataurl, options);
+        jQuery(node).bind('result', {
+            hinput: hidden,
+            input: node
+        },
+        self.hideRealValue).removeAttr('name').autocomplete(dataurl, options);
     },
 
-
     hideRealValue: function(evt, data, value) {
-	if (!value){
-	    value="";
-	}
+        if (!value) {
+            value = "";
+        }
         evt.data.hinput.value = value;
     },
 
@@ -156,68 +189,80 @@
      */
     parseResult: function(data) {
         var parsed = [];
-        for (var i=0; i < data.length; i++) {
-                var value = ''+data[i][0]; // a string is required later by jquery.autocomplete.js
-                var label = data[i][1];
-                parsed[parsed.length] = {
-                    data: [label],
-                    value: value,
-                    result: label
-                };
+        for (var i = 0; i < data.length; i++) {
+            var value = '' + data[i][0]; // a string is required later by jquery.autocomplete.js
+            var label = data[i][1];
+            parsed[parsed.length] = {
+                data: [label],
+                value: value,
+                result: label
+            };
         };
         return parsed;
     }
 
 });
 
-/*
+/**
+ * .. class:: Widgets.SuggestForm
+ *
  * suggestform displays a suggest field and associated validate / cancel buttons
  * constructor's argumemts are the same that BaseSuggestField widget
  */
 Widgets.SuggestForm = defclass("SuggestForm", null, {
 
-    __init__ : function(inputid, initfunc, varargs, validatefunc, options) {
-	this.validatefunc = validatefunc || noop;
-	this.sgfield = new Widgets.BaseSuggestField(inputid, initfunc,
-						    varargs, options);
-	this.oklabel = options.oklabel || 'ok';
-	this.cancellabel = options.cancellabel || 'cancel';
-	bindMethods(this);
-	connect(this.sgfield, 'validate', this, this.entryValidated);
+    __init__: function(inputid, initfunc, varargs, validatefunc, options) {
+        this.validatefunc = validatefunc || noop;
+        this.sgfield = new Widgets.BaseSuggestField(inputid, initfunc, varargs, options);
+        this.oklabel = options.oklabel || 'ok';
+        this.cancellabel = options.cancellabel || 'cancel';
+        bindMethods(this);
+        connect(this.sgfield, 'validate', this, this.entryValidated);
     },
 
-    show : function(parentnode) {
-	var sgnode = this.sgfield.builddom();
-	var buttons = DIV({'class' : "sgformbuttons"},
-			  [A({'href' : "javascript: noop();",
-			      'onclick' : this.onValidateClicked}, this.oklabel),
-			   ' / ',
-			   A({'href' : "javascript: noop();",
-			      'onclick' : this.destroy}, escapeHTML(this.cancellabel))]);
-	var formnode = DIV({'class' : "sgform"}, [sgnode, buttons]);
- 	appendChildNodes(parentnode, formnode);
-	this.sgfield.textinput.focus();
-	this.formnode = formnode;
-	return formnode;
+    show: function(parentnode) {
+        var sgnode = this.sgfield.builddom();
+        var buttons = DIV({
+            'class': "sgformbuttons"
+        },
+        [A({
+            'href': "javascript: noop();",
+            'onclick': this.onValidateClicked
+        },
+        this.oklabel), ' / ', A({
+            'href': "javascript: noop();",
+            'onclick': this.destroy
+        },
+        escapeHTML(this.cancellabel))]);
+        var formnode = DIV({
+            'class': "sgform"
+        },
+        [sgnode, buttons]);
+        appendChildNodes(parentnode, formnode);
+        this.sgfield.textinput.focus();
+        this.formnode = formnode;
+        return formnode;
     },
 
-    destroy : function() {
-	signal(this, 'destroy');
-	this.sgfield.destroy();
-	removeElement(this.formnode);
+    destroy: function() {
+        signal(this, 'destroy');
+        this.sgfield.destroy();
+        removeElement(this.formnode);
     },
 
-    onValidateClicked : function() {
-	this.validatefunc(this, this.sgfield.taglist());
+    onValidateClicked: function() {
+        this.validatefunc(this, this.sgfield.taglist());
     },
     /* just an indirection to pass the form instead of the sgfield as first parameter */
-    entryValidated : function(sgfield, taglist) {
-	this.validatefunc(this, taglist);
+    entryValidated: function(sgfield, taglist) {
+        this.validatefunc(this, taglist);
     }
 });
 
-
-/* called when the use clicks on a tree node
+/**
+ * .. function:: toggleTree(event)
+ *
+ * called when the use clicks on a tree node
  *  - if the node has a `cubicweb:loadurl` attribute, replace the content of the node
  *    by the url's content.
  *  - else, there's nothing to do, let the jquery plugin handle it.
@@ -227,91 +272,136 @@
     var url = linode.attr('cubicweb:loadurl');
     if (url) {
         linode.find('ul.placeholder').remove();
-	linode.loadxhtml(url, {callback: function(domnode) {
-	    linode.removeAttr('cubicweb:loadurl');
-	    jQuery(domnode).treeview({toggle: toggleTree,
-				      prerendered: true});
-	    return null;
-	}}, 'post', 'append');
+        linode.loadxhtml(url, {
+            callback: function(domnode) {
+                linode.removeAttr('cubicweb:loadurl');
+                jQuery(domnode).treeview({
+                    toggle: toggleTree,
+                    prerendered: true
+                });
+                return null;
+            }
+        },
+        'post', 'append');
     }
 }
 
-
-/* widget based on SIMILE's timeline widget
+/**
+ * .. class:: Widgets.TimelineWidget
+ *
+ * widget based on SIMILE's timeline widget
  * http://code.google.com/p/simile-widgets/
  *
  * Beware not to mess with SIMILE's Timeline JS namepsace !
  */
 
 Widgets.TimelineWidget = defclass("TimelineWidget", null, {
-    __init__: function (wdgnode) {
- 	var tldiv = DIV({id: "tl", style: 'height: 200px; border: 1px solid #ccc;'});
-	wdgnode.appendChild(tldiv);
-	var tlunit = wdgnode.getAttribute('cubicweb:tlunit') || 'YEAR';
-	var eventSource = new Timeline.DefaultEventSource();
-	var bandData = {
-	  eventPainter:     Timeline.CubicWebEventPainter,
-	  eventSource:    eventSource,
-	  width:          "100%",
-	  intervalUnit:   Timeline.DateTime[tlunit.toUpperCase()],
-	  intervalPixels: 100
-	};
-	var bandInfos = [ Timeline.createBandInfo(bandData) ];
-	var tl = Timeline.create(tldiv, bandInfos);
-	var loadurl = wdgnode.getAttribute('cubicweb:loadurl');
-	Timeline.loadJSON(loadurl, function(json, url) {
-			    eventSource.loadJSON(json, url); });
+    __init__: function(wdgnode) {
+        var tldiv = DIV({
+            id: "tl",
+            style: 'height: 200px; border: 1px solid #ccc;'
+        });
+        wdgnode.appendChild(tldiv);
+        var tlunit = wdgnode.getAttribute('cubicweb:tlunit') || 'YEAR';
+        var eventSource = new Timeline.DefaultEventSource();
+        var bandData = {
+            eventPainter: Timeline.CubicWebEventPainter,
+            eventSource: eventSource,
+            width: "100%",
+            intervalUnit: Timeline.DateTime[tlunit.toUpperCase()],
+            intervalPixels: 100
+        };
+        var bandInfos = [Timeline.createBandInfo(bandData)];
+        var tl = Timeline.create(tldiv, bandInfos);
+        var loadurl = wdgnode.getAttribute('cubicweb:loadurl');
+        Timeline.loadJSON(loadurl, function(json, url) {
+            eventSource.loadJSON(json, url);
+        });
 
     }
 });
 
 Widgets.TemplateTextField = defclass("TemplateTextField", null, {
 
-    __init__ : function(wdgnode) {
-	this.variables = getNodeAttribute(wdgnode, 'cubicweb:variables').split(',');
-	this.options = {'name'   : wdgnode.getAttribute('cubicweb:inputid'),
-			'rows' : wdgnode.getAttribute('cubicweb:rows') || 40,
-			'cols' : wdgnode.getAttribute('cubicweb:cols') || 80
-		       };
-	// this.variableRegexp = /%\((\w+)\)s/;
-	this.errorField = DIV({'class' : "errorMessage"});
-	this.textField = TEXTAREA(this.options);
-	jQuery(this.textField).bind('keyup', {'self': this}, this.highlightInvalidVariables);
-	jQuery('#substitutions').prepend(this.errorField);
-	jQuery('#substitutions .errorMessage').hide();
-	wdgnode.appendChild(this.textField);
+    __init__: function(wdgnode) {
+        this.variables = jQuery(wdgnode).attr('cubicweb:variables').split(',');
+        this.options = {
+            name: wdgnode.getAttribute('cubicweb:inputid'),
+            rows: wdgnode.getAttribute('cubicweb:rows') || 40,
+            cols: wdgnode.getAttribute('cubicweb:cols') || 80
+        };
+        // this.variableRegexp = /%\((\w+)\)s/;
+        this.errorField = DIV({
+            'class': "errorMessage"
+        });
+        this.textField = TEXTAREA(this.options);
+        jQuery(this.textField).bind('keyup', {
+            'self': this
+        },
+        this.highlightInvalidVariables);
+        jQuery('#substitutions').prepend(this.errorField);
+        jQuery('#substitutions .errorMessage').hide();
+        wdgnode.appendChild(this.textField);
     },
 
     /* signal callbacks */
 
-    highlightInvalidVariables : function(event) {
-	var self = event.data.self;
-	var text = self.textField.value;
-	var unknownVariables = [];
-	var it = 0;
-	var group = null;
-	var variableRegexp = /%\((\w+)\)s/g;
-	// emulates rgx.findAll()
-	while ( group=variableRegexp.exec(text) ) {
-	    if ( !self.variables.contains(group[1]) ) {
-		unknownVariables.push(group[1]);
-	    }
-	    it++;
-	    if (it > 5) {
-		break;
-	    }
-	}
-	var errText = '';
-	if (unknownVariables.length) {
-	    errText = "Detected invalid variables : " + ", ".join(unknownVariables);
-	    jQuery('#substitutions .errorMessage').show();
-	} else {
-	    jQuery('#substitutions .errorMessage').hide();
-	}
-	self.errorField.innerHTML = errText;
+    highlightInvalidVariables: function(event) {
+        var self = event.data.self;
+        var text = self.textField.value;
+        var unknownVariables = [];
+        var it = 0;
+        var group = null;
+        var variableRegexp = /%\((\w+)\)s/g;
+        // emulates rgx.findAll()
+        while (group = variableRegexp.exec(text)) {
+            if (!self.variables.contains(group[1])) {
+                unknownVariables.push(group[1]);
+            }
+            it++;
+            if (it > 5) {
+                break;
+            }
+        }
+        var errText = '';
+        if (unknownVariables.length) {
+            errText = "Detected invalid variables : " + unknownVariables.join(', ');
+            jQuery('#substitutions .errorMessage').show();
+        } else {
+            jQuery('#substitutions .errorMessage').hide();
+        }
+        self.errorField.innerHTML = errText;
     }
 
 });
 
-
-CubicWeb.provide('widgets.js');
+cw.widgets = {
+    /**
+     * .. function:: insertText(text, areaId)
+     *
+     * inspects textarea with id `areaId` and replaces the current selected text
+     * with `text`. Cursor is then set at the end of the inserted text.
+     */
+    insertText: function (text, areaId) {
+        var textarea = jQuery('#' + areaId);
+        if (document.selection) { // IE
+            var selLength;
+            textarea.focus();
+            var sel = document.selection.createRange();
+            selLength = sel.text.length;
+            sel.text = text;
+            sel.moveStart('character', selLength - text.length);
+            sel.select();
+        } else if (textarea.selectionStart || textarea.selectionStart == '0') { // mozilla
+            var startPos = textarea.selectionStart;
+            var endPos = textarea.selectionEnd;
+            // insert text so that it replaces the [startPos, endPos] part
+            textarea.value = textarea.value.substring(0, startPos) + text + textarea.value.substring(endPos, textarea.value.length);
+            // set cursor pos at the end of the inserted text
+            textarea.selectionStart = textarea.selectionEnd = startPos + text.length;
+            textarea.focus();
+        } else { // safety belt for other browsers
+            textarea.value += text;
+        }
+    }
+};
\ No newline at end of file