/**
* JQuery extension for tagging with autocomplete.
* @author: Jeremy Goecks
* @require: jquery.autocomplete plugin
*/
//
// Initialize "tag click functions" for tags.
//
function init_tag_click_function(tag_elt, click_func) {
$(tag_elt).find('.tag-name').each( function() {
$(this).click( function() {
var tag_str = $(this).text();
var tag_name_and_value = tag_str.split(":");
click_func(tag_name_and_value[0], tag_name_and_value[1]);
return true;
});
});
}
jQuery.fn.autocomplete_tagging = function(options) {
var defaults = {
get_toggle_link_text_fn: function(tags) {
var text = "";
var num_tags = obj_length(tags);
if (num_tags > 0) {
text = num_tags + (num_tags > 1 ? " Tags" : " Tag");
} else {
text = "Add tags";
}
return text;
},
tag_click_fn : function (name, value) {},
editable: true,
input_size: 20,
in_form: false,
tags : {},
use_toggle_link: true,
item_id: "",
add_tag_img: "",
add_tag_img_rollover: "",
delete_tag_img: "",
ajax_autocomplete_tag_url: "",
ajax_retag_url: "",
ajax_delete_tag_url: "",
ajax_add_tag_url: ""
};
var settings = jQuery.extend(defaults, options);
//
// Initalize object's elements.
//
// Get elements for this object. For this_obj, assume the last element with the id is the "this"; this is somewhat of a hack to address the problem
// that there may be two tagging elements for a single item if there are both community and individual tags for an element.
var this_obj = $(this);
var tag_area = this_obj.find('.tag-area');
var toggle_link = this_obj.find('.toggle-link');
var tag_input_field = this_obj.find('.tag-input');
var add_tag_button = this_obj.find('.add-tag-button');
// Initialize toggle link.
toggle_link.click( function() {
// Take special actions depending on whether toggle is showing or hiding link.
var after_toggle_fn;
if (tag_area.is(":hidden")) {
after_toggle_fn = function() {
// If there are no tags, go right to editing mode by generating a click on the area.
var num_tags = $(this).find('.tag-button').length;
if (num_tags === 0) {
tag_area.click();
}
};
} else {
after_toggle_fn = function() {
tag_area.blur();
};
}
tag_area.slideToggle("fast", after_toggle_fn);
return $(this);
});
// Initialize tag input field.
if (settings.editable) {
tag_input_field.hide();
}
tag_input_field.keyup( function(e) {
if ( e.keyCode === 27 ) {
// Escape key
$(this).trigger( "blur" );
} else if (
( e.keyCode === 13 ) || // Return Key
( e.keyCode === 188 ) || // Comma
( e.keyCode === 32 ) // Space
) {
//
// Check input.
//
var new_value = this.value;
// Do nothing if return key was used to autocomplete.
if (return_key_pressed_for_autocomplete === true) {
return_key_pressed_for_autocomplete = false;
return false;
}
// Suppress space after a ":"
if ( new_value.indexOf(": ", new_value.length - 2) !== -1) {
this.value = new_value.substring(0, new_value.length-1);
return false;
}
// Remove trigger keys from input.
if ( (e.keyCode === 188) || (e.keyCode === 32) ) {
new_value = new_value.substring( 0 , new_value.length - 1 );
}
// Trim whitespace.
new_value = $.trim(new_value);
// Too short?
if (new_value.length < 2) {
return false;
}
//
// New tag OK - apply it.
//
this.value = ""; // Reset text field now that tag is being added
// Add button for tag after all other tag buttons.
var new_tag_button = build_tag_button(new_value);
var tag_buttons = tag_area.children(".tag-button");
if (tag_buttons.length !== 0) {
var last_tag_button = tag_buttons.slice(tag_buttons.length-1);
last_tag_button.after(new_tag_button);
} else {
tag_area.prepend(new_tag_button);
}
// Add tag to internal list.
var tag_name_and_value = new_value.split(":");
settings.tags[tag_name_and_value[0]] = tag_name_and_value[1];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
// Commit tag to server.
var zz = $(this);
$.ajax({
url: settings.ajax_add_tag_url,
data: { new_tag: new_value },
error: function() {
// Failed. Roll back changes and show alert.
new_tag_button.remove();
delete settings.tags[tag_name_and_value[0]];
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
alert( "Add tag failed" );
},
success: function() {
// Flush autocomplete cache because it's not out of date.
// TODO: in the future, we could remove the particular item
// that was chosen from the cache rather than flush it.
zz.flushCache();
}
});
return false;
}
});
// Add autocomplete to input.
var format_item_func = function(key, row_position, num_rows, value, search_term) {
var tag_name_and_value = value.split(":");
return (tag_name_and_value.length === 1 ? tag_name_and_value[0] : tag_name_and_value[1]);
};
var autocomplete_options = { selectFirst: false, formatItem: format_item_func,
autoFill: false, highlight: false };
tag_input_field.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options);
// Initialize delete tag images for current tags.
this_obj.find('.delete-tag-img').each(function() {
init_delete_tag_image( $(this) );
});
// Initialize tag click function.
init_tag_click_function($(this), settings.tag_click_fn);
// Initialize "add tag" button.
add_tag_button.click( function() {
$(this).hide();
// Clicking on button is the same as clicking on the tag area.
tag_area.click();
return false;
});
//
// Set up tag area interactions; these are needed only if tags are editable.
//
if (settings.editable) {
// When the tag area blurs, go to "view tag" mode.
tag_area.bind("blur", function(e) {
if (obj_length(settings.tags) > 0) {
add_tag_button.show();
tag_input_field.hide();
tag_area.removeClass("active-tag-area");
tag_area.addClass("tooltip");
} else {
// No tags, so do nothing to ensure that input is still visible.
}
});
// On click, enable user to add tags.
tag_area.click( function(e) {
var is_active = $(this).hasClass("active-tag-area");
// If a "delete image" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("delete-tag-img") && !is_active) {
return false;
}
// If a "tag name" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("tag-name") && !is_active) {
return false;
}
// Remove tooltip.
$(this).removeClass("tooltip");
// Hide add tag button, show tag_input field. Change background to show
// area is active.
$(this).addClass("active-tag-area");
add_tag_button.hide();
tag_input_field.show();
tag_input_field.focus();
// Add handler to document that will call blur when the tag area is blurred;
// a tag area is blurred when a user clicks on an element outside the area.
var handle_document_click = function(e) {
var check_click = function(tag_area, target) {
var tag_area_id = tag_area.attr("id");
// Blur the tag area if the element clicked on is not in the tag area.
if (target !== tag_area) {
tag_area.blur();
$(window).unbind("click.tagging_blur");
$(this).addClass("tooltip");
}
};
check_click(tag_area, $(e.target));
};
// TODO: we should attach the click handler to all frames in order to capture
// clicks outside the frame that this element is in.
//window.parent.document.onclick = handle_document_click;
//var temp = $(window.parent.document.body).contents().find("iframe").html();
//alert(temp);
//$(document).parent().click(handle_document_click);
$(window).bind("click.tagging_blur", handle_document_click);
return false;
});
}
// If using toggle link, hide the tag area. Otherwise, show the tag area.
if (settings.use_toggle_link) {
tag_area.hide();
}
//
// Helper functions.
//
//
// Collapse tag name + value into a single string.
//
function build_tag_str(tag_name, tag_value) {
return tag_name + ( tag_value ? ":" + tag_value : "");
}
// Initialize a "delete tag image": when click, delete tag from UI and send delete request to server.
function init_delete_tag_image(delete_img) {
$(delete_img).mouseenter( function () {
$(this).attr("src", settings.delete_tag_img_rollover);
});
$(delete_img).mouseleave( function () {
$(this).attr("src", settings.delete_tag_img);
});
$(delete_img).click( function () {
// Tag button is image's parent.
var tag_button = $(this).parent();
// Get tag name, value.
var tag_name_elt = tag_button.find(".tag-name").eq(0);
var tag_str = tag_name_elt.text();
var tag_name_and_value = tag_str.split(":");
var tag_name = tag_name_and_value[0];
var tag_value = tag_name_and_value[1];
var prev_button = tag_button.prev();
tag_button.remove();
// Remove tag from local list for consistency.
delete settings.tags[tag_name];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
// Delete tag.
$.ajax({
url: settings.ajax_delete_tag_url,
data: { tag_name: tag_name },
error: function() {
// Failed. Roll back changes and show alert.
settings.tags[tag_name] = tag_value;
if (prev_button.hasClass("tag-button")) {
prev_button.after(tag_button);
} else {
tag_area.prepend(tag_button);
}
alert( "Remove tag failed" );
toggle_link.text(settings.get_toggle_link_text_fn(settings.tags));
// TODO: no idea why it's necessary to set this up again.
delete_img.mouseenter( function () {
$(this).attr("src", settings.delete_tag_img_rollover);
});
delete_img.mouseleave( function () {
$(this).attr("src", settings.delete_tag_img);
});
},
success: function() {}
});
return true;
});
}
//
// Function that builds a tag button.
//
function build_tag_button(tag_str) {
// Build "delete tag" image.
var delete_img = $("
").attr("src", settings.delete_tag_img).addClass("delete-tag-img");
init_delete_tag_image(delete_img);
// Build tag button.
var tag_name_elt = $("").text(tag_str).addClass("tag-name");
tag_name_elt.click( function() {
var tag_name_and_value = tag_str.split(":");
settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]);
return true;
});
var tag_button = $("").addClass("tag-button");
tag_button.append(tag_name_elt);
// Allow delete only if element is editable.
if (settings.editable) {
tag_button.append(delete_img);
}
return tag_button;
}
};