Added new autocomplete for task assignments.
Introduced the cool jquery token plugin.
This commit is contained in:
parent
2e56bfe895
commit
5d028ccf3a
10 changed files with 880 additions and 13 deletions
11
app/controllers/users_controller.rb
Normal file
11
app/controllers/users_controller.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class UsersController < ApplicationController
|
||||
|
||||
# Currently used to display users nick and ids for autocomplete
|
||||
def index
|
||||
@users = User.where("nick LIKE ?", "%#{params[:q]}%")
|
||||
respond_to do |format|
|
||||
format.json { render :json => @users.map { |u| {:id => u.id, :name => u.nick} } }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
2
app/helpers/users_helper.rb
Normal file
2
app/helpers/users_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module UsersHelper
|
||||
end
|
|
@ -27,13 +27,13 @@ class Task < ActiveRecord::Base
|
|||
def enough_users_assigned?
|
||||
assignments.find_all_by_accepted(true).size >= required_users ? true : false
|
||||
end
|
||||
|
||||
# extracts nicknames from a comma seperated string
|
||||
|
||||
# Get users from comma seperated ids
|
||||
# and makes the users responsible for the task
|
||||
def user_list=(string)
|
||||
@user_list = string.split(%r{,\s*})
|
||||
new_users = @user_list - users.collect(&:nick)
|
||||
old_users = users.reject { |user| @user_list.include?(user.nick) }
|
||||
def user_list=(ids)
|
||||
list = ids.split(",")
|
||||
new_users = list - users.collect(&:id)
|
||||
old_users = users.reject { |user| list.include?(user.id) }
|
||||
|
||||
logger.debug "New users: #{new_users}"
|
||||
logger.debug "Old users: #{old_users}"
|
||||
|
@ -44,8 +44,8 @@ class Task < ActiveRecord::Base
|
|||
assignments.find(:all, :conditions => ["user_id IN (?)", old_users.collect(&:id)]).each(&:destroy)
|
||||
end
|
||||
# create new assignments
|
||||
new_users.each do |nick|
|
||||
user = User.find_by_nick(nick)
|
||||
new_users.each do |id|
|
||||
user = User.find(id)
|
||||
if user.blank?
|
||||
errors.add(:user_list)
|
||||
else
|
||||
|
@ -62,7 +62,7 @@ class Task < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def user_list
|
||||
@user_list ||= users.collect(&:nick).join(", ")
|
||||
@user_list ||= users.collect(&:id).join(", ")
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
%head
|
||||
%meta{"http-equiv" => "content-type", :content => "text/html;charset=UTF-8"}
|
||||
%title= "FoodSoft - " + (yield(:title) or controller.controller_name)
|
||||
= stylesheet_link_tag 'main', 'rails_messages', 'nav', 'simple_form', :cache => "all_cached"
|
||||
= stylesheet_link_tag 'main', 'rails_messages', 'nav', 'simple_form', 'token-input', :cache => "all_cached"
|
||||
= stylesheet_link_tag "print", :media => "print"
|
||||
<!--[if lte IE 7]>
|
||||
= stylesheet_link_tag 'ie_hacks'
|
||||
<![endif]-->
|
||||
= javascript_include_tag 'jquery.min', 'jquery-ui.min', 'jquery_ujs', 'application', 'ordering', :cache => "all_cached"
|
||||
= javascript_include_tag 'jquery.min', 'jquery-ui.min', 'jquery_ujs', 'jquery.tokeninput', 'application', 'ordering', :cache => "all_cached"
|
||||
= yield(:head)
|
||||
%body
|
||||
#logininfo= render :partial => 'shared/loginInfo'
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
- content_for :head do
|
||||
:javascript
|
||||
$(function() {
|
||||
$("#task_user_list").tokenInput("#{users_path(:format => :json)}", {
|
||||
crossDomain: false,
|
||||
prePopulate: $("#task_user_list").data("pre")
|
||||
});
|
||||
});
|
||||
|
||||
= simple_form_for @task do |f|
|
||||
= f.input :name
|
||||
= f.input :description
|
||||
= f.input :duration, :as => :select, :collection => 1..3
|
||||
= f.input :user_list, :as => :string
|
||||
= f.input :user_list, :as => :string, :input_html => { 'data-pre' => @task.users.map { |u| {:id => u.id, :name => u.nick} }.to_json }
|
||||
= f.input :required_users
|
||||
= f.association :workgroup
|
||||
= f.input :due_date, :include_blank => true
|
||||
|
|
|
@ -23,7 +23,6 @@ Foodsoft::Application.routes.draw do
|
|||
match '/home/profile' => 'home#profile', :as => 'my_profile'
|
||||
match '/home/ordergroup' => 'home#ordergroup', :as => 'my_ordergroup'
|
||||
|
||||
|
||||
############ Wiki
|
||||
|
||||
resources :pages do
|
||||
|
@ -157,6 +156,8 @@ Foodsoft::Application.routes.draw do
|
|||
|
||||
############## The rest
|
||||
|
||||
resources :users, :only => [:index]
|
||||
|
||||
match '/:controller(/:action(/:id))'
|
||||
|
||||
end # End of /:foodcoop scope
|
||||
|
|
718
public/javascripts/jquery.tokeninput.js
Normal file
718
public/javascripts/jquery.tokeninput.js
Normal file
|
@ -0,0 +1,718 @@
|
|||
/*
|
||||
* jQuery Plugin: Tokenizing Autocomplete Text Entry
|
||||
* Version 1.4.2
|
||||
*
|
||||
* Copyright (c) 2009 James Smith (http://loopj.com)
|
||||
* Licensed jointly under the GPL and MIT licenses,
|
||||
* choose which one suits your project best!
|
||||
*
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// Default settings
|
||||
var DEFAULT_SETTINGS = {
|
||||
hintText: "Type in a search term",
|
||||
noResultsText: "No results",
|
||||
searchingText: "Searching...",
|
||||
deleteText: "×",
|
||||
searchDelay: 300,
|
||||
minChars: 1,
|
||||
tokenLimit: null,
|
||||
jsonContainer: null,
|
||||
method: "GET",
|
||||
contentType: "json",
|
||||
queryParam: "q",
|
||||
tokenDelimiter: ",",
|
||||
preventDuplicates: false,
|
||||
prePopulate: null,
|
||||
animateDropdown: true,
|
||||
onResult: null,
|
||||
onAdd: null,
|
||||
onDelete: null
|
||||
};
|
||||
|
||||
// Default classes to use when theming
|
||||
var DEFAULT_CLASSES = {
|
||||
tokenList: "token-input-list",
|
||||
token: "token-input-token",
|
||||
tokenDelete: "token-input-delete-token",
|
||||
selectedToken: "token-input-selected-token",
|
||||
highlightedToken: "token-input-highlighted-token",
|
||||
dropdown: "token-input-dropdown",
|
||||
dropdownItem: "token-input-dropdown-item",
|
||||
dropdownItem2: "token-input-dropdown-item2",
|
||||
selectedDropdownItem: "token-input-selected-dropdown-item",
|
||||
inputToken: "token-input-input-token"
|
||||
};
|
||||
|
||||
// Input box position "enum"
|
||||
var POSITION = {
|
||||
BEFORE: 0,
|
||||
AFTER: 1,
|
||||
END: 2
|
||||
};
|
||||
|
||||
// Keys "enum"
|
||||
var KEY = {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
ESCAPE: 27,
|
||||
SPACE: 32,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
END: 35,
|
||||
HOME: 36,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
NUMPAD_ENTER: 108,
|
||||
COMMA: 188
|
||||
};
|
||||
|
||||
|
||||
// Expose the .tokenInput function to jQuery as a plugin
|
||||
$.fn.tokenInput = function (url_or_data, options) {
|
||||
var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
|
||||
|
||||
return this.each(function () {
|
||||
new $.TokenList(this, url_or_data, settings);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// TokenList class for each input
|
||||
$.TokenList = function (input, url_or_data, settings) {
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
|
||||
// Configure the data source
|
||||
if($.type(url_or_data) === "string") {
|
||||
// Set the url to query against
|
||||
settings.url = url_or_data;
|
||||
|
||||
// Make a smart guess about cross-domain if it wasn't explicitly specified
|
||||
if(settings.crossDomain === undefined) {
|
||||
if(settings.url.indexOf("://") === -1) {
|
||||
settings.crossDomain = false;
|
||||
} else {
|
||||
settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
|
||||
}
|
||||
}
|
||||
} else if($.type(url_or_data) === "array") {
|
||||
// Set the local data to search through
|
||||
settings.local_data = url_or_data;
|
||||
}
|
||||
|
||||
// Build class names
|
||||
if(settings.classes) {
|
||||
// Use custom class names
|
||||
settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
|
||||
} else if(settings.theme) {
|
||||
// Use theme-suffixed default class names
|
||||
settings.classes = {};
|
||||
$.each(DEFAULT_CLASSES, function(key, value) {
|
||||
settings.classes[key] = value + "-" + settings.theme;
|
||||
});
|
||||
} else {
|
||||
settings.classes = DEFAULT_CLASSES;
|
||||
}
|
||||
|
||||
|
||||
// Save the tokens
|
||||
var saved_tokens = [];
|
||||
|
||||
// Keep track of the number of tokens in the list
|
||||
var token_count = 0;
|
||||
|
||||
// Basic cache to save on db hits
|
||||
var cache = new $.TokenList.Cache();
|
||||
|
||||
// Keep track of the timeout, old vals
|
||||
var timeout;
|
||||
var input_val;
|
||||
|
||||
// Create a new text input an attach keyup events
|
||||
var input_box = $("<input type=\"text\" autocomplete=\"off\">")
|
||||
.css({
|
||||
outline: "none"
|
||||
})
|
||||
.focus(function () {
|
||||
if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
|
||||
show_dropdown_hint();
|
||||
}
|
||||
})
|
||||
.blur(function () {
|
||||
hide_dropdown();
|
||||
})
|
||||
.bind("keyup keydown blur update", resize_input)
|
||||
.keydown(function (event) {
|
||||
var previous_token;
|
||||
var next_token;
|
||||
|
||||
switch(event.keyCode) {
|
||||
case KEY.LEFT:
|
||||
case KEY.RIGHT:
|
||||
case KEY.UP:
|
||||
case KEY.DOWN:
|
||||
if(!$(this).val()) {
|
||||
previous_token = input_token.prev();
|
||||
next_token = input_token.next();
|
||||
|
||||
if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
|
||||
// Check if there is a previous/next token and it is selected
|
||||
if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
|
||||
deselect_token($(selected_token), POSITION.BEFORE);
|
||||
} else {
|
||||
deselect_token($(selected_token), POSITION.AFTER);
|
||||
}
|
||||
} else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
|
||||
// We are moving left, select the previous token if it exists
|
||||
select_token($(previous_token.get(0)));
|
||||
} else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
|
||||
// We are moving right, select the next token if it exists
|
||||
select_token($(next_token.get(0)));
|
||||
}
|
||||
} else {
|
||||
var dropdown_item = null;
|
||||
|
||||
if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
|
||||
dropdown_item = $(selected_dropdown_item).next();
|
||||
} else {
|
||||
dropdown_item = $(selected_dropdown_item).prev();
|
||||
}
|
||||
|
||||
if(dropdown_item.length) {
|
||||
select_dropdown_item(dropdown_item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.BACKSPACE:
|
||||
previous_token = input_token.prev();
|
||||
|
||||
if(!$(this).val().length) {
|
||||
if(selected_token) {
|
||||
delete_token($(selected_token));
|
||||
} else if(previous_token.length) {
|
||||
select_token($(previous_token.get(0)));
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if($(this).val().length === 1) {
|
||||
hide_dropdown();
|
||||
} else {
|
||||
// set a timeout just long enough to let this function finish.
|
||||
setTimeout(function(){do_search();}, 5);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.TAB:
|
||||
case KEY.ENTER:
|
||||
case KEY.NUMPAD_ENTER:
|
||||
case KEY.COMMA:
|
||||
if(selected_dropdown_item) {
|
||||
add_token($(selected_dropdown_item));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESCAPE:
|
||||
hide_dropdown();
|
||||
return true;
|
||||
|
||||
default:
|
||||
if(String.fromCharCode(event.which)) {
|
||||
// set a timeout just long enough to let this function finish.
|
||||
setTimeout(function(){do_search();}, 5);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Keep a reference to the original input box
|
||||
var hidden_input = $(input)
|
||||
.hide()
|
||||
.val("")
|
||||
.focus(function () {
|
||||
input_box.focus();
|
||||
})
|
||||
.blur(function () {
|
||||
input_box.blur();
|
||||
});
|
||||
|
||||
// Keep a reference to the selected token and dropdown item
|
||||
var selected_token = null;
|
||||
var selected_dropdown_item = null;
|
||||
|
||||
// The list to store the token items in
|
||||
var token_list = $("<ul />")
|
||||
.addClass(settings.classes.tokenList)
|
||||
.click(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
|
||||
toggle_select_token(li);
|
||||
} else {
|
||||
// Deselect selected token
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
|
||||
// Focus input box
|
||||
input_box.focus();
|
||||
}
|
||||
})
|
||||
.mouseover(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && selected_token !== this) {
|
||||
li.addClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.mouseout(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && selected_token !== this) {
|
||||
li.removeClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.insertBefore(hidden_input);
|
||||
|
||||
// The token holding the input box
|
||||
var input_token = $("<li />")
|
||||
.addClass(settings.classes.inputToken)
|
||||
.appendTo(token_list)
|
||||
.append(input_box);
|
||||
|
||||
// The list to store the dropdown items in
|
||||
var dropdown = $("<div>")
|
||||
.addClass(settings.classes.dropdown)
|
||||
.appendTo("body")
|
||||
.hide();
|
||||
|
||||
// Magic element to help us resize the text input
|
||||
var input_resizer = $("<tester/>")
|
||||
.insertAfter(input_box)
|
||||
.css({
|
||||
position: "absolute",
|
||||
top: -9999,
|
||||
left: -9999,
|
||||
width: "auto",
|
||||
fontSize: input_box.css("fontSize"),
|
||||
fontFamily: input_box.css("fontFamily"),
|
||||
fontWeight: input_box.css("fontWeight"),
|
||||
letterSpacing: input_box.css("letterSpacing"),
|
||||
whiteSpace: "nowrap"
|
||||
});
|
||||
|
||||
// Pre-populate list if items exist
|
||||
hidden_input.val("");
|
||||
li_data = settings.prePopulate || hidden_input.data("pre");
|
||||
if(li_data && li_data.length) {
|
||||
$.each(li_data, function (index, value) {
|
||||
insert_token(value.id, value.name);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Private functions
|
||||
//
|
||||
|
||||
function resize_input() {
|
||||
if(input_val === (input_val = input_box.val())) {return;}
|
||||
|
||||
// Enter new content into resizer and resize input accordingly
|
||||
var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
input_resizer.html(escaped);
|
||||
input_box.width(input_resizer.width() + 30);
|
||||
}
|
||||
|
||||
function is_printable_character(keycode) {
|
||||
return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
|
||||
(keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
|
||||
(keycode >= 186 && keycode <= 192) || // ; = , - . / ^
|
||||
(keycode >= 219 && keycode <= 222)); // ( \ ) '
|
||||
}
|
||||
|
||||
// Inner function to a token to the list
|
||||
function insert_token(id, value) {
|
||||
var this_token = $("<li><p>"+ value +"</p> </li>")
|
||||
.addClass(settings.classes.token)
|
||||
.insertBefore(input_token);
|
||||
|
||||
// The 'delete token' button
|
||||
$("<span>" + settings.deleteText + "</span>")
|
||||
.addClass(settings.classes.tokenDelete)
|
||||
.appendTo(this_token)
|
||||
.click(function () {
|
||||
delete_token($(this).parent());
|
||||
return false;
|
||||
});
|
||||
|
||||
// Store data on the token
|
||||
var token_data = {"id": id, "name": value};
|
||||
$.data(this_token.get(0), "tokeninput", token_data);
|
||||
|
||||
// Save this token for duplicate checking
|
||||
saved_tokens.push(token_data);
|
||||
|
||||
// Update the hidden input
|
||||
var token_ids = $.map(saved_tokens, function (el) {
|
||||
return el.id;
|
||||
});
|
||||
hidden_input.val(token_ids.join(settings.tokenDelimiter));
|
||||
|
||||
token_count += 1;
|
||||
|
||||
return this_token;
|
||||
}
|
||||
|
||||
// Add a token to the token list based on user input
|
||||
function add_token (item) {
|
||||
var li_data = $.data(item.get(0), "tokeninput");
|
||||
var callback = settings.onAdd;
|
||||
|
||||
// See if the token already exists and select it if we don't want duplicates
|
||||
if(token_count > 0 && settings.preventDuplicates) {
|
||||
var found_existing_token = null;
|
||||
token_list.children().each(function () {
|
||||
var existing_token = $(this);
|
||||
var existing_data = $.data(existing_token.get(0), "tokeninput");
|
||||
if(existing_data && existing_data.id === li_data.id) {
|
||||
found_existing_token = existing_token;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(found_existing_token) {
|
||||
select_token(found_existing_token);
|
||||
input_token.insertAfter(found_existing_token);
|
||||
input_box.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new tokens
|
||||
insert_token(li_data.id, li_data.name);
|
||||
|
||||
// Check the token limit
|
||||
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
|
||||
input_box.hide();
|
||||
hide_dropdown();
|
||||
return;
|
||||
} else {
|
||||
input_box.focus();
|
||||
}
|
||||
|
||||
// Clear input box
|
||||
input_box.val("");
|
||||
|
||||
// Don't show the help dropdown, they've got the idea
|
||||
hide_dropdown();
|
||||
|
||||
// Execute the onAdd callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback(li_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Select a token in the token list
|
||||
function select_token (token) {
|
||||
token.addClass(settings.classes.selectedToken);
|
||||
selected_token = token.get(0);
|
||||
|
||||
// Hide input box
|
||||
input_box.val("");
|
||||
|
||||
// Hide dropdown if it is visible (eg if we clicked to select token)
|
||||
hide_dropdown();
|
||||
}
|
||||
|
||||
// Deselect a token in the token list
|
||||
function deselect_token (token, position) {
|
||||
token.removeClass(settings.classes.selectedToken);
|
||||
selected_token = null;
|
||||
|
||||
if(position === POSITION.BEFORE) {
|
||||
input_token.insertBefore(token);
|
||||
} else if(position === POSITION.AFTER) {
|
||||
input_token.insertAfter(token);
|
||||
} else {
|
||||
input_token.appendTo(token_list);
|
||||
}
|
||||
|
||||
// Show the input box and give it focus again
|
||||
input_box.focus();
|
||||
}
|
||||
|
||||
// Toggle selection of a token in the token list
|
||||
function toggle_select_token(token) {
|
||||
var previous_selected_token = selected_token;
|
||||
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
|
||||
if(previous_selected_token === token.get(0)) {
|
||||
deselect_token(token, POSITION.END);
|
||||
} else {
|
||||
select_token(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a token from the token list
|
||||
function delete_token (token) {
|
||||
// Remove the id from the saved list
|
||||
var token_data = $.data(token.get(0), "tokeninput");
|
||||
var callback = settings.onDelete;
|
||||
|
||||
// Delete the token
|
||||
token.remove();
|
||||
selected_token = null;
|
||||
|
||||
// Show the input box and give it focus again
|
||||
input_box.focus();
|
||||
|
||||
// Remove this token from the saved list
|
||||
saved_tokens = $.grep(saved_tokens, function (val) {
|
||||
return (val.id !== token_data.id);
|
||||
});
|
||||
|
||||
// Update the hidden input
|
||||
var token_ids = $.map(saved_tokens, function (el) {
|
||||
return el.id;
|
||||
});
|
||||
hidden_input.val(token_ids.join(settings.tokenDelimiter));
|
||||
|
||||
token_count -= 1;
|
||||
|
||||
if(settings.tokenLimit !== null) {
|
||||
input_box
|
||||
.show()
|
||||
.val("")
|
||||
.focus();
|
||||
}
|
||||
|
||||
// Execute the onDelete callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback(token_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide and clear the results dropdown
|
||||
function hide_dropdown () {
|
||||
dropdown.hide().empty();
|
||||
selected_dropdown_item = null;
|
||||
}
|
||||
|
||||
function show_dropdown() {
|
||||
dropdown
|
||||
.css({
|
||||
position: "absolute",
|
||||
top: $(token_list).offset().top + $(token_list).outerHeight(),
|
||||
left: $(token_list).offset().left,
|
||||
zindex: 999
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
function show_dropdown_searching () {
|
||||
if(settings.searchingText) {
|
||||
dropdown.html("<p>"+settings.searchingText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function show_dropdown_hint () {
|
||||
if(settings.hintText) {
|
||||
dropdown.html("<p>"+settings.hintText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the query part of the search term
|
||||
function highlight_term(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
|
||||
}
|
||||
|
||||
// Populate the results dropdown with some results
|
||||
function populate_dropdown (query, results) {
|
||||
if(results && results.length) {
|
||||
dropdown.empty();
|
||||
var dropdown_ul = $("<ul>")
|
||||
.appendTo(dropdown)
|
||||
.mouseover(function (event) {
|
||||
select_dropdown_item($(event.target).closest("li"));
|
||||
})
|
||||
.mousedown(function (event) {
|
||||
add_token($(event.target).closest("li"));
|
||||
return false;
|
||||
})
|
||||
.hide();
|
||||
|
||||
$.each(results, function(index, value) {
|
||||
var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
|
||||
.appendTo(dropdown_ul);
|
||||
|
||||
if(index % 2) {
|
||||
this_li.addClass(settings.classes.dropdownItem);
|
||||
} else {
|
||||
this_li.addClass(settings.classes.dropdownItem2);
|
||||
}
|
||||
|
||||
if(index === 0) {
|
||||
select_dropdown_item(this_li);
|
||||
}
|
||||
|
||||
$.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
|
||||
});
|
||||
|
||||
show_dropdown();
|
||||
|
||||
if(settings.animateDropdown) {
|
||||
dropdown_ul.slideDown("fast");
|
||||
} else {
|
||||
dropdown_ul.show();
|
||||
}
|
||||
} else {
|
||||
if(settings.noResultsText) {
|
||||
dropdown.html("<p>"+settings.noResultsText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight an item in the results dropdown
|
||||
function select_dropdown_item (item) {
|
||||
if(item) {
|
||||
if(selected_dropdown_item) {
|
||||
deselect_dropdown_item($(selected_dropdown_item));
|
||||
}
|
||||
|
||||
item.addClass(settings.classes.selectedDropdownItem);
|
||||
selected_dropdown_item = item.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove highlighting from an item in the results dropdown
|
||||
function deselect_dropdown_item (item) {
|
||||
item.removeClass(settings.classes.selectedDropdownItem);
|
||||
selected_dropdown_item = null;
|
||||
}
|
||||
|
||||
// Do a search and show the "searching" dropdown if the input is longer
|
||||
// than settings.minChars
|
||||
function do_search() {
|
||||
var query = input_box.val().toLowerCase();
|
||||
|
||||
if(query && query.length) {
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.AFTER);
|
||||
}
|
||||
|
||||
if(query.length >= settings.minChars) {
|
||||
show_dropdown_searching();
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(function(){
|
||||
run_search(query);
|
||||
}, settings.searchDelay);
|
||||
} else {
|
||||
hide_dropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do the actual search
|
||||
function run_search(query) {
|
||||
var cached_results = cache.get(query);
|
||||
if(cached_results) {
|
||||
populate_dropdown(query, cached_results);
|
||||
} else {
|
||||
// Are we doing an ajax search or local data search?
|
||||
if(settings.url) {
|
||||
// Extract exisiting get params
|
||||
var ajax_params = {};
|
||||
ajax_params.data = {};
|
||||
if(settings.url.indexOf("?") > -1) {
|
||||
var parts = settings.url.split("?");
|
||||
ajax_params.url = parts[0];
|
||||
|
||||
var param_array = parts[1].split("&");
|
||||
$.each(param_array, function (index, value) {
|
||||
var kv = value.split("=");
|
||||
ajax_params.data[kv[0]] = kv[1];
|
||||
});
|
||||
} else {
|
||||
ajax_params.url = settings.url;
|
||||
}
|
||||
|
||||
// Prepare the request
|
||||
ajax_params.data[settings.queryParam] = query;
|
||||
ajax_params.type = settings.method;
|
||||
ajax_params.dataType = settings.contentType;
|
||||
if(settings.crossDomain) {
|
||||
ajax_params.dataType = "jsonp";
|
||||
}
|
||||
|
||||
// Attach the success callback
|
||||
ajax_params.success = function(results) {
|
||||
if($.isFunction(settings.onResult)) {
|
||||
results = settings.onResult.call(this, results);
|
||||
}
|
||||
cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
|
||||
// only populate the dropdown if the results are associated with the active search query
|
||||
if(input_box.val().toLowerCase() === query) {
|
||||
populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
}
|
||||
};
|
||||
|
||||
// Make the request
|
||||
$.ajax(ajax_params);
|
||||
} else if(settings.local_data) {
|
||||
// Do the search through local data
|
||||
var results = $.grep(settings.local_data, function (row) {
|
||||
return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
|
||||
});
|
||||
|
||||
populate_dropdown(query, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Really basic cache for the results
|
||||
$.TokenList.Cache = function (options) {
|
||||
var settings = $.extend({
|
||||
max_size: 500
|
||||
}, options);
|
||||
|
||||
var data = {};
|
||||
var size = 0;
|
||||
|
||||
var flush = function () {
|
||||
data = {};
|
||||
size = 0;
|
||||
};
|
||||
|
||||
this.add = function (query, results) {
|
||||
if(size > settings.max_size) {
|
||||
flush();
|
||||
}
|
||||
|
||||
if(!data[query]) {
|
||||
size += 1;
|
||||
}
|
||||
|
||||
data[query] = results;
|
||||
};
|
||||
|
||||
this.get = function (query) {
|
||||
return data[query];
|
||||
};
|
||||
};
|
||||
}(jQuery));
|
113
public/stylesheets/token-input.css
Normal file
113
public/stylesheets/token-input.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
/* Example tokeninput style #1: Token vertical list*/
|
||||
ul.token-input-list {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
width: 400px;
|
||||
border: 1px solid #999;
|
||||
cursor: text;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 999;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
list-style-type: none;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
ul.token-input-list li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.token-input-list li input {
|
||||
border: 0;
|
||||
width: 350px;
|
||||
padding: 3px 8px;
|
||||
background-color: white;
|
||||
-webkit-appearance: caret;
|
||||
}
|
||||
|
||||
li.token-input-token {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 3px;
|
||||
padding: 3px 5px;
|
||||
background-color: #d0efa0;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li.token-input-token p {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li.token-input-token span {
|
||||
float: right;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.token-input-selected-token {
|
||||
background-color: #08844e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
li.token-input-selected-token span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
div.token-input-dropdown {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.token-input-dropdown p {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li {
|
||||
background-color: #fff;
|
||||
padding: 3px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item2 {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-selected-dropdown-item {
|
||||
background-color: #d0efa0;
|
||||
}
|
||||
|
9
test/functional/users_controller_test.rb
Normal file
9
test/functional/users_controller_test.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
require 'test_helper'
|
||||
|
||||
class UsersControllerTest < ActionController::TestCase
|
||||
test "should get index" do
|
||||
get :index
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
end
|
4
test/unit/helpers/users_helper_test.rb
Normal file
4
test/unit/helpers/users_helper_test.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
require 'test_helper'
|
||||
|
||||
class UsersHelperTest < ActionView::TestCase
|
||||
end
|
Loading…
Reference in a new issue