how to find exact cursor position in tinymce - tinymce

I have tried to get the exact position to drop the content in tinymce. But not able to find the cursor position and not able to fetch the data values takes place. Its only fetching values in previous position.
Please help me.
I have tried below code,
tinymce.activeEditor.selection.getRng() - here we can't able to find the exact position. it fetch only previoous position.
ed.on("drop", function($event) {
if($event.preventDefault) {
$event.preventDefault();
}
if($event.stopPropagation()) {
$event.stopPropagation();
}
if ($event.dataTransfer) {
var data = $event.dataTransfer.getData("Text");
var $data = angular.fromJson(data);
}
});
In drop also we cant able to find the exact position.
Please give me the solution.

This is a tricky one.
I wrote two functions. One to get the absolute cursor position in tinymce and one to set the cursor to a specified cursor position. Plus two helpfull functions to set the caret in the editor and one to get the block element in which the actual caret is to be found.
I have those functions placed inside my own custom tinymce plugin myownplugin, but you may put those functions where you like.
Here they are:
// gets the caret offset to the editor start
get_caret_offset: function()
{
var ed = this.editor;
var range = ed.selection.getRng(1);
var copied_range = range.cloneRange();
var subtract_from_index = 0;
// count returns
var index = $(ed.plugins.myownplugin.p_of(range.startContainer)).index();
var add_to_offset = 0;
// add 1 for each empty paragraph
var $children = $(ed.getBody()).children();
// some node contents need to be subtracted from the offset later on.
// free paragraphs count for example
var subtract = 0;
var rte_starts = 0;
for (var i=0; i < index; i++)
{
var child_node = $children.eq(i).get(0);
if (child_node)
{
if (child_node.nodeName == 'RTEPHTAG')
{
subtract_from_index++;
if (child_node.className == "start")
{
subtract = 0;
}
else subtract = 0;
}
if (subtract && (child_node.nodeName == 'P' || child_node.nodeName == 'OL' || child_node.nodeName == 'UL'))
{
subtract_from_index += child_node.textContent.replace(re_exspaces,'')
.replace( /\ufeff/g, '' )
.replace( /\u00ad/g, '' )
.replace( //g, '')
.replace( /­/g, '')
.replace( /­/g, '').length + 1;
}
// count bBRs
else if(child_node.nodeName == 'P' || child_node.nodeName == 'OL' || child_node.nodeName == 'UL')
{
add_to_offset += $(child_node).find('br').length;
}
}
}
// copy range to be able to calculate characters to the start of the initial range
copied_range.setStart(ed.getBody().firstChild, 0);
// caret paragraph needs to be counted too?
if(subtract)
{
if (index)
{
var endnode = $children.eq(index - 1).get(0);
if (endnode) copied_range.setEndAfter(endnode);
else copied_range.setEnd(range.endContainer, range.endOffset);
}
else return 0;
}
else copied_range.setEnd(range.endContainer, range.endOffset);
var str = copied_range.toString();
subtract_from_index += ( str.match(/[\uD800-\uDBFF]/g) || [] ).length;
var caret_offset = str
.replace(re_exspaces,'')
.replace( /\ufeff/g, '' )
.replace( /\u00ad/g, '' )
.replace( //g, '')
.replace( /­/g, '')
.replace( /­/g, '').length + index + add_to_offset + rte_starts - subtract_from_index;
// add BRs in the actual paragraph?
var p_end = ed.plugins.myownplugin.p_of(range.endContainer);
if(range.endContainer && range.endContainer.nodeName != 'BODY')
{
if (p_end){
copied_range.setStartBefore(p_end);
}
}
return caret_offset;
},
// sets the caret according to the offset
set_caret_offset: function(offset, scroll_into_view)
{
function get_count(where_str, search_str) {
var m = where_str.match(new RegExp(search_str.toString().replace(/(?=[.\\+*?[^\]$(){}\|])/g, "\\"), "g"));
return m ? m.length:0;
}
var ed = this.editor;
// Check: enough content to set caret?
if (!offset)
ed.plugins.myownplugin.setCursor(ed, ed.getBody().firstChild, 1);
var $body = $(ed.getBody());
var content = ed.getContent({format : 'text', no_events : 1})
.replace(re_exspaces,'')
.replace( /\ufeff/g, '' )
.replace( /\u00ad/g, '' )
.replace( //g, '')
.replace( /­/g, '')
.replace( /­/g, '');
var content_length = content.length + $body.find('br').length - ( content.match(/[\uD800-\uDBFF]/g) || [] ).length;
if (content_length <= offset)
{
// set caret to the end of the editor
ed.plugins.myownplugin.setCursor(ed, ed.getBody().lastChild, 0);
}
else
{
var offset_till_now = 0;
var node = ed.getBody().firstChild;
var rng;
var counter = 0;
while (node && offset > offset_till_now)
{
var br_count = 0;
// Offset muss verringert werden, da Indesign Ps und BRs als Zeichen zählt
if (counter && (node.nodeName == "BR" || node.nodeName == "P" || node.nodeName == "OL" || node.nodeName == "UL"))
{
offset = offset - 1;
}
counter++;
if (node.nodeName == "P" || node.nodeName == "OL" || node.nodeName == "UL")
{
br_count = $(node).find('br').length;
}
var textContent = node.textContent
.replace(re_exspaces,'')
.replace( /\ufeff/g, '' )
.replace( /\u00ad/g, '' )
.replace( //g, '')
.replace( /­/g, '')
.replace( /­/g, '');
var length = textContent.length + br_count - ( textContent.match(/[\uD800-\uDBFF]/g) || [] ).length;
// textnode?
if (node && node.nodeType == 3)
{
if (offset <= offset_till_now + length)
{
rng = ed.selection.getRng(1);
rng.setStart(node, offset - offset_till_now);
rng.setEnd(node, offset - offset_till_now);
offset_till_now = offset; // fertig
}
else
{
node = node.nextSibling;
offset_till_now += length;
}
}
else
{
// caret to be inserted here ?
if (offset < offset_till_now + length)
{
// nur text nodes als Kinder?
// dann schreibe den textknoten neu und setze die Range entsprechend
// ansonsten weiter mit dem ersten Kind
if (!node.children.length)
{
var textnode = ed.getDoc().createTextNode(node.textContent);
node.innerHTML = '';
node.appendChild(textnode);
rng = ed.selection.getRng(1);
rng.setStart(textnode, offset - offset_till_now);
rng.setEnd(textnode, offset - offset_till_now);
var copied_range = rng.cloneRange();
copied_range.setStart(textnode, 0);
copied_range.setEnd(textnode, offset - offset_till_now);
var t = copied_range.toString();
var add_to_index = ( t.match(/[\uD800-\uDBFF]/g) || [] ).length;
rng.setStart(textnode, offset - offset_till_now + add_to_index);
rng.setEnd(textnode, offset - offset_till_now + add_to_index);
offset_till_now = offset; // fertig
}
node = node.firstChild;
}
// caret needs to be set after this node
else
{
if (offset == offset_till_now + length)
{
// set caret to start of node
ed.plugins.myownplugin.setCursor(ed, node, 0);
offset_till_now = offset; // fertig
}
else
{
// get following node
node = node.nextSibling;
offset_till_now += length;
}
}
}
}
}
// scrollinto view
if (scroll_into_view)
{
ed.execCommand('mceInsertContent', false, '<span class="set_caret_marker">\ufeff</span>');
setTimeout(function() {
var $marker = $(ed.getBody()).find('.set_caret_marker');
if ($marker.length)
{
$marker.get(0).scrollIntoView();
ed.selection.select($marker.get(0));
$marker.remove();
}
}, 500);
}
},
// sets the cursor position (start defines if the caret is to be placed at the start)
setCursor: function (ed, element, start)
{
// caret may appear inside lis only
if (element.nodeName == "UL" || element.nodeName == "OL")
{
$element = $(element);
element = start ? $element.find('li:first').get(0) : $element.find('li:last').get(0);
}
var doc = ed.getDoc();
var edwin = ed.getWin();
var range = ed.selection.dom.createRng();
range.selectNodeContents(element);
range.collapse(start);
var sel = ed.selection.getSel();
sel && sel.removeAllRanges();
sel && sel.addRange(range);
},
// returns the block parent element of the actual element
// stopper defines a node at which we will stop
p_of: function (node, stopper)
{
stopper = stopper ? stopper.toUpperCase() : null;
while (node)
{
if (node.nodeName == 'BODY') { return null; }
if (node.nodeName == 'P' || node.nodeName == 'UL' || node.nodeName == 'OL' || node.nodeName == stopper) { return node; }
else { node = node.parentNode; }
}
return null;
},
I hope this is usefull for some of you.

Related

Flutter TextField input validation for a date

I am trying to write a date input control which accepts a date like 23/12/1997. What I would like it to do is automatically insert the / characters for the user. So as they type in 23 the listener returns 23/, so that they can then type in 12. At this point the listener again adds a / leaving the user to complete the date by typing 1997.
My TextEditingController code half works and looks like this:
final _controller = TextEditingController();
_controller.addListener(() {
String text = _controller.text;
if (text.length == 2) {
text += '/';
}
if (text.length == 5) {
text += '/';
}
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
print(_controller.text);
}
So it works fine until the user makes a mistake and needs to backtrack. As soon as a / is deleted it is immediately replaced stopping any further editing of the date.
In order to get it to work I need to access is the previously entered text to determine if the user is backspacing. So if text == 23/ && previous_text == 23/1 then I can remove the / from text.
I found this question textfield must only accept numbers and I think it may help me, but I am not sure how to implement an existing widget and override its methods. Of course there may be a simpler way to do this within the TextEditingController?
I have found what I needed to solve my date validation input. It is not perfect, but it is good enough for what I am trying to do. All I needed was to look at the inputFormatters method of a TextField(). This allow manipulation of the input text to put it into any number of user-defined formats. I am including a segment of my code for anyone who would like to try it out:
class _DateFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
if (cLen == 1) {
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
// Add a / char
cText += '/';
}
} else if (cLen == 4) {
// Can only be 0 or 1
if (int.parse(cText.substring(3, 4)) > 1) {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
// Month cannot be greater than 12
int mm = int.parse(cText.substring(3, 5));
if (mm == 0 || mm > 12) {
// Remove char
cText = cText.substring(0, 4);
} else {
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
int y2 = int.parse(cText.substring(6, 8));
if (y2 < 19 || y2 > 20) {
// Remove char
cText = cText.substring(0, 7);
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
To use it simply call it from the Textfield() as shown below. I've also incorporated two built in methods as well. WhitelistingTextInputFormatter() to only allow digits and a slash(/) character to be entered and LengthLimitingTextInputFormatter() to restrict the number of characters allowed. The latter could be achieved using the maxLength parameter of TextField() but it is here by way of example. Note that there is also a BlacklistingTextInputFormatter() which does as you would expect.
WhitelistingTextInputFormatter was removed use FilteringTextInputFormatter.allow(RegExp("[0-9-]")), to replace, and If you want to change split symbol (current is "/"), Pls add it to RegExp(....).
TextField(
// maxLength: 10,
keyboardType: TextInputType.datetime,
controller: _controllerDOB,
focusNode: _focusNodeDOB,
decoration: InputDecoration(
hintText: 'DD/MM/YYYY',
counterText: '',
),
inputFormatters: [
WhitelistingTextInputFormatter(RegExp("[0-9/]")),
LengthLimitingTextInputFormatter(10),
_DateFormatter(),
],
),
You can use datepicker dialog made by flutter.
DateTime _date = DateTime.now()
onPressed: () {
showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(2020),
lastDate: DateTime(2021),
).then((date) {
setState(() {
_date = date;
});
});
},
I find your code to be a great utility, without any dependencies. I took the liberty to do a few mods and thought of posting it back here, as I find your concept very neat and lightweight on the UI. The requirements were;
Validating the date for non-31-day months and leap years. The mods were quite straightforward.
Preventing the user entering "/" at undesirable places which will throw the algorithm off-track. The simplest solution is to make the
keyboardType: TextInputType.number, in the TextField
This works perfectly for mobile devices.
But Flutter being cross-platform, this solution may not be foolproof when it comes to a device with physical keyboard. I tried various checks and blocks but only partially succeeded; i.e, user can still input "/" in between the digits of the Day and Month. I think there is an internal delay between KB input and programmatic formatter.
Following is the modified code for _DateFormatter. I have used the /// notion to distinguish my comments. They should be read along with the original // comments.
class _DateFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue prevText, TextEditingValue currText) {
int selectionIndex;
String date;
String month;
int year;
// Get the previous and current input strings
String pText = prevText.text;
String cText = currText.text;
cText = cText.replaceAll("//", "/");
// Abbreviate lengths
int cLen = cText.length;
int pLen = pText.length;
/// ENTERING THE DATE
if (cLen == 1) {
/// User enters the first digit of the date. The first digit
// Can only be 0, 1, 2 or 3
if (int.parse(cText) > 3) {
// Remove char
cText = '';
}
} else if (cLen == 2 && pLen == 1) {
/// User has already entered a valid first digit of the date, now he
/// enters the second digit of the date; but
// Days cannot be greater than 31
int dd = int.parse(cText.substring(0, 2));
if (dd == 0 || dd > 31) {
// Remove char
cText = cText.substring(0, 1);
} else {
/// User has entered a valid date (between 1 and 31). So now,
// Add a / char
cText += '/';
}
/// ENTERING THE MONTH
} else if (cLen == 4) {
/// after entering a valid date and programmatic insertion of '/', now User has entered
/// the first digit of the Month. But, it
// Can only be 0 or 1
/// (and, not '/' either)
if (int.parse(cText.substring(3, 4)) > 1 || cText.substring(3, 4) == "/") {
// Remove char
cText = cText.substring(0, 3);
}
} else if (cLen == 5 && pLen == 4) {
int mm = int.parse(cText.substring(3, 5));
int dd = int.parse(cText.substring(0, 2));
/// User has entered the second digit of the Month, but the
// Month cannot be greater than 12
/// Also, that entry cannot be '/'
if ((mm == 0 || mm > 12|| cText.substring(3, 5) == "/") ||
/// If the date is 31, the month cannot be Apr, Jun, Sept or Nov
(dd == 31 && (mm == 02 || mm == 04 || mm == 06 || mm == 09 || mm == 11)) ||
/// If the date is greater than 29, the month cannot be Feb
/// (Leap years will be dealt with, when user enters the Year)
(dd > 29 && (mm == 02))) {
// Remove char
cText = cText.substring(0, 4);
}
else if (cText.length == 5) {
/// the Month entered is valid; so,
// Add a / char
cText += '/';
}
} else if ((cLen == 3 && pLen == 4) || (cLen == 6 && pLen == 7)) {
// Remove / char
cText = cText.substring(0, cText.length - 1);
} else if (cLen == 3 && pLen == 2) {
if (int.parse(cText.substring(2, 3)) > 1) {
// Replace char
cText = cText.substring(0, 2) + '/';
} else {
// Insert / char
cText =
cText.substring(0, pLen) + '/' + cText.substring(pLen, pLen + 1);
}
/// ENTERING THE YEAR
} else if (cLen == 6 && pLen == 5) {
// Can only be 1 or 2 - if so insert a / char
int y1 = int.parse(cText.substring(5, 6));
if (y1 < 1 || y1 > 2) {
// Replace char
/// i.e, add '/' after the 5th position
cText = cText.substring(0, 5) + '/';
} else {
// Insert / char
cText = cText.substring(0, 5) + '/' + cText.substring(5, 6);
}
} else if (cLen == 7) {
/// the first digit of year
// Can only be 1 or 2
int y1 = int.parse(cText.substring(6, 7));
if (y1 < 1 || y1 > 2) {
// Remove char
cText = cText.substring(0, 6);
}
} else if (cLen == 8) {
// Can only be 19 or 20
/// Also, there cannot be / typed by the user
String y2 = cText.substring(6, 8);
if (y2 != "19" && y2 != "20") {
// Remove char
cText = cText.substring(0, 7);
}
} else if (cLen == 9) {
/// There cannot be / typed by the user
if (cText.substring(8, 9) == "/") {
// Remove char
cText = cText.substring(0, 8);
}
} else if (cLen == 10) {
/// There cannot be / typed by the user
if (cText.substring(9, 10) == "/") {
// Remove char
cText = cText.substring(0, 9);
}
/// If the year entered is not a leap year but the date entered is February 29,
/// it will be advanced to the next valid date
date = cText.substring(0, 2);
month = cText.substring(3, 5);
year = int.parse(cText.substring(6, 10));
bool isNotLeapYear = !((year % 4 == 0) && (year % 100 != 0) ||
(year % 400 == 0));
if (isNotLeapYear && month == "02" && date == "29") {
cText = "01/03/$year";
}
}
selectionIndex = cText.length;
return TextEditingValue(
text: cText,
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
} // END OF class _DateFormatter
I found one solution for this, But not an optimized solution, but it is covering almost all the scenarios,
forward slash during adding fields
remove the forward slash on clearing fields
in between editing handling... etc
class CustomDateTextFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var text = _format(newValue.text, '/', oldValue);
return newValue.copyWith(
text: text, selection: _updateCursorPosition(text, oldValue));
}
}
String _format(String value, String seperator, TextEditingValue old) {
var finalString = '';
var dd = '';
var mm = '';
var yyy = '';
var oldVal = old.text;
print('<------------------------- start---------------------------->');
print('oldVal -> $oldVal');
print('value -> $value');
var temp_oldVal = oldVal;
var temp_value = value;
if (!oldVal.contains(seperator) ||
oldVal.isEmpty ||
seperator.allMatches(oldVal).length < 2) {
oldVal += '///';
}
if (!value.contains(seperator) || _backSlashCount(value) < 2) {
value += '///';
}
var splitArrOLD = oldVal.split(seperator);
var splitArrNEW = value.split(seperator);
print('----> splitArrOLD: $splitArrOLD');
print('----> splitArrNEW: $splitArrNEW');
for (var i = 0; i < 3; i++) {
splitArrOLD[i] = splitArrOLD[i].toString().trim();
splitArrNEW[i] = splitArrNEW[i].toString().trim();
}
// block erasing
if ((splitArrOLD[0].isNotEmpty &&
splitArrOLD[2].isNotEmpty &&
splitArrOLD[1].isEmpty &&
temp_value.length < temp_oldVal.length &&
splitArrOLD[0] == splitArrNEW[0] &&
splitArrOLD[2].toString().trim() ==
splitArrNEW[1].toString().trim()) ||
(_backSlashCount(temp_oldVal) > _backSlashCount(temp_value) &&
splitArrNEW[1].length > 2) ||
(splitArrNEW[0].length > 2 && _backSlashCount(temp_oldVal) == 1) ||
(_backSlashCount(temp_oldVal) == 2 &&
_backSlashCount(temp_value) == 1 &&
splitArrNEW[0].length > splitArrOLD[0].length)) {
finalString = temp_oldVal; // making the old date as it is
print('blocked finalString : $finalString ');
} else {
if (splitArrNEW[0].length > splitArrOLD[0].length) {
if (splitArrNEW[0].length < 3) {
dd = splitArrNEW[0];
} else {
for (var i = 0; i < 2; i++) {
dd += splitArrNEW[0][i];
}
}
if (dd.length == 2 && !dd.contains(seperator)) {
dd += seperator;
}
} else if (splitArrNEW[0].length == splitArrOLD[0].length) {
print('splitArrNEW[0].length == 2');
if (oldVal.length > value.length && splitArrNEW[1].isEmpty) {
dd = splitArrNEW[0];
} else {
dd = splitArrNEW[0] + seperator;
}
} else if (splitArrNEW[0].length < splitArrOLD[0].length) {
print('splitArrNEW[0].length < splitArrOLD[0].length');
if (oldVal.length > value.length &&
splitArrNEW[1].isEmpty &&
splitArrNEW[0].isNotEmpty) {
dd = splitArrNEW[0];
} else if (temp_oldVal.length > temp_value.length &&
splitArrNEW[0].isEmpty &&
_backSlashCount(temp_value) == 2) {
dd += seperator;
} else {
if (splitArrNEW[0].isNotEmpty) {
dd = splitArrNEW[0] + seperator;
}
}
}
print('dd value --> $dd');
if (dd.isNotEmpty) {
finalString = dd;
if (dd.length == 2 &&
!dd.contains(seperator) &&
oldVal.length < value.length &&
splitArrNEW[1].isNotEmpty) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
} else if (splitArrNEW[2].isNotEmpty &&
splitArrNEW[1].isEmpty &&
temp_oldVal.length > temp_value.length) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
} else if (oldVal.length < value.length &&
(splitArrNEW[1].isNotEmpty || splitArrNEW[2].isNotEmpty)) {
if (seperator.allMatches(dd).isEmpty) {
finalString += seperator;
}
}
} else if (_backSlashCount(temp_oldVal) == 2 && splitArrNEW[1].isNotEmpty) {
dd += seperator;
}
print('finalString after dd=> $finalString');
if (splitArrNEW[0].length == 3 && splitArrOLD[1].isEmpty) {
mm = splitArrNEW[0][2];
}
if (splitArrNEW[1].length > splitArrOLD[1].length) {
print('splitArrNEW[1].length > splitArrOLD[1].length');
if (splitArrNEW[1].length < 3) {
mm = splitArrNEW[1];
} else {
for (var i = 0; i < 2; i++) {
mm += splitArrNEW[1][i];
}
}
if (mm.length == 2 && !mm.contains(seperator)) {
mm += seperator;
}
} else if (splitArrNEW[1].length == splitArrOLD[1].length) {
print('splitArrNEW[1].length = splitArrOLD[1].length');
if (splitArrNEW[1].isNotEmpty) {
mm = splitArrNEW[1];
}
} else if (splitArrNEW[1].length < splitArrOLD[1].length) {
print('splitArrNEW[1].length < splitArrOLD[1].length');
if (splitArrNEW[1].isNotEmpty) {
mm = splitArrNEW[1] + seperator;
}
}
print('mm value --> $mm');
if (mm.isNotEmpty) {
finalString += mm;
if (mm.length == 2 && !mm.contains(seperator)) {
if (temp_oldVal.length < temp_value.length) {
finalString += seperator;
}
}
}
print('finalString after mm=> $finalString');
if (splitArrNEW[1].length == 3 && splitArrOLD[2].isEmpty) {
yyy = splitArrNEW[1][2];
}
if (splitArrNEW[2].length > splitArrOLD[2].length) {
print('splitArrNEW[2].length > splitArrOLD[2].length');
if (splitArrNEW[2].length < 5) {
yyy = splitArrNEW[2];
} else {
for (var i = 0; i < 4; i++) {
yyy += splitArrNEW[2][i];
}
}
} else if (splitArrNEW[2].length == splitArrOLD[2].length) {
print('splitArrNEW[2].length == splitArrOLD[2].length');
if (splitArrNEW[2].isNotEmpty) {
yyy = splitArrNEW[2];
}
} else if (splitArrNEW[2].length < splitArrOLD[2].length) {
print('splitArrNEW[2].length < splitArrOLD[2].length');
yyy = splitArrNEW[2];
}
print('yyy value --> $yyy');
if (yyy.isNotEmpty) {
if (_backSlashCount(finalString) < 2) {
if (splitArrNEW[0].isEmpty && splitArrNEW[1].isEmpty) {
finalString = seperator + seperator + yyy;
} else {
finalString = finalString + seperator + yyy;
}
} else {
finalString += yyy;
}
} else {
if (_backSlashCount(finalString) > 1 && oldVal.length > value.length) {
var valueUpdate = finalString.split(seperator);
finalString = valueUpdate[0] + seperator + valueUpdate[1];
}
}
print('finalString after yyyy=> $finalString');
}
print('<------------------------- finish---------------------------->');
return finalString;
}
TextSelection _updateCursorPosition(String text, TextEditingValue oldValue) {
var endOffset = max(
oldValue.text.length - oldValue.selection.end,
0,
);
var selectionEnd = text.length - endOffset;
print('My log ---> $selectionEnd');
return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
}
int _backSlashCount(String value) {
return '/'.allMatches(value).length;
}
We can Use our custom formator as in inputFormatters like below
TextField(
// maxLength: 10,
keyboardType: TextInputType.datetime,
controller: _controllerDOB,
focusNode: _focusNodeDOB,
decoration: InputDecoration(
hintText: 'DD/MM/YYYY',
counterText: '',
),
inputFormatters: [
WhitelistingTextInputFormatter(RegExp("[0-9/]")),
LengthLimitingTextInputFormatter(10),
CustomDateTextFormatter(),
],
),
try out this solution.

Need to update the Date format on my Google Mail Script

I'm creating a Gmail script that includes 5 variables, one of which is a due date. I just want it to populate as MM/DD/YYYY, however, it is currently populating as Thu Sep 13 2018 00:00:00 GMT-0400 (EDT).
Is there a way I can do that? I've pasted my code below for your reference. Any assistance is much appreciated.
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(range.getValues(), normalizeHeaders(headers));
}
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue;
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit(char);
}
function isDigit(char) {
return char >= '0' && char <= '9';

Convert Number to Words in apex salesforce

Below apex code can be used to convert number (currency) into words. This code can be used in triggers,visualforce pages to convert any number/currency field value in words and stored in any text field.
How to call this class
Decimal d = 1491511.61;
NumberTOWordConvertion nwcObj = new NumberTOWordConvertion();
String numInWords = nwcObj.getNumberTOWordConvertion(d);
Output : Fourteen Lakh Ninety One Thousand Five Hundred and Eleven Rupess And Sixty One Paisa Only.
public class NumberTOWordConvertion {
// Call this method with Number to convert
public String getNumberTOWordConvertion(Decimal num) {
Decimal junkVal = num;
Decimal junkValPaisa = junkVal - Math.floor(junkVal);
junkVal = Math.floor(junkVal);
String obStr = junkVal.toPlainString();
String[] numReversed = obStr.split('');
String[] actnumber = reverse(numReversed);
String firstHalf = convertInWords(numReversed, actnumber);
Integer tmp = Math.round(junkValPaisa * 100);
junkValPaisa = (Decimal)tmp / 100; System.debug('jj :' + junkValPaisa);
String paisaStr = junkValPaisa.toPlainString();
String secondHalf;
if (paisaStr == '0') {
secondHalf = '';
} else if (paisaStr.length() != 4) {
paisaStr = paisaStr + '0';
paisaStr = paisaStr.substring(2);
String [] numReversedPaisa = paisaStr.split('');
String[] actnumberPaisa = reverse(numReversedPaisa);
secondHalf = convertInWords(numReversedPaisa, actnumberPaisa);
} else {
paisaStr = paisaStr.substring(2);
String [] numReversedPaisa = paisaStr.split('');
String[] actnumberPaisa = reverse(numReversedPaisa);
secondHalf = convertInWords(numReversedPaisa, actnumberPaisa);
}
String SumOFHalves = '';
if (secondHalf.length() > 4) {
firstHalf = firstHalf.replace('Only', 'Rupess And ');
secondHalf = secondHalf.replace('Only', 'Paisa Only');
SumOFHalves = firstHalf + secondHalf;
} else {
firstHalf = firstHalf.replace('Only', 'Rupess Only');
SumOFHalves = firstHalf;
}
// IF amount has any value
if (SumOFHalves.length() > 5) {
return SumOFHalves;
} else {
return '';
}
}
// Method reverse the number
public List<String> reverse(List<String> strToRev) {
List<String> revList = new List<String>();
for (Integer i = strToRev.size() - 1; i >= 0; i--) {
revList.add(strToRev.get(i));
}
revList.add('');
return revList;
}
public String convertInWords(String[] numRev, String[] actnum) {
List<String> iWords = new List<String> {'Zero', ' One', ' Two', ' Three', ' Four', ' Five', ' Six', ' Seven', ' Eight', ' Nine'};
List<String> ePlace = new List<String> {' Ten', ' Eleven', ' Twelve', ' Thirteen', ' Fourteen', ' Fifteen', ' Sixteen', ' Seventeen', ' Eighteen', ' Nineteen'};
List<String> tensPlace = new List<String> {'dummy', ' Ten', ' Twenty', ' Thirty', ' Forty', ' Fifty', ' Sixty', ' Seventy', ' Eighty', ' Ninety' };
Integer iWordsLength = numRev.size();
String totalWords = '';
List<String> inWords = new List<String>();
for (Integer k = 0; k < iWordsLength; k++) {
inWords.add('');
}
String finalWord = '';
Integer j = 0;
// Main For loop
for (Integer i = 0; i < iWordsLength; i++) {
if (i == 0) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
inWords[j] = inWords[j] + ' Only';
} else if (i == 1) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 2) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i - 1] != '0' && actnum[i - 2] != '0') {
inWords[j] = iWords[Integer.valueof(actnum[i])] + ' Hundred and';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])] + ' Hundred';
}
} else if (i == 3) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
if (actnum[i + 1] != '0' || Integer.valueof(actnum[i]) > 0) {
inWords[j] = inWords[j] + ' Thousand';
}
} else if (i == 4) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 5) {
if (actnum[i] == '0' || actnum[i + 1] == '1') {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
if (actnum[i + 1] != '0' || Integer.valueof(actnum[i]) > 0) {
inWords[j] = inWords[j] + ' Lakh';
}
} else if (i == 6) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
} else if (i == 7) {
if (actnum[i] == '0' || actnum[i + 1] == '1' ) {
inWords[j] = '';
} else {
inWords[j] = iWords[Integer.valueof(actnum[i])];
}
inWords[j] = inWords[j] + ' Crore';
} else if (i == 8) {
if (actnum[i] == '0') {
inWords[j] = '';
} else if (actnum[i] == '1') {
inWords[j] = ePlace[Integer.valueof(actnum[i - 1])];
} else {
inWords[j] = tensPlace[Integer.valueof(actnum[i])];
}
}
j++;
}
// End of For loop
// Reverse the List
inWords = reverse(inWords);
for (Integer i = 0; i < inWords.size(); i++) {
finalWord += inWords[i];
}
return finalWord;
}
}
String.valueOf(num);
returns the string equivalent of the num

How to rotate image from cameraUI for iOS in actionscript 3.0

I'm building an App with actionscript 3.0 in my Flash builder. This is a followup question to this question, It works but when I take the picture, the image comes out rotated to the left. how can I check which way the user is holding the phone? and then what code do I use to rotate the image to it's corresponding place?
Thanks in advanced!
EDIT:
I'm using this code to rotate the image, but it seems to only rotate the image being displayed not the image file, any ideas?
var mat:Matrix = new Matrix();
mat.translate(-W/2, -H/2);
mat.rotate(Math.PI/2);
mat.translate(+W/2, +H/2);
mat.concat(myObj.transform.matrix);
myObj.transform.matrix = mat;
~Myy
You can't get stage.orientation data when you have the cameraUI view active, so here's how i solved the same issue, please note that this controller has many reusable functions, i first get the raw image data from media promise (this raw data has EXIF data that i will read to get image orientation from the camera) i then convert this byteArray to BitmapData and then resize and scale as i need. One thing to note is that if you convert the raw image data to Bitmap data you will lose EXIF data so read it before you modify the image, hope this helps and i posted it here late i know but there's no other solution in the web and maybe someone will need this sometime. Cheers
SnapshotController.as
package controllers
{
import flash.display.BitmapData;
import flash.display.JPEGEncoderOptions;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.MediaEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.media.CameraUI;
import flash.media.MediaPromise;
import flash.media.MediaType;
import flash.utils.ByteArray;
import flash.utils.IDataInput;
import mx.utils.Base64Encoder;
import classes.APIupload;
public class SnapshotController
{
private var cameraUI:CameraUI = new CameraUI();
private var dataSource:IDataInput;
private var tempDir:File = new File();
private var imageOrientation:int = 0 ;
public function SnapshotController()
{
}
public function LaunchCameraUI():void
{
if( CameraUI.isSupported ) {
trace( "Initializing camera..." );
cameraUI.addEventListener( MediaEvent.COMPLETE, imageSelected );
cameraUI.launch( MediaType.IMAGE );
} else {
trace( "CameraUI is not supported.");
}
}
private function imageSelected( event:MediaEvent ):void {
trace( "Media selected..." );
var imagePromise:MediaPromise = event.data;
dataSource = imagePromise.open();
if( imagePromise.isAsync ) {
trace( "Asynchronous media promise." );
var eventSource:IEventDispatcher = dataSource as IEventDispatcher;
eventSource.addEventListener( Event.COMPLETE, onMediaLoaded );
} else {
trace( "Synchronous media promise." );
readMediaData();
}
}
private function onMediaLoaded( event:Event ):void {
trace("Media load complete");
readMediaData();
}
private function readMediaData():void {
var imageBytes:ByteArray = new ByteArray();
dataSource.readBytes( imageBytes );
imageOrientation = getOrientation(imageBytes);
//saveImageFile(imageBytes);
//Saving this byteArray will save a big file with the EXIF in it.
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, onMediaPromiseLoaded);
loader.loadBytes(imageBytes);
}
private function onMediaPromiseLoaded(e:Event):void
{
trace("Media file loaded");
var base64Enc:Base64Encoder = new Base64Encoder();
var mpLoaderInfo:LoaderInfo = e.target as LoaderInfo;
mpLoaderInfo.removeEventListener(Event.COMPLETE, onMediaPromiseLoaded);
var scale:Number = 0.25;
var matrix:Matrix = new Matrix();
matrix.scale(scale, scale);
var jpgQuality:int = 80;
var bmd:BitmapData = new BitmapData(mpLoaderInfo.width*scale, mpLoaderInfo.height*scale);
bmd.draw(mpLoaderInfo.content,matrix,null,null,null,true);
var rotatedBMD:BitmapData = rotateBitmapData(bmd, imageOrientation);
var bytes:ByteArray = rotatedBMD.encode(new Rectangle(0,0, rotatedBMD.width , rotatedBMD.height), new JPEGEncoderOptions(jpgQuality), bytes);
saveImageFile(bytes); //this is the smaller file saved. it does not have EXIF data but you can write your own using AS3 eXIF class.
}
private function rotateBitmapData( bitmapData:BitmapData, degree:int = 0 ) :BitmapData
{
var newBitmap:BitmapData;
var matrix:Matrix = new Matrix();
matrix.rotate( degree * (Math.PI / 180) );
if ( degree == 90 ) {
newBitmap = new BitmapData( bitmapData.height, bitmapData.width, true );
matrix.translate( bitmapData.height, 0 );
} else if ( degree == -90 || degree == 270) {
newBitmap = new BitmapData( bitmapData.height, bitmapData.width, true );
matrix.translate( 0, bitmapData.width );
} else if ( degree == 180 ) {
newBitmap = new BitmapData( bitmapData.width, bitmapData.height, true );
matrix.translate( bitmapData.width, bitmapData.height );
}else if(degree == 0){
newBitmap = new BitmapData( bitmapData.width, bitmapData.height, true );
//matrix.translate( bitmapData.width, bitmapData.height );
}
newBitmap.draw( bitmapData, matrix, null, null, null, true )
return newBitmap;
}
private function saveImageFile(ba:ByteArray):void{
var now:Date = new Date();
var filename:String = "IMG" + now.fullYear + now.month + now.day + now.hours + now.minutes + now.seconds + ".jpg";
var temp:File = File.documentsDirectory.resolvePath( filename );
var stream:FileStream = new FileStream();
stream.open( temp, FileMode.WRITE );
stream.writeBytes( ba );
stream.close();
}
private function getOrientation(jpeg:ByteArray):int{
if (jpeg == null) {
return 0;
}
var offset:int = 0;
var length:int = 0;
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
var marker:int = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
trace("Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
if (length > 8) {
// Identify the byte order.
var tag:int = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
trace("Invalid byte order");
return 0;
}
var littleEndian:Boolean = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
var count:int = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
trace( "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
var orientation:int = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
}
trace( "Unsupported orientation");
return 0;
}
offset += 12;
length -= 12;
}
}
trace( "Orientation not found");
return 0;
}
private function pack(bytes:ByteArray,offset:int,length:int,
littleEndian:Boolean):int {
var step:int = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
var value:int = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
}
}
You can use Stage.deviceOrientation or Stage.orientation* to determine which way round the phone is.
*not sure if this one works on iOS
Is it the BitmapData result itself that you want to rotate (ie create a new BitmapData with rotated image) or just rotate a Bitmap on the display list?
Edit:
Ok, heres some code to rotate a BitmapData object:
function rotateBitmapData(angle:int, source:BitmapData):BitmapData
{
var newWidth:int = source.rect.width;
var newHeight:int = source.rect.height;
if (angle==90 || angle==270)
{
newWidth = source.rect.height;
newHeight = source.rect.width;
}
var newBmd:BitmapData = new BitmapData(newWidth, newHeight, source.transparent);
var tx:Number = 0;
var ty:Number = 0;
if (angle==90 || angle==180)
{
tx = newWidth;
}
if (angle==180 || angle==270)
{
ty = newHeight;
}
var matrix:Matrix = new Matrix();
matrix.createBox(1, 1, Math.PI*angle/180, tx, ty);
newBmd.draw(source, matrix);
return newBmd;
}
angle should be 0,90,180 or 270. It will return a new BitmapData object rotated by specified angle.
You can use StageOrientationEvent.ORIENTATION_CHANGING Event :
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, OrientationChangeHandler);
private function OrientationChangeHandler(e:StageOrientationEvent):void
{
switch (e.afterOrientation)
{
case StageOrientation.DEFAULT :
break;
case StageOrientation.ROTATED_RIGHT :
break;
case StageOrientation.ROTATED_LEFT :
break;
case StageOrientation.UPSIDE_DOWN :
break;
}
}
this can help you.

how to highlight user selected text within a piece of text which has already been highlighted?

I have a page where I am displaying some text in a div and I need to highlight this text in certain parts. I have done this by surrounding the text I need to highlight with a tag and appropriate css styling.
E.g.
<div>
My text will look like this with <span class="highlight">highlighted bits</span> in it.
</div>
This works fine. However, another requirement for this page is that the user must be able to select texts, click a button, and the selected text must be highlighted too.
The problem I have is when trying to identify the range of the selected text to grab (using window.getSelection.getRangeAt(0)), this gives me the range which resets after every <span> tag in the text, not from the beginning of the text.
For those who would like to know in the future this is how I did it:
jQuery.fn.highlight = function(startOffset,endOffset,type) {
function innerHighlight(node, startOffset,endOffset) {
var calledStartOffset = parseInt(startOffset);
var startOffsetNode=getChildNodeForOffset(node,parseInt(startOffset));
var endOffsetNode=getChildNodeForOffset(node,parseInt(endOffset));
startOffset = resizeOffsetForNode(startOffsetNode,parseInt(startOffset));
if (startOffsetNode == endOffsetNode){
endOffset = resizeOffsetForNode(endOffsetNode,parseInt(endOffset));
highlightSameNode(startOffsetNode, parseInt(startOffset),parseInt(endOffset),type,calledStartOffset);
} else {
highlightDifferentNode(startOffsetNode,endOffsetNode,parseInt(startOffset),parseInt(endOffset),type,calledStartOffset);
}
}
return this.each(function() {
innerHighlight(this, startOffset,endOffset);
});
};
function resizeOffsetForNode(offsetNode,offset){
if (offsetNode.id >= 0){
offset = parseInt(offset)-parseInt(offsetNode.id);
} else if (offsetNode.previousSibling != null && offsetNode.previousSibling.id > 0){
offset = parseInt(offset)-parseInt(offsetNode.previousSibling.id)-parseInt(offsetNode.previousSibling.textContent.length);
}
return offset;
}
function getChildNodeForOffset(testNode,offset) {
if (testNode.nodeType == 1 && testNode.childNodes && !/(script|style)/i.test(testNode.tagName)) {
var offsetNode=null;
var currentNode;
for (var i = 0; i < testNode.childNodes.length; ++i) {
currentNode=testNode.childNodes[i];
if (currentNode.id >= 0 && parseInt(currentNode.id) <= parseInt(offset) && ((parseInt(currentNode.id) + parseInt(currentNode.textContent.length)) >= parseInt(offset))){
offsetNode = currentNode;
break;
} else if (currentNode.id >= 0 && parseInt(currentNode.id) > parseInt(offset)){
offsetNode = currentNode.previousSibling;
break;
}
}
if (offsetNode==null){
offsetNode = testNode.childNodes[testNode.childNodes.length-1];
}
return offsetNode;
}
}
function highlightSameNode(node, startOffset,endOffset,type,calledStartOffset) {
var skip = 0;
if (node.nodeType == 3) {
if (startOffset >= 0) {
var spannode = document.createElement('span');
spannode.className = 'entity '+ type;
spannode.id=calledStartOffset;
var middlebit = node.splitText(startOffset);
var endbit = middlebit.splitText(endOffset-startOffset);
var middleclone = middlebit.cloneNode(true);
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
}
} else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
var childnode = node.childNodes[0];
highlightSameNode(childnode, startOffset,endOffset,type,calledStartOffset);
}
}
function highlightDifferentNode(startnode, endnode, startOffset,endOffset,type,calledStartOffset) {
var skip = 0;
if (startnode.nodeName == "#text") {
if (startOffset >= 0) {
var spannode = document.createElement('span');
spannode.className = 'entity '+ type;
spannode.id=calledStartOffset;
var endbit = node.splitText(startOffset);
var endclone = endbit.cloneNode(true);
spannode.appendChild(endclone);
endbit.parentNode.replaceChild(spannode, endbit);
}
} else if (startnode.nodeName == "SPAN") {
if (startOffset >= 0) {
var spannode = document.createElement('span');
spannode.className = 'entity '+ type;
spannode.id=calledStartOffset;
var endTextbit = startnode.childNodes[0].splitText(startOffset);
spannode.appendChild(endTextbit);
startnode.parentNode.insertBefore(spannode, startnode.nextSibling);
}
}
var currentTestNode=startnode.nextSibling;
while (currentTestNode!=endnode){
if (currentTestNode.nodeName == "#text") {
var spannode = document.createElement('span');
spannode.className = 'entity '+ type;
spannode.id=parseInt(currentTestNode.previousSibling.id)+parseInt(currentTestNode.previousSibling.textContent.length);
var currentNodeClone=currentTestNode.cloneNode(true);
spannode.appendChild(currentNodeClone);
endbit.parentNode.replaceChild(spannode, currentTestNode);
} else if (currentTestNode.nodeName == "SPAN") {
currentTestNode.className = 'entity overlap';
}
currentTestNode=currentTestNode.nextSibling;
}
var previousNodeEnd = parseInt(endnode.previousSibling.id)+parseInt(endnode.previousSibling.textContent.length);
var spannode = document.createElement('span');
spannode.className = 'entity '+ type;
spannode.id=previousNodeEnd;
if (endnode.nodeName == "#text") {
if (endOffset >= 0) {
//end offset here is the original end offset from the beginning of the text, not node
var unwantedbit = endnode.splitText(parseInt(endOffset)-parseInt(previousNodeEnd));
var endclone = endnode.cloneNode(true);
spannode.appendChild(endclone);
endnode.parentNode.replaceChild(spannode, endnode);
}
} else if (endnode.nodeName == "SPAN") {
if (endOffset >= 0) {
var wantTextbit = endnode.childNodes[0].splitText(parseInt(endOffset)-parseInt(previousNodeEnd));
spannode.appendChild(wantTextbit);
wantTextbit.parentNode.parentNode.insertBefore(spannode, endnode);
}
}
if (startnode.textContent.length < 1){
startnode.parentNode.removeChild(startnode);
}
if (endnode.textContent.length < 1){
endnode.parentNode.removeChild(endnode);
}
}
jQuery.fn.removeHighlight = function() {
return this.find("span.entity").each(function() {
this.parentNode.firstChild.nodeName;
with (this.parentNode) {
replaceChild(this.firstChild, this);
normalize();
}
}).end();
};
function contains(a, b){
return a.contains ? a != b && a.contains(b) : !!(a.compareDocumentPosition(b) & 16);
}