diff -r 058bb3dc685f -r 0b59724cb3f2 web/data/cubicweb.widgets.js --- a/web/data/cubicweb.widgets.js Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,555 +0,0 @@ -/** - * Functions dedicated to widgets. - * - * :organization: Logilab - * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. - * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr - * - * - */ - -// widget namespace -Widgets = {}; - -/** - * .. 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) { - return new wdgclass(wdgnode); - } - return null; -} - -function renderJQueryDatePicker(subject, button_image, date_format, min_date, max_date){ - $widget = cw.jqNode(subject); - $widget.datepicker({buttonImage: button_image, dateFormat: date_format, - firstDay: 1, showOn: "button", buttonImageOnly: true, - minDate: min_date, maxDate: max_date}); - $widget.change(function(ev) { - maxOfId = $(this).data('max-of'); - if (maxOfId) { - cw.jqNode(maxOfId).datepicker("option", "maxDate", this.value); - } - minOfId = $(this).data('min-of'); - if (minOfId) { - cw.jqNode(minOfId).datepicker("option", "minDate", this.value); - } - }); -} - -/** - * .. 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); - } - }); -} - -jQuery(document).ready(function() { - buildWidgets(); -}); - -function postJSON(url, data, callback) { - return jQuery.post(url, data, callback, AJAX_BASE_URL); -} - -function getJSON(url, data, callback) { - return jQuery.get(url, data, callback, AJAX_BASE_URL); -} - - -(function ($) { - var defaultSettings = { - initialvalue: '', - multiple: false, - mustMatch: false, - delay: 50, - limit: 50 - }; - function split(val) { return val.split( /\s*,\s*/ ); } - function extractLast(term) { return split(term).pop(); } - function allButLast(val) { - var terms = split(val); - terms.pop(); - return terms; - } - - var methods = { - __init__: function(suggestions, options) { - return this.each(function() { - // here, `this` refers to the DOM element (e.g. input) being wrapped - // by cwautomplete plugin - var instanceData = $(this).data('cwautocomplete'); - if (instanceData) { - // already initialized - return; - } - var settings = $.extend({}, defaultSettings, options); - instanceData = { - initialvalue: settings.initialvalue, - userInput: this, - hiddenInput: null - }; - var hiHandlers = methods.hiddenInputHandlers; - $(this).data('cwautocomplete', instanceData); - // in case of an existing value, the hidden input must be initialized even if - // the value is not changed - if (($(instanceData.userInput).attr('cubicweb:initialvalue') !== undefined) && !instanceData.hiddenInput){ - hiHandlers.initializeHiddenInput(instanceData); - } - $.ui.autocomplete.prototype._value = methods._value; - $.data(this, 'settings', settings); - if (settings.multiple) { - $.ui.autocomplete.filter = methods.multiple.makeFilter(this); - $(this).bind({ - autocompleteselect: methods.multiple.select, - autocompletefocus: methods.multiple.focus, - keydown: methods.multiple.keydown - }); - } - // XXX katia we dont need it if minLength == 0, but by setting minLength = 0 - // we probably break the backward compatibility - $(this).bind('blur', methods.blur); - if ($.isArray(suggestions)) { // precomputed list of suggestions - settings.source = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions); - } else { // url to call each time something is typed - settings.source = function(request, response) { - var d = loadRemote(suggestions, {q: request.term, limit: settings.limit}, 'POST'); - d.addCallback(function (suggestions) { - suggestions = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions); - response(suggestions); - if((suggestions.length) == 0){ - methods.resetValues(instanceData); - } - }); - }; - } - $(this).autocomplete(settings); - if (settings.mustMatch) { - $(this).keypress(methods.ensureExactMatch); - } - }); - }, - - _value: function() { - /* We extend the widget with the ability to lookup and - handle several terms at once ('multiple' option). E.g.: - toto, titi, tu.... The autocompletion must be - performed only on the last of such a list of terms. - */ - var settings = $(this.element).data('settings'); - var value = this.valueMethod.apply( this.element, arguments ); - if (settings.multiple & arguments.length === 0) { - return extractLast(value); - } - return value - }, - - multiple: { - focus: function() { - // prevent value inserted on focus - return false; - }, - select: function(event, ui) { - var terms = allButLast(this.value); - // add the selected item - terms.push(ui.item.value); - // add placeholder to get the comma-and-space at the end - terms.push(""); - this.value = terms.join( ", " ); - return false; - }, - keydown: function(evt) { - if (evt.keyCode == $.ui.keyCode.TAB) { - evt.preventDefault(); - } - }, - makeFilter: function(userInput) { - return function(array, term) { - // remove already entered terms from suggestion list - array = cw.utils.difference(array, allButLast(userInput.value)); - var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); - return $.grep( array, function(value) { - return matcher.test( value.label || value.value || value ); - }); - }; - } - }, - blur: function(evt){ - var instanceData = $(this).data('cwautocomplete'); - if($(instanceData.userInput).val().strip().length==0){ - methods.resetValues(instanceData); - } - }, - - ensureExactMatch: function(evt) { - var instanceData = $(this).data('cwautocomplete'); - if (evt.keyCode == $.ui.keyCode.ENTER || evt.keyCode == $.ui.keyCode.TAB) { - var validChoices = $.map($('ul.ui-autocomplete li'), - function(li) {return $(li).text();}); - if ($.inArray($(instanceData.userInput).val(), validChoices) == -1) { - $(instanceData.userInput).val(''); - $(instanceData.hiddenInput).val(instanceData.initialvalue || ''); - } - } - }, - - resetValues: function(instanceData){ - $(instanceData.userInput).val(''); - $(instanceData.hiddenInput).val(''); - }, - - - hiddenInputHandlers: { - /** - * `hiddenInputHandlers` defines all methods specific to handle the - * hidden input created along the standard text input. - * An hiddenInput is necessary when displayed suggestions are - * different from actual values to submit. - * Imagine an autocompletion widget to choose among a list of CWusers. - * Suggestions would be the list of logins, but actual values would - * be the corresponding eids. - * To handle such cases, suggestions list should be a list of JS objects - * with two `label` and `value` properties. - **/ - suggestionSelected: function(evt, ui) { - var instanceData = $(this).data('cwautocomplete'); - instanceData.hiddenInput.value = ui.item.value; - instanceData.value = ui.item.label; - return false; // stop propagation - }, - - suggestionFocusChanged: function(evt, ui) { - var instanceData = $(this).data('cwautocomplete'); - instanceData.userInput.value = ui.item.label; - return false; // stop propagation - }, - - needsHiddenInput: function(suggestions) { - return suggestions[0].label !== undefined; - }, - initializeHiddenInput: function(instanceData) { - var userInput = instanceData.userInput; - var hiddenInput = INPUT({ - type: "hidden", - name: userInput.name, - // XXX katia : this must be handeled in .SuggestField widget, but - // it seems not to be used anymore - value: $(userInput).attr('cubicweb:initialvalue') || userInput.value - }); - $(userInput).removeAttr('name').after(hiddenInput); - instanceData.hiddenInput = hiddenInput; - $(userInput).bind({ - autocompleteselect: methods.hiddenInputHandlers.suggestionSelected, - autocompletefocus: methods.hiddenInputHandlers.suggestionFocusChanged - }); - }, - - /* - * internal convenience function: old jquery plugin accepted to be fed - * with a list of couples (value, label). The new (jquery-ui) autocomplete - * plugin expects a list of objects with "value" and "label" properties. - * - * This function converts the old format to the new one. - */ - checkSuggestionsDataFormat: function(instanceData, suggestions) { - // check for old (value, label) format - if ($.isArray(suggestions) && suggestions.length && - $.isArray(suggestions[0])){ - if (suggestions[0].length == 2) { - cw.log('[3.10] autocomplete init func should return {label,value} dicts instead of lists'); - suggestions = $.map(suggestions, function(sugg) { - return {value: sugg[0], label: sugg[1]}; - }); - } else { - if(suggestions[0].length == 1){ - suggestions = $.map(suggestions, function(sugg) { - return {value: sugg[0], label: sugg[0]}; - }); - } - } - } - var hiHandlers = methods.hiddenInputHandlers; - if (suggestions.length && hiHandlers.needsHiddenInput(suggestions) - && !instanceData.hiddenInput) { - hiHandlers.initializeHiddenInput(instanceData); - hiHandlers.fixUserInputInitialValue(instanceData, suggestions); - } - // otherwise, assume data shape is correct - return suggestions; - }, - - fixUserInputInitialValue: function(instanceData, suggestions) { - // 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 = instanceData.userInput.value; - if (!curvalue) { - return; - } - for (var i=0, length=suggestions.length; i < length; i++) { - var sugg = suggestions[i]; - if (sugg.value == curvalue) { - instanceData.userInput.value = sugg.label; - return; - } - } - } - } - }; - - $.fn.cwautocomplete = function(data, options) { - return methods.__init__.apply(this, [data, options]); - }; -})(jQuery); - - -Widgets.SuggestField = defclass('SuggestField', null, { - __init__: function(node, options) { - options = options || {}; - var multi = node.getAttribute('cubicweb:multi'); - options.multiple = (multi == "yes") ? true: false; - var d = loadRemote(node.getAttribute('cubicweb:dataurl')); - d.addCallback(function(data) { - $(node).cwautocomplete(data, options); - }); - } -}); - -Widgets.StaticFileSuggestField = defclass('StaticSuggestField', [Widgets.SuggestField], { - - __init__: function(node) { - Widgets.SuggestField.__init__(this, node, { - method: 'get' // XXX - }); - } - -}); - -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; - options = options || {}; - options.delay = 50; - // multiple selection not supported yet (still need to formalize correctly - // initial values / display values) - var initialvalue = cw.evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null'); - if (!initialvalue) { - initialvalue = node.value; - } - options.initialvalue = initialvalue; - Widgets.SuggestField.__init__(this, node, options); - } -}); - -/** - * .. 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. - */ -function toggleTree(event) { - var linode = jQuery(this); - var url = linode.attr('cubicweb:loadurl'); - if (url) { - linode.find('ul.placeholder').remove(); - var d = linode.loadxhtml(url, null, 'post', 'append'); - d.addCallback(function(domnode) { - linode.removeAttr('cubicweb:loadurl'); - linode.find('> ul.treeview').treeview({ - toggle: toggleTree, - prerendered: true - }); - return null; - } - ); - } -} - -Widgets.TemplateTextField = defclass("TemplateTextField", null, { - - __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 ($.inArray(group[1], self.variables) == -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; - } - -}); - -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; - } - } -}; - - -// InOutWidget This contains specific InOutnWidget javascript -// IE things can not handle hide/show options on select, this cloned list solition (should propably have 2 widgets) - -(function ($) { - - var methods = { - __init__: function(fromSelect, toSelect) { - // closed over state - var state = {'$fromNode' : $(cw.escape('#' + fromSelect)), - '$toNode' : $(cw.escape('#' + toSelect)), - 'name' : this.attr('id')}; - - function sortoptions($optionlist) { - var $sorted = $optionlist.find('option').sort(function(opt1, opt2) { - return $(opt1).text() > $(opt2).text() ? 1 : -1; - }); - // this somehow translates to an inplace sort - $optionlist.append($sorted); - }; - sortoptions(state.$fromNode); - sortoptions(state.$toNode); - - // will move selected options from one list to the other - // and call an option handler on each option - function moveoptions ($fromlist, $tolist, opthandler) { - $fromlist.find('option:selected').each(function(index, option) { - var $option = $(option); - // add a new option to the target list - $tolist.append(OPTION({'value' : $option.val()}, - $option.text())); - // process callback on the option - opthandler.call(null, $option); - // remove option from the source list - $option.remove(); - }); - // re-sort both lists - sortoptions($fromlist); - sortoptions($tolist); - }; - - function addvalues () { - moveoptions(state.$fromNode, state.$toNode, function ($option) { - // add an hidden input for the edit controller - var hiddenInput = INPUT({ - type: 'hidden', name: state.name, - value : $option.val() - }); - state.$toNode.parent().append(hiddenInput); - }); - }; - - function removevalues () { - moveoptions(state.$toNode, state.$fromNode, function($option) { - // remove hidden inputs for the edit controller - var selector = 'input[name=' + cw.escape(state.name) + ']' - state.$toNode.parent().find(selector).each(function(index, input) { - if ($(input).val() == $option.val()) { - $(input).remove(); - } - }); - }); - }; - - var $this = $(this); - $this.find('.cwinoutadd').bind( // 'add >>>' symbol - 'click', {'state' : state}, addvalues); - $this.find('.cwinoutremove').bind( // 'remove <<<' symbol - 'click', {'state' : state}, removevalues); - - state.$fromNode.bind('dblclick', {'state': state}, addvalues); - state.$toNode.bind('dblclick', {'state': state}, removevalues); - - } - }; - $.fn.cwinoutwidget = function(fromSelect, toSelect) { - return methods.__init__.apply(this, [fromSelect, toSelect]); - }; -})(jQuery);