web/data/cubicweb.widgets.js
changeset 0 b97547f5f1fa
child 916 968f00dd9a24
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     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');