From 038599bf8f22a79ed98155acadf88fae0e73d8d0 Mon Sep 17 00:00:00 2001 From: Florian Staudacher <florian_staudacher@yahoo.de> Date: Sun, 8 Jul 2012 21:32:04 +0200 Subject: [PATCH] update jquery.mentionsInput to latest version + make some tests a little nicer --- app/assets/javascripts/mentions.js | 23 +++- app/views/people/show.html.haml | 2 +- features/step_definitions/custom_web_steps.rb | 5 +- features/step_definitions/debug_steps.rb | 17 ++- .../javascripts/jquery.mentionsInput.js | 116 +++++++++++------- 5 files changed, 113 insertions(+), 50 deletions(-) diff --git a/app/assets/javascripts/mentions.js b/app/assets/javascripts/mentions.js index b6e2304373..851337e389 100644 --- a/app/assets/javascripts/mentions.js +++ b/app/assets/javascripts/mentions.js @@ -3,12 +3,31 @@ var Mentions = { return mentionsInput.mentionsInput(Mentions.options); }, + // pre-fetch the list of contacts for the current user. + // called by the initializer of the publisher, for faster ('offline') + // execution of the filtering for mentions fetchContacts : function(){ Mentions.contacts || $.getJSON("/contacts", function(data) { - Mentions.contacts = data; + Mentions.contacts = Mentions.createList(data); }); }, + // creates a list of mentions out of a list of contacts + // @see _contactToMention + createList: function(contacts) { + return _.map(contacts, Mentions._contactToMention); + }, + + // takes a given contact object and modifies to fit the format + // expected by the jQuery.mentionsInput plugin. + // @see http://podio.github.com/jquery-mentions-input/ + _contactToMention: function(contact) { + contact.value = contact.name; + return contact; + }, + + // default options for jQuery.mentionsInput + // @see http://podio.github.com/jquery-mentions-input/ options: { elastic: false, minChars: 1, @@ -20,7 +39,7 @@ var Mentions = { }, templates: { - mentionItemSyntax: _.template("@{<%= mention.name %> ; <%= mention.handle %>}") + mentionItemSyntax: _.template("@{<%= name %> ; <%= handle %>}") } } }; diff --git a/app/views/people/show.html.haml b/app/views/people/show.html.haml index 206cfad3cb..6ee3f982ac 100644 --- a/app/views/people/show.html.haml +++ b/app/views/people/show.html.haml @@ -6,7 +6,7 @@ - content_for :head do = javascript_include_tag :people :javascript - Mentions.options.prefillMention = #{@person.to_json}; + Mentions.options.prefillMention = Mentions._contactToMention(#{@person.to_json}); - content_for :page_title do = @person.name diff --git a/features/step_definitions/custom_web_steps.rb b/features/step_definitions/custom_web_steps.rb index 298ba941cd..11cc641ed9 100644 --- a/features/step_definitions/custom_web_steps.rb +++ b/features/step_definitions/custom_web_steps.rb @@ -38,8 +38,9 @@ Then /^the publisher should be expanded$/ do end When /^I append "([^"]*)" to the publisher$/ do |stuff| - previous_value = page.find("#status_message_fake_text").value - fill_in "status_message_fake_text", :with => previous_value + " " + stuff + elem = find('#status_message_fake_text') + elem.native.send_keys ' ' + stuff + wait_until do page.find("#status_message_text").value.match(/#{stuff}/) end diff --git a/features/step_definitions/debug_steps.rb b/features/step_definitions/debug_steps.rb index 2d5e17d433..58a36d5333 100644 --- a/features/step_definitions/debug_steps.rb +++ b/features/step_definitions/debug_steps.rb @@ -1,12 +1,21 @@ +module DebuggingCukeHelpers + def start_debugging + require 'ruby-debug' + debugger + true + end +end + +World(DebuggingCukeHelpers) + + When 'I debug' do - require 'ruby-debug' - debugger - true + start_debugging end When /^I wait for (\d+) seconds?$/ do |seconds| sleep seconds.to_i - warn "DELETEME - this step is for debugging only\n" + warn "\nDELETEME - this step is for debugging, only!\n" end When /^I open the error console$/ do diff --git a/vendor/assets/javascripts/jquery.mentionsInput.js b/vendor/assets/javascripts/jquery.mentionsInput.js index 274f24404e..40efd9db0f 100644 --- a/vendor/assets/javascripts/jquery.mentionsInput.js +++ b/vendor/assets/javascripts/jquery.mentionsInput.js @@ -1,6 +1,6 @@ /* * Mentions Input - * Version 1.0 + * Version 1.0.2 * Written by: Kenneth Auchenberg (Podio) * * Using underscore.js @@ -28,8 +28,8 @@ autocompleteListItemAvatar : _.template('<img src="<%= avatar %>" />'), autocompleteListItemIcon : _.template('<div class="icon <%= icon %>"></div>'), mentionsOverlay : _.template('<div class="mentions"><div></div></div>'), - mentionItemSyntax : _.template('@[<%= name %>](<%= type %>:<%= id %>)'), - mentionItemHighlight : _.template('<strong><span><%= name %></span></strong>') + mentionItemSyntax : _.template('@[<%= value %>](<%= type %>:<%= id %>)'), + mentionItemHighlight : _.template('<strong><span><%= value %></span></strong>') } }; @@ -56,18 +56,24 @@ domNode.focus(); } } + }, + rtrim: function(string) { + return string.replace(/\s+$/,""); } }; - var MentionsInput = function (input) { - var settings; - var elmInputBox, elmInputWrapper, elmAutocompleteList, elmWrapperBox, elmMentionsOverlay, elmActiveAutoCompleteItem; + var MentionsInput = function (settings) { + + var domInput, elmInputBox, elmInputWrapper, elmAutocompleteList, elmWrapperBox, elmMentionsOverlay, elmActiveAutoCompleteItem; var mentionsCollection = []; + var autocompleteItemCollection = {}; var inputBuffer = []; var currentDataQuery = ''; + settings = $.extend(true, {}, defaultSettings, settings ); + function initTextarea() { - elmInputBox = $(input); + elmInputBox = $(domInput); if (elmInputBox.attr('data-mentions-input') == 'true') { return; @@ -83,16 +89,19 @@ elmInputBox.bind('keypress', onInputBoxKeyPress); elmInputBox.bind('input', onInputBoxInput); elmInputBox.bind('click', onInputBoxClick); + elmInputBox.bind('blur', onInputBoxBlur); - if (settings.elastic) { + // Elastic textareas, internal setting for the Dispora guys + if( settings.elastic ) { elmInputBox.elastic(); } + } function initAutocomplete() { elmAutocompleteList = $(settings.templates.autocompleteList()); elmAutocompleteList.appendTo(elmWrapperBox); - elmAutocompleteList.delegate('li', 'click', onAutoCompleteItemClick); + elmAutocompleteList.delegate('li', 'mousedown', onAutoCompleteItemClick); } function initMentionsOverlay() { @@ -100,20 +109,20 @@ elmMentionsOverlay.prependTo(elmWrapperBox); } - function updateNames() { + function updateValues() { var syntaxMessage = getInputBoxValue(); _.each(mentionsCollection, function (mention) { - var textSyntax = settings.templates.mentionItemSyntax({ name : mention.name, type : 'contact', id : mention.id, mention: mention }); - - syntaxMessage = syntaxMessage.replace(mention.name, textSyntax); + var textSyntax = settings.templates.mentionItemSyntax(mention); + syntaxMessage = syntaxMessage.replace(mention.value, textSyntax); }); var mentionText = utils.htmlEncode(syntaxMessage); _.each(mentionsCollection, function (mention) { - var textSyntax = settings.templates.mentionItemSyntax({ name : utils.htmlEncode(mention.name), type : 'contact', id : mention.id, mention : mention }); - var textHighlight = settings.templates.mentionItemHighlight({ name : utils.htmlEncode(mention.name), mention : mention }); + var formattedMention = _.extend({}, mention, {value: utils.htmlEncode(mention.value)}); + var textSyntax = settings.templates.mentionItemSyntax(formattedMention); + var textHighlight = settings.templates.mentionItemHighlight(formattedMention); mentionText = mentionText.replace(textSyntax, textHighlight); }); @@ -133,12 +142,13 @@ var inputText = getInputBoxValue(); mentionsCollection = _.reject(mentionsCollection, function (mention, index) { - return !mention.name || inputText.indexOf(mention.name) == -1; + return !mention.value || inputText.indexOf(mention.value) == -1; }); mentionsCollection = _.compact(mentionsCollection); } function addMention(mention) { + var currentMessage = getInputBoxValue(); // Using a regex to figure out positions @@ -150,9 +160,7 @@ var start = currentMessage.substr(0, startCaretPosition); var end = currentMessage.substr(currentCaretPosition, currentMessage.length); - var startEndIndex = (start + mention.name).length; - - var updatedMessageText = start + mention.name + end; + var startEndIndex = (start + mention.value).length + 1; mentionsCollection.push(mention); @@ -162,8 +170,9 @@ hideAutoComplete(); // Mentions & syntax message + var updatedMessageText = start + mention.value + ' ' + end; elmInputBox.val(updatedMessageText); - updateNames(); + updateValues(); // Set correct focus and selection elmInputBox.focus(); @@ -175,7 +184,8 @@ } function onAutoCompleteItemClick(e) { - var mention = $(this).data("mention"); + var elmTarget = $(this); + var mention = autocompleteItemCollection[elmTarget.attr('data-uid')]; addMention(mention); @@ -186,21 +196,26 @@ resetBuffer(); } + function onInputBoxBlur(e) { + hideAutoComplete(); + } + function onInputBoxInput(e) { - updateNames(); + updateValues(); updateMentionsCollection(); hideAutoComplete(); var triggerCharIndex = _.lastIndexOf(inputBuffer, settings.triggerChar); if (triggerCharIndex > -1) { currentDataQuery = inputBuffer.slice(triggerCharIndex + 1).join(''); + currentDataQuery = utils.rtrim(currentDataQuery); _.defer(_.bind(doSearch, this, currentDataQuery)); } } function onInputBoxKeyPress(e) { - if(e.keyCode != KEY.BACKSPACE) { + if(e.keyCode !== KEY.BACKSPACE) { var typedValue = String.fromCharCode(e.which || e.keyCode); inputBuffer.push(typedValue); } @@ -212,6 +227,14 @@ if (e.keyCode == KEY.LEFT || e.keyCode == KEY.RIGHT || e.keyCode == KEY.HOME || e.keyCode == KEY.END) { // Defer execution to ensure carat pos has changed after HOME/END keys _.defer(resetBuffer); + + // IE9 doesn't fire the oninput event when backspace or delete is pressed. This causes the highlighting + // to stay on the screen whenever backspace is pressed after a highlighed word. This is simply a hack + // to force updateValues() to fire when backspace/delete is pressed in IE9. + if (navigator.userAgent.indexOf("MSIE 9") > -1) { + _.defer(updateValues); + } + return; } @@ -247,7 +270,7 @@ case KEY.RETURN: case KEY.TAB: if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) { - elmActiveAutoCompleteItem.click(); + elmActiveAutoCompleteItem.trigger('mousedown'); return false; } @@ -273,9 +296,9 @@ elmAutocompleteList.show(); // Filter items that has already been mentioned - var mentionedNames = _.pluck(mentionsCollection, 'name'); + var mentionValues = _.pluck(mentionsCollection, 'value'); results = _.reject(results, function (item) { - return _.include(mentionedNames, item.name); + return _.include(mentionValues, item.name); }); if (!results.length) { @@ -287,15 +310,19 @@ var elmDropDownList = $("<ul>").appendTo(elmAutocompleteList).hide(); _.each(results, function (item, index) { + var itemUid = _.uniqueId('mention_'); + + autocompleteItemCollection[itemUid] = _.extend({}, item, {value: item.name}); + var elmListItem = $(settings.templates.autocompleteListItem({ 'id' : utils.htmlEncode(item.id), 'display' : utils.htmlEncode(item.name), 'type' : utils.htmlEncode(item.type), 'content' : utils.highlightTerm(utils.htmlEncode((item.name)), query) - })).data('mention', item); + })).attr('data-uid', itemUid); - if (index === 0) { - selectAutoCompleteItem(elmListItem); + if (index === 0) { + selectAutoCompleteItem(elmListItem); } if (settings.showAvatars) { @@ -323,18 +350,27 @@ } } + function resetInput() { + elmInputBox.val(''); + mentionsCollection = []; + updateValues(); + } + // Public methods return { - init : function (options) { - settings = options; + init : function (domTarget) { + + domInput = domTarget; initTextarea(); initAutocomplete(); initMentionsOverlay(); + resetInput(); - if(options.prefillMention) { - addMention(options.prefillMention); + if( settings.prefillMention ) { + addMention( settings.prefillMention ); } + }, val : function (callback) { @@ -347,9 +383,7 @@ }, reset : function () { - elmInputBox.val(''); - mentionsCollection = []; - updateNames(); + resetInput(); }, getMentions : function (callback) { @@ -364,20 +398,20 @@ $.fn.mentionsInput = function (method, settings) { + var outerArguments = arguments; + if (typeof method === 'object' || !method) { - settings = $.extend(true, {}, defaultSettings, method); + settings = method; } - var outerArguments = arguments; - return this.each(function () { - var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(this)); + var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(settings)); if (_.isFunction(instance[method])) { return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1)); } else if (typeof method === 'object' || !method) { - return instance.init.call(this, settings); + return instance.init.call(this, this); } else { $.error('Method ' + method + ' does not exist'); -- GitLab