autocomplete wip. separated keyup and keydown. wip.

This commit is contained in:
danielvincent 2011-02-07 18:46:48 -08:00 committed by Raphael Sofaer
parent 6eafd73d2c
commit 2b25823253
2 changed files with 177 additions and 71 deletions

View file

@ -2,7 +2,18 @@
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
var KEY = {
UP: 38,
DOWN: 40,
DEL: 46,
TAB: 9,
RETURN: 13,
ESC: 27,
COMMA: 188,
PAGEUP: 33,
PAGEDOWN: 34,
BACKSPACE: 8
};
//TODO: make this a widget
var Publisher = {
close: function(){
@ -62,59 +73,143 @@ var Publisher = {
var visibleLoc = Publisher.autocompletion.addMentionToInput(visibleInput, visibleCursorIndex, formatted);
$.Autocompleter.Selection(visibleInput[0], visibleLoc[1], visibleLoc[1]);
var hiddenCursorIndex = visibleCursorIndex + Publisher.autocompletion.mentionList.offsetFrom(visibleCursorIndex);
var hiddenLoc = Publisher.autocompletion.addMentionToInput(Publisher.hiddenInput(), hiddenCursorIndex, Publisher.autocompletion.hiddenMentionFromPerson(data));
var mentionString = Publisher.autocompletion.hiddenMentionFromPerson(data);
var mention = { visibleStart: visibleLoc[0],
visibleEnd : visibleLoc[1],
hiddenStart : hiddenLoc[0],
hiddenEnd : hiddenLoc[1]
mentionString : mentionString
};
Publisher.autocompletion.mentionList.push(mention);
Publisher.oldInputContent = visibleInput.val();
Publisher.hiddenInput().val(Publisher.autocompletion.mentionList.generateHiddenInput(visibleInput.val()));
},
mentionList : {
mentions : [],
sortedMentions : function(){
return this.mentions.sort(function(m1, m2){
if(m1.visibleStart > m2.visibleStart){
return -1;
} else if(m1.visibleStart < m2.visibleStart){
return 1;
} else {
return 0;
}
});
},
push : function(mention){
mention.offset = mention.hiddenEnd - mention.visibleEnd;
this.mentions.push(mention);
},
keypressAt : function(visibleCursorIndex){
var mentionIndex = this.mentionAt(visibleCursorIndex);
var mention = this.mentions[mentionIndex];
if(!mention){return;}
var visibleMentionString = Publisher.input().val().slice(mention.visibleStart, mention.visibleEnd);
var hiddenContent = Publisher.hiddenInput().val();
hiddenContent = hiddenContent.slice(0,mention.hiddenStart) +
visibleMentionString +
hiddenContent.slice(mention.hiddenEnd);
Publisher.hiddenInput().val(hiddenContent);
generateHiddenInput : function(visibleString){
var resultString = visibleString;
for(i in this.sortedMentions()){
var mention = this.mentions[i];
var start = resultString.slice(0, mention.visibleStart);
var insertion = mention.mentionString;
var end = resultString.slice(mention.visibleEnd);
resultString = start + insertion + end;
}
return resultString;
},
insertionAt : function(insertionEndIndex, insertionStartIndex, keyCode){
this.incrementMentionLocations(insertionStartIndex, insertionEndIndex - insertionStartIndex);
var mentionIndex = this.mentionAt(insertionEndIndex);
var mention = this.mentions[mentionIndex];
if(mention){
this.mentions.splice(mentionIndex, 1);
}
},
deletionAt : function(visibleCursorIndex, keyCode){
var effectiveCursorIndex;
if(keyCode == KEY.DEL){
effectiveCursorIndex = visibleCursorIndex;
}else{
effectiveCursorIndex = visibleCursorIndex - 1;
}
this.decrementMentionLocations(effectiveCursorIndex, keyCode);
var mentionIndex = this.mentionAt(effectiveCursorIndex);
var mention = this.mentions[mentionIndex];
if(mention){
this.mentions.splice(mentionIndex, 1);
}
},
incrementMentionLocations : function(effectiveCursorIndex, offset){
var changedMentions = this.mentionsAfter(effectiveCursorIndex);
for(i in changedMentions){
var mention = changedMentions[i];
mention.visibleStart += offset;
mention.visibleEnd += offset;
}
},
decrementMentionLocations : function(effectiveCursorIndex){
var visibleOffset = -1;
var changedMentions = this.mentionsAfter(effectiveCursorIndex);
for(i in changedMentions){
var mention = changedMentions[i];
mention.visibleStart += visibleOffset;
mention.visibleEnd += visibleOffset;
}
},
mentionAt : function(visibleCursorIndex){
for(i in this.mentions){
var mention = this.mentions[i];
if(visibleCursorIndex >= mention.visibleStart && visibleCursorIndex < mention.visibleEnd){
if(visibleCursorIndex > mention.visibleStart && visibleCursorIndex < mention.visibleEnd){
return i;
}
}
return false;
},
mentionsAfter : function(visibleCursorIndex){
var resultMentions = [];
for(i in this.mentions){
var mention = this.mentions[i];
if(visibleCursorIndex <= mention.visibleStart){
resultMentions.push(mention);
}
}
return resultMentions;
},
},
repopulateHiddenInput: function(){
var newHiddenVal = Publisher.autocompletion.mentionList.generateHiddenInput(Publisher.input().val());
if(newHiddenVal != Publisher.hiddenInput().val()){
Publisher.hiddenInput().val(newHiddenVal);
}
},
offsetFrom: function(visibleCursorIndex){
var mention = {visibleStart : -1, fake: true};
var currentMention;
for(i in this.mentions){
currentMention = this.mentions[i];
if(visibleCursorIndex >= currentMention.visibleStart &&
currentMention.visibleStart > mention.visibleStart){
mention = currentMention;
}
}
if(mention && !mention.fake){
return mention.offset;
keyUpHandler : function(event){
var input = Publisher.input();
var cursorIndexAtKeydown = Publisher.cursorIndexAtKeydown;
Publisher.cursorIndexAtKeydown = -1;
if(input.val() == Publisher.oldInputContent || event.keyCode == KEY.RETURN || event.keyCode == KEY.DEL || event.keyCode == KEY.BACKSPACE){
Publisher.autocompletion.repopulateHiddenInput();
return;
}else {
return 0;
Publisher.oldInputContent = input.val();
var visibleCursorIndex = input[0].selectionStart;
Publisher.autocompletion.mentionList.insertionAt(visibleCursorIndex, cursorIndexAtKeydown, event.keyCode);
Publisher.autocompletion.repopulateHiddenInput();
}
},
keyDownHandler : function(event){
var input = Publisher.input();
var visibleCursorIndex = input[0].selectionStart;
if(Publisher.cursorIndexAtKeydown == -1){
Publisher.cursorIndexAtKeydown = visibleCursorIndex;
}
if((event.keyCode == KEY.DEL && visibleCursorIndex < input.val().length) || (event.keyCode == KEY.BACKSPACE && visibleCursorIndex > 0)){
Publisher.autocompletion.mentionList.deletionAt(visibleCursorIndex, event.keyCode);
}
Publisher.autocompletion.repopulateHiddenInput();
},
addMentionToInput: function(input, cursorIndex, formatted){
@ -126,13 +221,15 @@ var Publisher = {
var stringEnd = inputContent.slice(stringLoc[1]);
input.val(stringStart + formatted + stringEnd);
var offset = formatted.length - stringLoc[1] - stringLoc[0]
Publisher.autocompletion.mentionList.incrementMentionLocations(stringStart.length, offset);
return [stringStart.length, stringStart.length + formatted.length]
},
findStringToReplace: function(value, cursorIndex){
var atLocation = value.lastIndexOf('@', cursorIndex);
if(atLocation == -1){return [0,0];}
var nextAt = cursorIndex//value.indexOf(' @', cursorIndex+1);
var nextAt = cursorIndex
if(nextAt == -1){nextAt = value.length;}
return [atLocation, nextAt];
@ -164,6 +261,7 @@ var Publisher = {
Publisher.input().autocomplete(Publisher.autocompletion.contactsJSON(),
Publisher.autocompletion.options());
Publisher.input().result(Publisher.autocompletion.selectItemCallback);
Publisher.oldInputContent = Publisher.input().val();
}
},
initialize: function() {
@ -183,6 +281,8 @@ var Publisher = {
Publisher.autocompletion.initialize();
Publisher.hiddenInput().val(Publisher.input().val());
Publisher.input().keydown(Publisher.autocompletion.keyDownHandler);
Publisher.input().keyup(Publisher.autocompletion.keyUpHandler);
Publisher.form().find("textarea").bind("focus", function(evt) {
Publisher.open();
$(this).css('min-height', '42px');

View file

@ -62,19 +62,17 @@ describe("Publisher", function() {
});
});
describe("autocompletion", function(){
describe("onKeypress", function(){
});,
describe("searchTermFromValue", function(){
var func;
beforeEach(function(){func = Publisher.autocompletion.searchTermFromValue;});
it("returns nothing if the cursor is before the @", function(){
expect(func('not @dan grip', 2)).toBe('');
});
it("returns everything after an @ if the cursor is a word after that @", function(){
it("returns everything up to the cursor if the cursor is a word after that @", function(){
expect(func('not @dan grip', 13)).toBe('dan grip');
});
it("returns everything after an @ if the cursor is after that @", function(){
expect(func('not @dan grip', 7)).toBe('dan grip');
it("returns up to the cursor if the cursor is after that @", function(){
expect(func('not @dan grip', 7)).toBe('da');
});
it("returns everything after an @ at the start of the line", function(){
@ -89,11 +87,11 @@ describe("Publisher", function() {
it("returns nothing if there are letters preceding the @", function(){
expect(func('ioj@asdo', 8)).toBe('');
});
it("returns everything between @s if there are 2 @s and the cursor is between them", function(){
expect(func('@asdpo aoisdj @asodk', 8)).toBe('asdpo aoisdj');
it("returns everything up to the cursor if there are 2 @s and the cursor is between them", function(){
expect(func('@asdpo aoisdj @asodk', 8)).toBe('asdpo');
});
it("returns everything after the 2nd @ if there are 2 @s and the cursor after them", function(){
expect(func('@asod asdo @asd asok', 15)).toBe('asd asok');
it("returns everything from the 2nd @ up to the cursor if there are 2 @s and the cursor after them", function(){
expect(func('@asod asdo @asd asok', 15)).toBe('asd');
});
});
@ -105,18 +103,15 @@ describe("Publisher", function() {
var visibleInput, visibleVal,
hiddenInput, hiddenVal,
list,
func,
mention;
beforeEach(function(){
spec.loadFixture('aspects_index');
list = Publisher.autocompletion.mentionList;
func = list.keypressAt;
visibleInput = Publisher.input();
hiddenInput = Publisher.hiddenInput();
mention = { visibleStart : 0,
visibleEnd : 5,
hiddenStart : 0,
hiddenEnd : 21
mentionString : "@{Danny; dan@pod.org}"
};
list.mentions = [];
list.push(mention);
@ -125,6 +120,11 @@ describe("Publisher", function() {
hiddenVal = "@{Danny; dan@pod.org} loves testing javascript";
hiddenInput.val(hiddenVal);
});
describe("generateHiddenInput", function(){
it("replaces mentions in a string", function(){
expect(list.generateHiddenInput(visibleVal)).toBe(hiddenVal);
});
});
describe("push", function(){
it("adds mention to mentions array", function(){
expect(list.mentions.length).toBe(1);
@ -142,34 +142,39 @@ describe("Publisher", function() {
describe("keypressAt", function(){
it("does nothing if there is no visible mention at that index", function(){
list.keypressAt(8);
expect(visibleInput.val()).toBe(visibleVal)
expect(hiddenInput.val()).toBe(hiddenVal)
expect(visibleInput.val()).toBe(visibleVal);
expect(hiddenInput.val()).toBe(hiddenVal);
});
it("deletes the mention from the hidden field if there is a mention", function(){
list.keypressAt(3);
expect(visibleInput.val()).toBe(visibleVal)
expect(hiddenInput.val()).toBe(visibleVal)
expect(visibleInput.val()).toBe(visibleVal);
expect(list.generateHiddenInput(visibleInput.val())).toBe(visibleVal);
});
it("deletes the mention from the list", function(){
list.keypressAt(3);
expect(list.mentionAt(3)).toBeFalsy();
});
it("updates the offsets of the remaining mentions in the list");
it("calls updateMentionLocations", function(){
mentionTwo = { visibleStart : 8,
visibleEnd : 15,
mentionString : "@{SomeoneElse; other@pod.org}"
};
list.push(mentionTwo);
spyOn(list, 'updateMentionLocations');
list.keypressAt(3, 60);
expect(list.updateMentionLocations).toHaveBeenCalled();
});
describe("offsetFrom", function(){
var func;
beforeEach(function(){
func = list.offsetFrom;
});
it("returns the offset of the mention at that location", function(){
expect(list.offsetFrom(3)).toBe(mention.offset);
});
it("returns the offset of the previous mention if there is no mention there", function(){
expect(list.offsetFrom(10)).toBe(mention.offset);
});
it("returns 0 if there are no mentions", function(){
list.mentions = [];
expect(list.offsetFrom(8)).toBe(0);
describe("updateMentionLocations", function(){
it("updates the offsets of the remaining mentions in the list", function(){
mentionTwo = { visibleStart : 8,
visibleEnd : 15,
mentionString : "@{SomeoneElse; other@pod.org}"
};
list.push(mentionTwo);
list.updateMentionLocations(7, 60);
expect(mentionTwo.visibleStart).toBe(9);
expect(mentionTwo.visibleEnd).toBe(16);
});
});
});
@ -184,37 +189,38 @@ describe("Publisher", function() {
spec.loadFixture('aspects_index');
func = Publisher.autocompletion.addMentionToInput;
input = Publisher.input();
Publisher.autocompletion.mentionList = [];
replaceWith = "Replace with this.";
});
it("replaces everything after an @ if the cursor is a word after that @", function(){
it("replaces everything up to the cursor if the cursor is a word after that @", function(){
input.val('not @dan grip');
var cursorIndex = 13;
func(input, cursorIndex, replaceWith);
expect(input.val()).toBe('not ' + replaceWith);
});
it("replaces everything after an @ if the cursor is after that @", function(){
it("replaces everything between @ and the cursor if the cursor is after that @", function(){
input.val('not @dan grip');
var cursorIndex = 7;
func(input, cursorIndex, replaceWith);
expect(input.val()).toBe('not ' + replaceWith);
expect(input.val()).toBe('not ' + replaceWith + 'n grip');
});
it("replaces everything after an @ at the start of the line", function(){
it("replaces everything up to the cursor from @ at the start of the line", function(){
input.val('@dan grip');
var cursorIndex = 9;
func(input, cursorIndex, replaceWith);
expect(input.val()).toBe(replaceWith);
});
it("replaces everything between @s if there are 2 @s and the cursor is between them", function(){
it("replaces everything between the first @ and the cursor if there are 2 @s and the cursor is between them", function(){
input.val('@asdpo aoisdj @asodk');
var cursorIndex = 8;
func(input, cursorIndex, replaceWith);
expect(input.val()).toBe(replaceWith + ' @asodk');
expect(input.val()).toBe(replaceWith + 'aoisdj @asodk');
});
it("replaces everything after the 2nd @ if there are 2 @s and the cursor after them", function(){
input.val('@asod asdo @asd asok');
var cursorIndex = 15;
func(input, cursorIndex, replaceWith);
expect(input.val()).toBe('@asod asdo ' + replaceWith);
expect(input.val()).toBe('@asod asdo ' + replaceWith + ' asok');
});
});
});