JQuery Autocomplete for each line in Textarea - javascript

I tried searching, but am not finding a solution.
I am currently using Jquery autocomplete, along with an external service that stores the lists of possible returned results. The autocomplete is being done on a textarea, and I need to try to autocomplete for each line of text. So the user types one line, gets auto complete. Goes to a new line, starts typing, autocomplete appears only for what's on that line.
The set up is pretty standard to what JQuery shows off. I.E.:
<textarea id="entities"></textarea>
JS:
$("#entities").autocomplete({
serviceUrl: [the service url],
paramName: 'prefix'
});
I know there are ways to determine line number and get the value of a specific line such as:
$("#entities").on('keyup', function() {
var textLines = $("#entities").val().substr(0, $("#entities").selectionStart).split("\n");
var currentLineNumber = textLines.length - 1;
console.log(lines[currentLineNumber]);
});
But I'm not sure how I could call the autocomplete function upon typing a new line.
Edit: Someone suggested using contentEditable, but this results in different wrappers for each line depending on the browser.
<div id="entities" class="entities-text" contenteditable="true"></div>
IE converts each line to:
<p>Line 1</p>
<p>Line 2</p>
FF shows:
Line 1<br>
Line 2<br>
Chrome gives:
<div>Line 1</div>
<div>Line 2</div>

I am not so sure that you can achieve that so easily using a textarea and for each row of the textarea.
My suggestion is to switch to a contenteditable div, maybe formatted through CSS like a textarea if you want that style, then every time that you detect a new line, wrap the new line with an HTML element (for example a p).
Then just set the autocomplete to work with all the p elements inside that div.
Here you have a really good example on how to do that in case that you type a # (it's an autocomplete for email addresses).
Changing a bit the code, you will have your job pretty done.
autocomplete with contenteditable div instead of textarea doesn't seem to work
There is also a link to the jsfiddle example inside the post.

I think this Function help full to you ... complete your requirement.
function CreateTextAreaAutoFill(idstr, AutoFillAry) {
$("#" + idstr)
// don't navigate away from the field on tab when selecting an item
.on("keydown", function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.autocomplete({
minLength: 0,
source: function (request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(
AutoFillAry, extractLast(request.term, idstr)));
},
focus: function () {
// prevent value inserted on focus
return false;
},
open: function (event, ui) {
// $(this).autocomplete('widget').find('li').css('font-size', '10px');
// $(this).autocomplete('widget').css('height', 100);
},
select: function (event, ui) {
//var terms = split(this.value);
//// remove the current input
//terms.pop();
//// add the selected item
//terms.push(ui.item.value);
//// add placeholder to get the comma-and-space at the end
//terms.push("");
//this.value = terms.join(" ");//terms.join(", ");
debugger;
var term = this.value;
var cursorPosition = $('#' + idstr).prop("selectionStart");
var SpaceInd = term.lastIndexOf(" ", (cursorPosition - 1));
// var SubStr = term.substring((SpaceInd + 1), cursorPosition);
var NewLineInd = term.lastIndexOf("\n", (cursorPosition - 1));
var SubStrInd = SpaceInd < NewLineInd ? NewLineInd : SpaceInd;
var FirstStr = this.value.substring(0, (SubStrInd + 1)) + ui.item.value;
this.value = FirstStr + this.value.substring(cursorPosition, this.value.length);
$('#' + idstr).prop('selectionEnd', FirstStr.length);
return false;
}
});
//function split(val) {
// // return val.split(/;\s*/);
// // return val.split(' ');
// var LineAry = val.split("\n");
// var FinalResult = [];
// $.each(LineAry, function (ind, Aval) {
// FinalResult = FinalResult.concat(Aval.split(' '));
// })
// return FinalResult;
//}
function extractLast(term, idstr) {
debugger;
var cursorPosition = $('#' + idstr).prop("selectionStart");
var SpaceInd = term.lastIndexOf(" ", (cursorPosition - 1));
var NewLineInd = term.lastIndexOf("\n", (cursorPosition - 1));
var SubStrInd = SpaceInd < NewLineInd ? NewLineInd : SpaceInd;
var SubStr = term.substring((SubStrInd + 1), cursorPosition);
return SubStr;//split(term).pop();
}
}

Related

JQuery undo append

I've got a table with a button inside a td, once I press the button it adds text to the td. I want to remove this text inside the td once i press the button again. note that this button is used multiple times in the table hence the class attribute.
Which method could I use to get this done?
This is my code:
$(document).on('click', '.releasebutton', function () { // button class="releasebutton"
var label = $(this).text();
if (label == "Add") { // it is "Add" by default
$(this).text("Cancel");
$('.ReleaseTD').append("<br>" + "test"); // td class="ReleaseTD"
}
// the code above this works
else {
$(this).text("Add");
$('.ReleaseTD').remove("<br>" + "test");
// this obviously is wrong but this is where i would like the correct code
};
});
You could create ID for text inside like this:
$(document).on('click', '.releasebutton', function () { // button class="releasebutton"
var label = $(this).text();
if (label == "Add") { // it is "Add" by default
$(this).text("Cancel");
$('.ReleaseTD').append("<span id='textID'><br>" + "test</span>");
}
else {
$(this).text("Add");
$('#textID').remove();
};
});
Please try the following:
$(document).on('click', '.releasebutton', function () { // button class="releasebutton"
var label = $(this).text();
if (label == "Add") { // it is "Add" by default
$(this).text("Cancel");
$('.ReleaseTD').append("<span id='txt_name'><br>" + "test</span>");
}
// the code above this works
else {
$(this).text("Add");
$('#txt_name').remove();
};
});
Two ways:
1) Append your text into a span with a unique ID, and then delete this ID. For example, delete the ID with the largest number. Dumbest way would be to just store the latest ID in a global variable.
var global_last_appended_id = 0;
$(document).on('click', '.releasebutton', function () { // button class="releasebutton"
global_last_appended_id ++;
$('.ReleaseTD').append("<span id='appended-text-" + global_last_appended_id + "'><br>" + "test</span>");
}
// the code above this works
else {
$(this).text("Add");
$('#appended-text-' + global_last_appended_id).remove();
global_last_appended_id--; //go one step back so next time we remove the previous paragraph
};
});
Update: after your edit I've added the ability to undo multiple times. Basically there is unlimited undo.
2) [lame and wrong] Save the previous .html() - the whole HTML code of your element - into a global variable; then restore the previous version of the text from the global variable when necessary.

Append to an array for the new span element that is added via input text in Angularjs

Am struggling hard to control an array object with list of span values using watcher in Angularjs.
It is partially working, when i input span elements, an array automatically gets created for each span and when I remove any span element -> respective row from the existing array gets deleted and all the other rows gets realigned correctly(without disturbing the value and name).
The problem is when I remove a span element and reenter it using my input text, it is not getting added to my array. So, after removing one span element, and enter any new element - these new values are not getting appended to my array.
DemoCode fiddle link
What am I missing in my code?
How can I get reinserted spans to be appended to the existing array object without disturbing the values of leftover rows (name and values of array)?
Please note that values will get changed any time as per a chart.
This is the code am using:
<script>
function rdCtrl($scope) {
$scope.dataset_v1 = {};
$scope.dataset_wc = {};
$scope.$watch('dataset_wc', function (newVal) {
//alert('columns changed :: ' + JSON.stringify($scope.dataset_wc, null, 2));
$('#status').html(JSON.stringify($scope.dataset_wc));
}, true);
$(function () {
$('#tags input').on('focusout', function () {
var txt = this.value.replace(/[^a-zA-Z0-9\+\-\.\#]/g, ''); // allowed characters
if (txt) {
//alert(txt);
$(this).before('<span class="tag">' + txt.toLowerCase() + '</span>');
var div = $("#tags");
var spans = div.find("span");
spans.each(function (i, elem) { // loop over each spans
$scope.dataset_v1["d" + i] = { // add the key for each object results in "d0, d1..n"
id: i, // gives the id as "0,1,2.....n"
name: $(elem).text(), // push the text of the span in the loop
value: 3
}
});
$("#assign").click();
}
this.value = "";
}).on('keyup', function (e) {
// if: comma,enter (delimit more keyCodes with | pipe)
if (/(188|13)/.test(e.which)) $(this).focusout();
if ($('#tags span').length == 7) {
document.getElementById('inptags').style.display = 'none';
}
});
$('#tags').on('click', '.tag', function () {
var tagrm = this.innerHTML;
sk1 = $scope.dataset_wc;
removeparent(sk1);
filter($scope.dataset_v1, tagrm, 0);
$(this).remove();
document.getElementById('inptags').style.display = 'block';
$("#assign").click();
});
});
$scope.assign = function () {
$scope.dataset_wc = $scope.dataset_v1;
};
function filter(arr, m, i) {
if (i < arr.length) {
if (arr[i].name === m) {
arr.splice(i, 1);
arr.forEach(function (val, index) {
val.id = index
});
return arr
} else {
return filter(arr, m, i + 1)
}
} else {
return m + " not found in array"
}
}
function removeparent(d1)
{
dataset = d1;
d_sk = [];
Object.keys(dataset).forEach(function (key) {
// Get the value from the object
var value = dataset[key].value;
d_sk.push(dataset[key]);
});
$scope.dataset_v1 = d_sk;
}
}
</script>
using angular i think you are going to want more on the html side of things particularly using ng-repeat and ng-click and ng-model first you would want to create your array which could simply be done in your code file or using ng-init. heres an example
(function(){
var app=angular.module('demoApp',[]);
app.controller('demoApp',[function(){
this.spans=[""];
this.currentSpan='';
this.addSpan=function(){
this.spans.push(this.currentSpan);
this.currentSpan='';
};
}]);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='demoApp'>
<div ng-controller='demoApp as demo'>
<span ng-repeat='span in demo.spans track by $index' ng-click='demo.spans.splice($index,1)'>{{span}}</span><span>{{demo.currentSpan}}</span>
<textarea ng-model='demo.currentSpan' placeholder='new text'></textarea>
<button ng-click='demo.addSpan()' >add</button>
</div>
</div>

Result not showing in Input text

I have the following JQuery code I am working on. When I test it, the expected values are shown in the span but not in the input text box.
JQ
$(function() {
$("#textbox").each(function() {
var input = '#' + this.id;
counter(input);
$(this).keyup(function() {
counter(input);
});
});
});
function counter(field) {
var number = 0;
var text = $(field).val();
var word = $(field).val().split(/[ \n\r]/);
words = word.filter(function(word) {
return word.length > 0 && word.match(/^[A-Za-z0-9]/);
}).length;
$('.wordCount').text(words);
$('#sentencecount').text(words);
}
Please see Fiddle. Please let me know where I have gone wrong. Still new to JS.
Thanks
Change this:
$('#sentencecount').text(words);
to this:
$('#sentencecount').val(words);
The .text() method cannot be used on form inputs or scripts. To set or get the text value of input or textarea elements, use the .val() method. To get the value of a script element, use the .html() method. -> http://api.jquery.com/text/
Trying using val() instead This should fix it up.
http://jsfiddle.net/josephs8/6B9Ga/8/
You can not set text to an input you must use value
try this.
$('#sentencecount').text(words);
//has to be
$('#sentencecount').val(words);
and i have also updated your Jsfiddle
$(function() {
$("#textbox").each(function() {
var input = '#' + this.id;
counter(input);
$(this).keyup(function() {
counter(input);
});
});
});
function counter(field) {
var number = 0;
var text = $(field).val();
var word = $(field).val().split(/[ \n\r]/);
words = word.filter(function(word) {
return word.length > 0 && word.match(/^[A-Za-z0-9]/);
}).length;
$('.wordCount').text(words);
$('#sentencecount').val(words);
}

Using jQuery selector and setSelectionRange is not a function

I have assembled a basic jfiddle below. For some reason my selector works to retrieve the textarea box to set the value, but the selector doesnt work to use the setSelectionRange function. On the console you'll find an error for .setSelectionRange is not a function.
http://jsfiddle.net/dMdHQ/6/
code(please refer to jfiddle):
selector.setSelectionRange(carat,carat);
setSelectionRange(carat,carat) is not a method on jquery object. You want to use it on DOM element. So try:
selector[0].setSelectionRange(carat,carat); //use `[0]` or .get(0) on the jquery object
See Reference
Fiddle
For me this is a good solution
selector[0].setSelectionRange(start ,end);
But I would like to add one more thing. I noticed that setSelectionRange is something that becomes available asynchronously after the element gets focus.
var element = selector[0];
element.addEventListener('focus', function() {
element.setSelectionRange(start, end);
});
element.focus();
Also you can use alternatively:
element.selectionStart = start;
element.selectionEnd = end;
HTML:
<input type="search" value="Potato Pancakes" id="search">
JQUERY:
jQuery.fn.putCursorAtEnd = function() {
return this.each(function() {
$(this).focus()
// If this function exists...
if (this.setSelectionRange) {
// ... then use it (Doesn't work in IE)
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
var len = $(this).val().length * 2;
this.setSelectionRange(len, len);
} else {
// ... otherwise replace the contents with itself
// (Doesn't work in Google Chrome)
$(this).val($(this).val());
}
// Scroll to the bottom, in case we're in a tall textarea
// (Necessary for Firefox and Google Chrome)
this.scrollTop = 999999;
});
};
$("#search").putCursorAtEnd();
Check:
http://css-tricks.com/snippets/jquery/move-cursor-to-end-of-textarea-or-input/
You could try this which works for me. I use it to build an address from the separate address fields and then do the copy for pasting.
The HTML
<div id="d_clip_container" style="position:relative">
(copy to clipboard)
</div>;
<textarea id="clip" rows="0" cols="0" style="border:none;height:0;width:0;"></textarea>
The jQuery
$(document).ready(function() {
$('#d_clip_button').click(function() {
//get all the values of needed elements
var fName = $("#firstName").val();
var lName = $("#lastName").val();
var address = $("#Address").val();
var city = $("#City").val();
var state = $("#State").val();
var zip = $("#Zip").val();
//concatenate and set "clip" field with needed content
$('#clip').val(fName + " " + lName + "\n" + address + "\n" + city + ", " + state + " " + zip);
//Do it
if(copyToClipboard('#clip')) {
alert('text copied');
} else {
alert('copy failed');
}
});
});
function copyToClipboard(elem) {
// set focus to hidden element and select the content
$(elem).focus();
// select all the text therein
$(elem).select();
var succeed;
try {
succeed = document.execCommand("copy");
} catch(e) {
succeed = false;
}
// clear temporary content
$(target).val('');
return succeed;
}

Issues in textareas editing

I'm making a kind of text editor in a textarea where I process user input, including tabs. Every time the user inputs something, I run a paginate() function which paginates the text correctly on the page, this function takes about 20 milliseconds. Now, because I don't want a second input to be processed while the textarea is being paginated I've added a condition but that way I'm losing ctrl-V</kbdI functionality. So, following a suggestion by #Gabriel Gartz at this post: textarea on input issue.
I call again the function by saving first the context and the event. The function does get called again, but the paste still doesn't take place!
html:
<textarea id="taEditor"></textarea>
Javascript:
$("#taEditor").on('click keydown cut paste', processUserInput);
var IsProcessingEvent = false;
function processUserInput(e) {
if(!IsProcessingEvent) {
IsProcessingEvent = true;
//do various stuff before the textarea changes like: get value, get cursor pos etc
var taValueBefore = document.getElementById("taEditor").value;
if (e.keyCode === 9) {
e.preventDefault();
e.stopPropagation();
document.getElementById("taEditor").value += "\t";
}
getcursorpos();
//do various stuff after the textarea changes like: get value, get cursor pos etc
setTimeout(function() {
var taValueAfter = document.getElementById("taEditor").value;
getcursorpos();
if (taValueAfter !== taValueBefore) {
paginate(); //this function paginates the text in the textarea and sets the cursor
//paginate() takes about 20 milliseconds
}
if (doAgain.repeat) {
var lastEvent = doAgain;
doAgain.repeat = false;
document.getElementById("debug").innerHTML += "rerun: " + lastEvent.ctx.id + ":" + lastEvent.e.type + "<br>";
setTimeout(processUserInput.bind(lastEvent.ctx), 0, lastEvent.e);
}
document.getElementById("debug").innerHTML += e.type + "<br>";
IsProcessingEvent = false;
}, 0);
} else {
//save context and event
document.getElementById("debug").innerHTML += "isprocessing: " + e.type + "<br>";
doAgain = {
ctx: this,
e: e,
repeat: true
};
//i need to preventdefault here, because processUerInput also processes the TAB key and if i don't use preventdefault then the cursor will move focus to other elements during the pagination
e.preventDefault();
e.stopPropagation();
return false;
}
}
var doAgain = {
ctx: "",
e: "",
repeat: false
};
function getcursorpos() {
//for simplicity reasons it's empty
}
function paginate() {
var i = 0;
var k = 0;
//simulate 20-30 milliseconds of delay for pagination
for (i=0;i<100000000;i++) {
k++;
}
//for simplicity reasons it's empty
}
jsfiddle:
http://jsfiddle.net/63pkE/1/
to reproduce the problem: try to ctrl-v in the textarea.
I don't understand what I'm doing wrong.
EDIT
Here is a new jsfiddle where I replaced the
setTimeout(processUserInput.bind(lastEvent.ctx), 0, lastEvent.e);
line with
setTimeout(function() {
processUserInput.call(lastEvent.ctx, lastEvent.e);
}, 0);
since .bind is not crossbrowser and still it DOESN'T work.
http://jsfiddle.net/63pkE/2/
Try this and see if if works, I didn't notice any difference from your original code behavior and copy paste work now.
function processUserInput(e) {
if(!IsProcessingEvent) {
if(!e.ctrlKey){
IsProcessingEvent = true;
}
//Rest of the code
e.ctrlKey returns true if the Ctrl key is pressed.

Categories