/*jslint vars: true, unparam: true, white: true */ /*global jQuery, IQVOC */ IQVOC.EntitySelector = (function($) { "use strict"; var EntitySelector = function(node) { if(arguments.length === 0) { // subclassing; skip initialization return; } this.el = $(node).hide(); // XXX: rename this.container = $('
').data("widget", this); this.indicator = $(''). append(''); this.delimiter = ","; this.singular = this.el.data("singular") || false; this.entities = this.getSelection(); this.uriTemplate = this.el.data("entity-uri"); var selection = this.el.data("entities") || []; var self = this; selection = $.map(selection, function(entity, i) { return self.createEntity(entity); }); selection = $('
    ').append(selection); var input = $(''); this.inputGroup = $('
    '). append(input, this.indicator); this.container.append(this.inputGroup, selection). insertAfter(node).prepend(node); // XXX: does not belong here -- XXX: obsolete? var lang = this.el.data("language"); if(lang) { $('').append(lang). prependTo(this.inputGroup); } IQVOC.autocomplete(input, $.proxy(this, "onInput"), { noResultsMsg: this.el.data("no-results-msg"), onSelect: this.onSelect, displayKey: "label" }); if(this.singular && this.entities.length) { this.inputGroup.hide(); } }; // data transformations; target format is an array of objects with members // `value` and `label` // optional second argument `excludes` is an array of item IDs to exclude EntitySelector.preprocessors = { // converts an array of objects with members `id` and `name` "default": function(data, excludes) { return $.map(data, function(entity, i) { return $.inArray(entity.id, excludes) !== -1 ? null : { value: entity.id, label: entity.name }; }); } }; EntitySelector.sourceSelectors = { "default": function(callback) { var uri = this.el.data("query-url"); callback(uri); } }; $.extend(EntitySelector.prototype, { onInput: function(query, callback) { this.indicator.addClass("active"); var self = this; var responder = function(data, status, xhr) { // TODO: rename, move elsewhere data = self.processResponse(data); callback(data); self.indicator.removeClass("active"); }; var sourceSelector = this.el.data("source-selector") || "default"; EntitySelector.sourceSelectors[sourceSelector].call(this, function(uri) { // XXX: direct `EntitySelector` reference limits subclassing $.getJSON(uri, { query: query }, responder); // TODO: error handling }); }, onSelect: function(ev, item) { var el = $(this).val("") var widget = el.closest(".entity_select").data("widget"); if(widget.add(item.value)) { var entity = widget. createEntity({ id: item.value, name: item.label }); var list = widget.container.find("ul").append(entity); // force redraw to work around Safari rendering bug -- FIXME: crude and uninformed list.css("overflow", "auto"); setTimeout(function() { list.css("overflow", ""); }, 1); if(widget.singular) { widget.inputGroup.hide(); } } return false; }, onDelete: function(ev) { var el = $(this), entity = el.closest("li"), widget = el.closest(".entity_select").data("widget"); widget.remove(entity.data("id")); entity.remove(); if(widget.singular && !widget.entities.length) { widget.inputGroup.show(); } ev.preventDefault(); }, processResponse: function(data) { // TODO: rename var preprocessor = this.el.data("preprocessor") || "default"; var exclude = this.el.data("exclude") || null; var excludes = this.getSelection().concat(exclude ? [exclude] : []); return EntitySelector.preprocessors[preprocessor](data, excludes); // XXX: direct `EntitySelector` reference limits subclassing }, createEntity: function(entity) { var el; if(this.uriTemplate) { var uri = this.uriTemplate.replace("%7Bid%7D", entity.id); // XXX: not very generic el = $('').attr("href", uri).text(entity.name); } else { el = $('').text(entity.name); } var delBtn = $('x'). // "btn" to avoid fancy "button" class -- XXX: hacky workaround!? click(this.onDelete); return $("
  • ").data("id", entity.id).append(el).append(delBtn)[0]; }, add: function(entity) { if($.inArray(entity, this.entities) === -1) { this.entities.push(entity); this.setSelection(); return true; } else { return false; } }, remove: function(entity) { var pos = $.inArray(entity, this.entities); if(pos !== -1) { this.entities.splice(pos, 1); this.setSelection(); } }, setSelection: function() { this.el.val(this.entities.join(this.delimiter)); }, getSelection: function() { return $.map(this.el.val().split(this.delimiter), function(entity, i) { return entity ? $.trim(entity) : null; }); } }); return EntitySelector; }(jQuery));