web/data/cubicweb.widgets.js
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 25 Mar 2010 14:49:24 +0100
branchstable
changeset 5018 2f2d9bc6dca4
parent 4901 19ecbbc4f633
child 5453 d0c8076e298b
child 5767 1d811df051c2
permissions -rw-r--r--
[appobject] add version number to deprecation warning

/*
 *  :organization: Logilab
 *  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 *
 *
 */

// widget namespace
Widgets = {};


/* 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);
    }
}

/* 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);
	}
    });
}


// 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();});


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 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);
	});
    },

    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];
	});
    },

    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;
	    }
	}
    }
});

Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], {

    __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});
    }

});
//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;
        // multiple selection not supported yet (still need to formalize correctly
        // initial values / display values)
        var initialvalue = 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);
        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});
        node.parentNode.appendChild(hidden);
        jQuery(node).bind('result', {hinput: hidden, input:node}, self.hideRealValue)
            .removeAttr('name').autocomplete(dataurl, options);
    },


    hideRealValue: function(evt, data, value) {
	if (!value){
	    value="";
	}
        evt.data.hinput.value = value;
    },

    /*
     * @param data: a list of couple (value, label) to fill the suggestion list,
     *              (returned by CW through AJAX)
     */
    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
                };
        };
        return parsed;
    }

});

/*
 * 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);
    },

    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);
    },

    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);
    }
});


/* 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.
 */
function toggleTree(event) {
    var linode = jQuery(this);
    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');
    }
}


/* 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); });

    }
});

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);
    },

    /* 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;
    }

});

/*
 * ComboBox with a textinput : allows to add a new value
 */

Widgets.AddComboBox = defclass('AddComboBox', null, {
   __init__ : function(wdgnode) {
       jQuery("#add_newopt").click(function() {
	  var new_val = jQuery("#newopt").val();
	      if (!new_val){
		  return false;
	      }
          name = wdgnode.getAttribute('name').split(':');
	  this.rel = name[0];
	  this.eid_to = name[1];
          this.etype_to = wdgnode.getAttribute('cubicweb:etype_to');
          this.etype_from = wdgnode.getAttribute('cubicweb:etype_from');
     	  var d = asyncRemoteExec('add_and_link_new_entity', this.etype_to, this.rel, this.eid_to, this.etype_from, 'new_val');
          d.addCallback(function (eid) {
	      jQuery(wdgnode).find("option[selected]").removeAttr("selected");
              var new_option = OPTION({'value':eid, 'selected':'selected'}, value=new_val);
              wdgnode.appendChild(new_option);
          });
          d.addErrback(function (xxx) {
              log('xxx =', xxx);
          });
     });
   }
});


CubicWeb.provide('widgets.js');