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