web/data/cubicweb.widgets.js
author Aurelien Campeas <aurelien.campeas@logilab.fr>
Wed, 21 Oct 2009 11:34:11 +0200
branchstable
changeset 3767 03924de0014d
parent 3079 c1a4fbf2539a
child 4738 6cca4f602486
permissions -rw-r--r--
reledit: stuff the value into its own div and properly hide it when necessary (but dont lump it with the landingzone div for it switches the form on when one clicks on a value to traverse it)

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

});


/*
 * 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');
    linode.find('ul.placeholder').remove();
    if (url) {
	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');