Get start index and end index for highlight using rangy-javascript - javascript

I have array of misspelled words(window.essaySpellingErrors).I have an essay text which is saved in the database from tinyMCE editor.(data.data.essay).
I want to render this essay text in div for teacher so that teacher can evaluate the essay.
But the issue is that i am not able to highlight properly the misspelled words in the essay while rendering. Also i want the start index and end index of the first occurrence of every misspelled word.
So that next time whenever the essay loads the highlights can be made from the start index and end index i saved previously.
I am using rangy for highlight making with angular along with some javascript.
Here i am attaching the code which loops every element of the misspelled words.
Here $scope.comments indicates the comments which teacher makes on essay and loads everytime whenever teacher opens to evaluate essay.When there is no comment added in the system by default misspelled comments are added in the comment box.
$scope.getEssayDetails = function(){
// Set the essay content.
// Write ajax call to get the essay content
$http({
method : 'POST',
url : Helpers.constants['CONTROLLER_PATH'] + 'essayAllotment/getEssay',
data : $.param({'eMode':eMode ,'essayScoreID':eScoreID}), //forms user object
headers : {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
}).success(function(data) {
if(Helpers.ajax_response( '' , data , [] )){
$scope.getEvaluationEssay($.parseJSON(data.result_data));
}
}).error(onAjaxError);
};
$scope.getEvaluationEssay=function(data, extraParams)
{
$("#essay_detail").html("");
$scope.comments = [];
$scope.togglecomment = false;
$scope.comments = [];
$scope.essayDetails = {};
window.essaySpellingErrors=[];
if(data.data.essaySpellingErrors!=undefined)
window.essaySpellingErrors=data.data.essaySpellingErrors;
data.data.essay=data.data.essay.replace(/&nbsp+/ig,' ');
//essayPlainText=data.data.essay.replace(/<span class=\'sp_err\'>(.*?)<\/span>/g,"$1");
//essayPlainText=essayPlainText.replace(/<span class=\'for_err\'>(.*?)<\/span>/g,"$1");
//essayPlainText=essayPlainText.replace(/\n/ig,' ');
//essayPlainText=essayPlainText.replace(/\s+/ig,' ');
essayPlainText=data.data.essay;
$("#essay_detail").html(essayPlainText);
if (data.data.report.selectFeedback != "") {
var sfbck = data.data.report.selectFeedback.split('{|~|}');
var commentStr;
for (var i = 0; i < sfbck.length; i++) {
var sfbi = sfbck[i].split('|*|');
if ((sfbi).length != 3) continue;
sfbi.push(i);
$scope.createCommentBox(sfbi);
var indexArr = sfbi[0].match(/\[(.*?)\]/);
commentStr=indexArr[1];
indices=sfbi[1].split('~');
startIndex=indices[0];
endIndex=indices[1];
//startIndex=new RegExp('\\b' + commentStr + '\\b');
//endIndex=startIndex+commentStr.length;
if (!el) el = document.getElementById('essay_detail');
if (!sel) sel = rangy.getSelection();
if (!range) range = rangy.createRange();
range.selectCharacters(el, startIndex, endIndex);
sel.setSingleRange(range);
$scope.setHilights(0,'',0);
}
data.data.essay=$("#essay_detail").html();
}else if(Object.keys(window.essaySpellingErrors).length>0){
for (var i = 0; i < window.essaySpellingErrors.length; i++) {
var spellWord=window.essaySpellingErrors[i];
var regex = new RegExp('\\b' + spellWord + '\\b','gi');
startIndex = $("#essay_detail").text().search(regex);
//endIndex=essayPlainText.indexOf(spellWord);
var results = new Array();//this is the results you want
while (regex.exec($("#essay_detail").text())){
results.push(regex.lastIndex);
}
endIndex=startIndex+spellWord.length;
var title='Comment for [' + spellWord + ']';
var label=startIndex + '~' + endIndex;
var commentText="Misspelt : "+spellWord;
var commentIndex=$scope.comments.length;
var commentObj=[title,label,commentText,commentIndex];
$scope.createCommentBox(commentObj);
if (!el) el = document.getElementById('essay_detail');
if (!sel) sel = rangy.getSelection();
if (!range) range = rangy.createRange();
range.selectCharacters(el, startIndex, endIndex);
sel.setSingleRange(range);
$scope.setHilights(0, '',1);
// }
//$scope.togglecomment = true;
}
data.data.essay=$("#essay_detail").html();
}
$scope.createCommentBox = function(commentObj){
var commentObject = { 'comment' : {'commentFor' : commentObj[0] , 'commentRange' : commentObj[1] , 'commentText' : commentObj[2], 'commentIndex' : commentObj[3]}};
$scope.comments.push(commentObject);
};
$scope.setHilights=function(n, evalNum , isSpellErrorComment) {
//console.log('set HiliteN');
var commentText="";
rSel = rangy.getSelection(); //console.log(rSel);
var txt = rSel.toHtml().replace(/style="background-color:#ff0000"/g, 'class="commentHilight" name="' + parseInt(startIndex) + '~' + parseInt(endIndex) + '"');
var textK = rSel.text();
//console.log(rSel.toHtml()+'------'+txt+'--------'+textK);
rRange = rSel.getRangeAt(0);
rRange.deleteContents();
var str='<span class="commentHilight startIndex-'+ parseInt(startIndex) +' endIndex-'+parseInt(endIndex)+'" label="' + parseInt(startIndex) + '~' + parseInt(endIndex) + '">' + textK + '</span>';
var node = rRange.createContextualFragment(str);
rRange.insertNode(node);
rSel.removeAllRanges();
return;
}

Related

Column filtering and stacking on table not working on backspace in input

I have created my table with jQuery and have it using filtering on the column. However, ran into something interesting.
Below is my fiddle
https://jsfiddle.net/4sy5dweg/1/
So my issue is as follows -
Once I find what I am looking for by filtering. If I decided to start backspacing I want it to continue searching that column and obviously start going backwards on searching.
I hope that makes sense.
$.extend($.expr[":"], {
"containsIN": function (elem, i, match, array) {
return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
$("[id*=TXTSEARCH]").keyup(function () {
tablename = $(this).closest('table').attr('id');
mytd = $(this).closest('th');
var indexColumn = mytd.index();
var data = this.value.toLowerCase().split(" ");
var jo = $("[id*=" + tablename + "]").find("tr:not(:first)");
jo.filter(function (i, v) {
var $t = $(this).children(":eq(" + indexColumn + ")");
for (var d = 0; d < data.length; ++d) {
if ($t.is(":not(:containsIN('" + data[d] + "'))")) {
console.log(data[d]);
return true;
}
}
return false;
}).hide();
})
I know the issue has to do with hiding that row on initial search. However, somehow for isntance if someone has a typo and goes to backspace it wont show anything because that row is still hidden. Is there a way to keep the other filters in place and basically remove the filter from the row being corrected?
I hope I am making sense.
Update I have attempted the following -
$("[id*=TXTSEARCH]").keyup(function () {
var tablename = $(this).closest('table').attr('id');
console.log(tablename);
var FindMyRow;
$("#" + tablename).find('th:visible').find($("[id*=TXTSEARCH]")).each(function () {
FindMyRow = $(this).closest('th:visible');
var indexColumn = FindMyRow.index();
console.log($(this).attr('id') + " " + indexColumn);
var data = this.value.toLowerCase().split(" ");
var jo = $("[id*=" + tablename + "]").find("tr:not(:first)");
jo.show().filter(function (i, v) {
var $t = $(this).children(":eq(" + indexColumn + ")");
for (var d = 0; d < data.length; ++d) {
if ($t.is(":not(:containsIN('" + data[d] + "'))")) {
console.log(data[d]);
return true;
}
}
return false;
}).hide();
});
to no avail.
Decided to change my way of doing it.
I added a button to each column header that the user can click on so that if they make a type they just check it before clicking it.
$("[id*=BTNSEARCH]").click(function (e) {
e.preventDefault();
var tablename = $(this).closest('table').attr('id');
mytd = $(this).closest('th');
var TextBoxToSearch = ($(this).prev('input').val());
var indexColumn = mytd.index();
FindMyRow = $(this).closest('th:visible');
var indexColumn = FindMyRow.index();
var data = TextBoxToSearch.toLowerCase().split(" ");
var jo = $("#" + tablename).find("tr:not(:first)");
jo.filter(function (i, v) {
var $t = $(this).children(":eq(" + indexColumn + ")");
for (var d = 0; d < data.length; ++d) {
if ($t.is(":not(:containsIN('" + data[d] + "'))")) {
return true;
}
}
return false;
}).hide();
})

Clearing localStorage of a page without affecting other localStorage

In my code i have set multiple items in local storage, when i use localStorage.clear() it resets the variable of my other webapps as well, and i do not want to remove each item one by one, i want to reset the specific container of my localStorage of that page, whole storage of my browser.
here is my code
if (localStorage.container) {
var i = parseInt(localStorage.container);
} else {
localStorage.setItem('container', "0");
var i = parseInt(localStorage.container);
}
var caseNum = $("#caseNum").val();
localStorage.setItem('caseNum' + i, caseNum);
var empName = checkName()
localStorage.setItem('empName' + i, empName);
var emp = $("#emp").val();
localStorage.setItem('emp' + i, emp);
var trigTime = $("#trigTime").val();
localStorage.setItem('trigTime' + i, trigTime);
var country = $("#country").val();
localStorage.setItem('country' + i, country);
var date = $("#date").val();
localStorage.setItem('date' + i, date);
var caseDate = $("#date").val();
localStorage.setItem('caseDate' + i, caseDate);
var doneTime = currTime();
localStorage.setItem('doneTime' + i, doneTime);
var dispo = $("#dispo").val();
localStorage.setItem('dispo' + i, dispo);
var subDispo = $("#subDispo").val();
localStorage.setItem('subDispo' + i, subDispo);
var caseCount = $("#caseCount").val();
localStorage.setItem('caseCount' + i, caseCount);
var A1 = '';
localStorage.setItem('A1' + i, A1);
var A2 = '';
localStorage.setItem('A2' + i, A2);
var A3 = '';
localStorage.setItem('A3' + i, A3);
var project = $("input[type='radio'][name='project']:checked").val();
localStorage.setItem('project' + i, project);
var prefix = '';
if (country == "Kenya") {
prefix = "KY";
}
localStorage.setItem('prefix' + i, prefix);
var called1 = '';
if ($("#called0").prop("checked") == true) {
called1 = "Yes";
} else if ($("#called0").prop("checked") == false) {
called1 = "No";
}
localStorage.setItem('called1' + i, called1);
i++;
localStorage.setItem('container', i);
that code is for my dynamic table, do it is creating an array of entries in tablerow.
i tried to use localStorage.removeItem(container); but does not work. Any suggestions please ?

Getting a error when click save and I am unable to create new number

When I click on save on my bootstrap modal button it tries to find next available number.
How ever if returns null throws error.
TypeError: matches is null
Question When click on save in bootstrap modal if no numbers found in textarea then will create a number. Currently if no findAvailableNumber function returns null unable to create a number
Codepen Example
$('#myLink').on('shown.bs.modal', function() {
var text = getSelectedText();
$('#title').val(text.trim());
$('#url').val('http://');
});
function getSelectedText() {
var textarea = document.getElementById("message");
var len = textarea.value.length;
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var sel = textarea.value.substring(start, end);
return sel;
}
function findAvailableNumber(textarea){
//Find lines with links
var matches = textarea.value.match(/(^|\n)\s*\[\d+\]:/g);
//Find corresponding numbers
var usedNumbers = matches.map(function(match){
return parseInt(match.match(/\d+/)[0]); }
);
//Find first unused number
var number = 1;
while(true){
if(usedNumbers.indexOf(number) === -1){
//Found unused number
return number;
}
number++;
}
return number;
}
$('#save-link').on('click', function(e) {
var textarea = document.getElementById("message");
var len = textarea.value.length;
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var sel = textarea.value.substring(start, end);
var counter = findAvailableNumber(textarea);
var replace = '[' + $('input#title').val() + ']' + '[' + counter + ']';
var id = '\n [' + counter + ']: ' + $('input#url').val();
if ($('#title').val().length > 0) {
textarea.value = textarea.value.substring(0,start) + replace +
textarea.value.substring(end,len) + id;
} else {
return false;
}
});
How links look in textarea when created.
[exmple-1][1] and [example-2][2]
[1]: http://www.example.com
[2]: http://www.example.com
You need to check to see if the <textarea> actually has a value within findAvailableNumber(). If not, return 1 to kick it off.
function findAvailableNumber(textarea){
var number = 1;
if(textarea.value){
//Find lines with links
var matches = textarea.value.match(/(^|\n)\s*\[\d+\]:/g);
//Find corresponding numbers
var usedNumbers = matches.map(function(match){
return parseInt(match.match(/\d+/)[0]); }
);
//Find first unused number
var number = 1;
while(true){
if(usedNumbers.indexOf(number) === -1){
//Found unused number
return number;
}
number++;
}
}
return number;
}
Here's an updated pen.

Locate Existing Numbers For Hyperlink In Textarea Set Next Avaibale One On Save / Click Modal

I am using CodeIgniter & jQuery and parsedown/markdown When I open my bootstrap modal, it allows me to create a new Reference-style link like on here.
I am trying to be able to find some how where it can find the next free number for my available in my textarea and when click save in model will set it.
I am fine [exmple-1][1] and [example-3][3]
[1]: http://www.example.com
[3]: http://www.example.com
And when I open my bootstrap modal and create a new hyperlink it will set and add the next available number
Here is the Codepen Example
Question: How can I when I create a new hyperlink in my bootstrap modal
and click save it can find the next available number set it. Because only 1 & 3 are set in example above next one should be 2 when click save in model
currently as you can see below I just use var counter = 1; and counter++; to be able to create numbers.
Script:
$('#myLink').on('shown.bs.modal', function() {
var text = getSelectedText();
$('#title').val(text.trim());
$('#url').val('http://');
});
function getSelectedText() {
var textarea = document.getElementById("message");
var len = textarea.value.length;
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var sel = textarea.value.substring(start, end);
return sel;
}
var counter = 1;
$('#save-link').on('click', function(e) {
var textarea = document.getElementById("message");
var len = textarea.value.length;
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var sel = textarea.value.substring(start, end);
var replace = '[' + $('input#title').val() + ']' + '[' + counter + ']';
var id = '\n [' + counter + ']: ' + $('input#url').val();
counter++;
if ($('#title').val().length > 0) {
textarea.value = textarea.value.substring(0,start) + replace +
textarea.value.substring(end,len) + ' \n' + id;
$('#myLink').modal('hide');
//$('#myLink form')[0].reset();
} else {
return false;
}
});
You can use a simple regex to find the used numbers in the textarea:
function findAvailableNumber(textarea){
//Find lines with links
var matches = textarea.value.match(/(^|\n)\s*\[\d+\]:/g);
//Find corresponding numbers
var usedNumbers = matches.map(function(match){
return parseInt(match.match(/\d+/)[0]); }
);
//Find first unused number
var number = 1;
while(true){
if(usedNumbers.indexOf(number) === -1){
//Found unused number
return number;
}
number++;
}
return number;
}
Add the function, remove the line var counter = 1; and replace counter++; with var counter = findAvailableNumber(textarea);
JSFiddle
As Barmar said: store your already generated numbers in an object or an array and check for the next non-existing number:
var existingNumbers = [1, 3];
function getNextNumber() {
var i = 1;
while (existingNumbers.indexOf(i) > - 1) {
i++;
}
existingNumbers.push(i);
return i;
}
Then get the next number with:
var number = getNextNumber();

adding to existing array on click just empties the array why?

Another angularJS question i have scope binding a click that should add one value on the first click ad another value on the second click but it just keeps returning and empty array and filling the first value again why? :
scope.dbclickalert = function (scope, $watch) {
var getCheckInDate = this.cellData.date;
var formatcheckindate = new Date(getCheckInDate);
var checkingdates = [];
var curr_datecell = formatcheckindate.getDate();
var padded_day = (curr_datecell < 10) ? '0' + curr_datecell : curr_datecell;
var curr_monthcell = formatcheckindate.getMonth() + 1;
var padded_month = (curr_monthcell < 10) ? '0' + curr_monthcell : curr_monthcell;
var curr_yearcell = formatcheckindate.getFullYear();
var date_stringcell = + padded_month + "/" + padded_day + "/" + curr_yearcell;
var checkindatestrg = "checkindate";
console.log(checkingdates.length);
if (checkingdates.length < 2) {
alert("element exists in array");
checkingdates.push('checkoutdate');
checkingdates.push(date_stringcell);
console.log(checkingdates + checkingdates.length);
} else {
checkingdates.push('checkindate');
checkingdates.push(date_stringcell);
}
var setCheckInDate = el.hasClass('checkInDate');
if (checkingdates === true) {
alert('You have allready set your check In Date');
} else {
el.addClass('checkInDate');
$('#checkoutbar').addClass('datePickerchecout');
}
$(".date-cell").each(function removeclasses() {
$(this).removeClass('true');
});
return getCheckInDate;
};
ok so when i declare this outside of the function i get undefined error:
scope.checkingdates = [];
scope.dbclickalert = function(scope, $watch){
var getCheckInDate = this.cellData.date;
var formatcheckindate = new Date(getCheckInDate);
var checkingdates = scope.checkingdates;
var curr_datecell = formatcheckindate.getDate();
var padded_day = (curr_datecell < 10) ? '0'+curr_datecell : curr_datecell;
var curr_monthcell = formatcheckindate.getMonth() + 1;
var padded_month = (curr_monthcell < 10) ? '0'+curr_monthcell : curr_monthcell;
var curr_yearcell = formatcheckindate.getFullYear();
var date_stringcell = + padded_month + "/" + padded_day + "/" + curr_yearcell;
var checkindatestrg = "checkindate";
console.log(checkingdates.length);
if (checkingdates.length < 2){
alert("element exists in array");
checkingdates.push('checkoutdate');
checkingdates.push(date_stringcell);
console.log(checkingdates+checkingdates.length);
}else{
checkingdates.push('checkindate');
checkingdates.push(date_stringcell);
}
var setCheckInDate = el.hasClass('checkInDate');
if (checkingdates === true){
alert('You have allready set your check In Date');
} else{
el.addClass('checkInDate');
$('#checkoutbar').addClass('datePickerchecout');
}
$(".date-cell").each(function removeclasses() {
$(this).removeClass('true');
});
return getCheckInDate;
};
ok in the third version of this it the same data "the date" again if the same div has been clicked but not if a second div with the same ng-click="dbclickalert()" is clicked why?
link: function(scope, el, attributes, dateSheetCtrl, $watch) {
scope.checkingdatesarry = [];
scope.dbclickalert = function(){
var getCheckInDate = this.cellData.date;
var formatcheckindate = new Date(getCheckInDate);
var checkingdates = scope.checkingdates;
var curr_datecell = formatcheckindate.getDate();
var padded_day = (curr_datecell < 10) ? '0'+curr_datecell : curr_datecell;
var curr_monthcell = formatcheckindate.getMonth() + 1;
var padded_month = (curr_monthcell < 10) ? '0'+curr_monthcell : curr_monthcell;
var curr_yearcell = formatcheckindate.getFullYear();
var date_stringcell = + padded_month + "/" + padded_day + "/" + curr_yearcell;
var checkindatestrg = "checkindate";
var checkoutdatestrg = "checkoutdate";
if( $.inArray('checkindate', scope.checkingdates) !== -1 ) {
scope.checkingdatesarry.push(checkoutdatestrg);
scope.checkingdatesarry.push(date_stringcell);
console.log(scope.checkingdatesarry + scope.checkingdatesarry.length);
}
else{
scope.checkingdatesarry.push(checkindatestrg);
scope.checkingdatesarry.push(date_stringcell);
console.log(scope.checkingdatesarry + scope.checkingdatesarry.length);
}
var setCheckInDate = el.hasClass('checkInDate');
if (scope.checkingdates === true){
alert('You have allready set your check In Date');
} else{
el.addClass('checkInDate');
$('#checkoutbar').addClass('datePickerchecout');
}
$(".date-cell").each(function removeclasses() {
$(this).removeClass('true');
});
return scope.checkingdatesarry;
};
ok for anyone that cares the answer was that because my div's where being created with a angularJS directive it was returning and array per div instead of one global array I have taken the array all the way out of the directive and moved it into a service and it works fine.
Because you redefined the array with empty list in the dbclickalert action. So every time when the action is triggered, the array will be empty.
var checkingdates = [];
You should move it outside the function and declare as
$scope.checkingdates = []; //In your code, you may use scope.checkingdates = []; if you renamed the $scope when you inject it

Categories