New line characters in text area increases text length in C# - javascript

I have this problem in my asp.net mvc application.
In one of my model there is a field "Description". The database column for this fields is set to NVarchar(300).
In my view I am creating a a text area as following.
#Html.TextAreaFor(m => m.Description, new { maxlength = "300" })
I am using "jquery.validate.unobtrusive.min.js" for client side validation. So when user types in the textarea and content length goes more than 300 characters it displays message "Please enter no more than 300 characaters."
Everything works fine till the following sceanario comes.
User enters following data in the text area.
f
f
f
f
f
f
f
f
sdfa
(this content has 8 new lines)
According to "unobtrusive" validation this content has length 300 (counting each new line "\n" as a single character) so the validation passes and page posts back.
In my C# code, due to Encoding, the same content becomes fo length 308 (Counting each new line "\r\n" as 2 characters) which in tern fails the data base operation as it only allows 300 characters.
If someone is saying that I should have StringLength attribute on this particular property, I have the following reason for not having it.
If I put this attribute the client-side validation does not happen for this particular property, it goes to the server and since the model is not valid it comes back to the page with error message.
Please advise me what could be the possible solution for this?

After taking a closer look at the solution by #Chris, I found that this would cause an endless loop for any control other than textarea with the #maxlength attribute. Furthermore, I found that using value (=the value of the textarea passed into the validator) would already have the leading and trailing line breaks cut off, which means the database operation still failed when it tried to save text containing those line breaks.
So here is my solution:
(function ($) {
if ($.validator) {
//get the reference to the original function into a local variable
var _getLength = $.validator.prototype.getLength;
//overwrite existing getLength of validator
$.validator.prototype.getLength = function (value, element) {
//double count line breaks for textareas only
if (element.nodeName.toLowerCase() === 'textarea') {
//Counts all the newline characters (\r = return for macs, \r\n for Windows, \n for Linux/unix)
var newLineCharacterRegexMatch = /\r?\n|\r/g;
//use [element.value] rather than [value] since I found that the value passed in does cut off leading and trailing line breaks.
if (element.value) {
//count newline characters
var regexResult = element.value.match(newLineCharacterRegexMatch);
var newLineCount = regexResult ? regexResult.length : 0;
//replace newline characters with nothing
var replacedValue = element.value.replace(newLineCharacterRegexMatch, "");
//return the length of text without newline characters + doubled newline character count
return replacedValue.length + (newLineCount * 2);
} else {
return 0;
}
}
//call the original function reference with apply
return _getLength.apply(this, arguments);
};
}
})(jQuery);
I tested this in Chrome and a few IE versions and it worked fine for me.

You can change the behavior for getLength in client validation to double count newlines by adding the following to your javascript after you've included jquery.validate.js. This will cause the server-side and client-side length methods to match letting you use the StringLength attribute (I assume your issue with StringLength was that the server and client validation methods differed).
$.validator.prototype._getLength = $.validator.prototype.getLength;
$.validator.prototype.getLength = function (value, element) {
// Double count newlines in a textarea because they'll be turned into \r\n by the server.
if (element.nodeName.toLowerCase() === 'textarea')
return value.length + value.split('\n').length - 1;
return this._getLength(value, element);
};

Related

How to escape apostrophe successfully in jQuery on iPhone that has Smart Punctuation

I haven't been able to successfully escape Apostrophe on iPhone. After some research, it seems the Smart Punctation feature is causing some issues here. I've tried everything I can find and nothing has worked.
A user is entering text into a field and I verify if this text is correct. Here is my jQuery code. The valid Text is EMPORER'S EYE or Emporer's Eye, but neither ever comes up as valid on iPhone.
$("#actionButton").click(function() {
var seats = $("#number2").val();
var apostrophe = '\u0027';
var error = null;
if ((seats === "EMPORER\'S EYE") || (seats === 'Emporer\'s Eye')) {
$("#message").fadeIn();
$("#draggable").fadeOut();
$("#draggable2").fadeOut();
} else {
$("#messageWrong").fadeIn().delay(2500).fadeOut();
$("#draggable").fadeOut().delay(2500).fadeIn();
$("#draggable2").fadeOut().delay(2500).fadeIn();
}
// If you really need setflag:
var setflag = error != null;
});
Here is a codepen I have of the entire function and process I'm trying to put together.
https://codepen.io/MaxwellR/pen/BaYGPJL
In order to make user experience less frustrating, I would skip on checking the special characters, as this is a slippery slope.
You can convert both input and expected value to certain format that allows some degree of liberty in how people spell things. Consider this example:
const sanitizeString = string => string
.trim() // remove surrounding whitespace
.toLowerCase() // ignore the case
.replaceAll(/[^\p{L}\s]/gu, '') // cut out all special characters
.replaceAll(/\s+/g, ' '); // convert any consecutive whitespace to a space
This can be a good balance between being correct and having some freedom to spell things differently.
const string = 'Emporer\'s Eye';
const sanitizedString = sanitizeString(string);
// sanitizedString is now "emporers eye"
In your case, you could have a single value inside the condition and it would work:
if (sanitizedString(seats) === sanitizedString('Emporer\'s Eye')) {
// some magic
}

count string length including 'invisible characters' like \r \n in javascript and php

I am trying to display a "x characters left" text next to a textarea, to see how much still can be written, with javascript / jquery:
var area = 'textarea#zoomcomment';
var c = $(area).val().length;
Then the input is validated with laravel's max validation.
'comment' => 'required|max:3000'
The javascript does not count the \n or \r characters, but PHP/laravel does. Is there a javascript function to count everything , including linebreaks etc ? I'd like to have the same string length with js and php.
edit:
it seems, that javascript/jquery counts \n\r as 1 and php strlen() counts it as 2 characters. I tried to avoid jquery, but it still seemed to count wrong :(
var c = document.getElementById('zoomcomment').value.length;
alert(c);
this question has a good answer
You could do it using html function which gives you innerhtml -
var area = 'textarea#zoomcomment';
var c = $(area).html().length;

\s RegEx not capturing new line data

I am trying to clean up input and put it into a desired way. Basically, we have serialnumbers that are entered several different ways - enter delimited (newline), space, comma, etc.
My problem in my code below in testing is that new line delimited isn't working. According to w3schools and 2 other sites:
The \s metacharacter is used to find a whitespace character.
A whitespace character can be:
-A space character
-A tab character
-A carriage return character
-A new line character
-A vertical tab character
-A form feed character
This should mean that I can catch basically any new line. In Netsuite, the user is entering the value as:
SN1SN2SN3
I want this to change to "SN1,SN2,SN3,". Currently the \s RegEx is not picking up the newline? Any help would be appreciated.
**For the record - while I am using Netsuite (CRM) to get the input, the rest of this code is typical javascript and regex work. This is why I am using all 3 tags - netsuite, js, and regex
function fixSerailNumberString(s_serialNum){
var cleanString = '';
var regExSpace = new RegExp('\\s',"g");
if(regExSpace.test(s_serialNum)){
var a_splitSN = s_serialNum.split(regExSpace);
for(var i = 0; i < a_splitSN.length;i++){
if(a_splitSN[i].length!=0){
cleanString = cleanString + a_splitSN[i]+((a_splitSN[i].split(',').length>1)?'':',');
}
}
return cleanString;
}
else{
alert("No cleaning needed");
return s_serialNum;
}
}
EDITS:
1-I need to handle both if it has spaces (such as "sn1, sn2, sn3" needs to become "sn1,sn2,sn3") and this newline issue. What I have above works for the spaces.
2- I am not sure if it matters, but the field is a textarea. Does that impact this?
#Cheery found why this was happening. As I said, I got the data from Netsuite and was using the API to get the data. In the UI of Netsuite this data did look like each line was on a new line, however, when doing a console.log the values were not.
Example:
UI displayed:
sn1
sn2
sn3
Console.log displayed:
sn1sn2sn3
I was assuming the UI translated into the actual value and didn't think to check what the string was.
NetSuite multi-select fields (like the Serial Numbers transaction column) usually return all selected values as a single string, as you've noted with "sn1sn2sn3"; however, each of these values is actually separated by a non-printing character \x05. Try .split(/\x05/).join(',')

Chrome counts characters wrong in textarea with maxlength attribute

Here is an example:
$(function() {
$('#test').change(function() {
$('#length').html($('#test').val().length)
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id=test maxlength=10></textarea>
length = <span id=length>0</span>
Fill textarea with lines (one character at one line) until browser allows.
When you finish, leave textarea, and js code will calculate characters too.
So in my case I could enter only 7 characters (including whitespaces) before chrome stopped me. Although value of maxlength attribute is 10:
Here's how to get your javascript code to match the amount of characters the browser believes is in the textarea:
http://jsfiddle.net/FjXgA/53/
$(function () {
$('#test').keyup(function () {
var x = $('#test').val();
var newLines = x.match(/(\r\n|\n|\r)/g);
var addition = 0;
if (newLines != null) {
addition = newLines.length;
}
$('#length').html(x.length + addition);
})
})
Basically you just count the total line breaks in the textbox and add 1 to the character count for each one.
Your carriage returns are considered 2 characters each when it comes to maxlength.
1\r\n
1\r\n
1\r\n
1
But it seems that the javascript only could one of the \r\n (I am not sure which one) which only adds up to 7.
It seems like the right method, based on Pointy's answer above, is to count all new lines as two characters. That will standardize it across browsers and match what will get sent when it's posted.
So we could follow the spec and replace all occurrences of a Carriage Return not followed by a New Line, and all New Lines not followed by a Carriage Return, with a Carriage Return - Line Feed pair.
var len = $('#test').val().replace(/\r(?!\n)|\n(?!\r)/g, "\r\n").length;
Then use that variable to display the length of the textarea value, or limit it, and so on.
For reasons unknown, jQuery always converts all newlines in the value of a <textarea> to a single character. That is, if the browser gives it \r\n for a newline, jQuery makes sure it's just \n in the return value of .val().
Chrome and Firefox both count the length of <textarea> tags the same way for the purposes of "maxlength".
However, the HTTP spec insists that newlines be represented as \r\n. Thus, jQuery, webkit, and Firefox all get this wrong.
The upshot is that "maxlength" on <textarea> tags is pretty much useless if your server-side code really has a fixed maximum size for a field value.
edit — at this point (late 2014) it looks like Chrome (38) behaves correctly. Firefox (33) however still doesn't count each hard return as 2 characters.
It looks like that javascript is considering length of new line character also.
Try using:
var x = $('#test').val();
x = x.replace(/(\r\n|\n|\r)/g,"");
$('#length').html(x.length);
I used it in your fiddle and it was working. Hope this helps.
That is because an new line is actually 2 bytes, and therefore 2 long. JavaScript doesn't see it that way and therefore it will count only 1, making the total of 7 (3 new lines)
Here's a more universal solution, which overrides the jQuery 'val' function. Will be making this issue into a blog post shortly and linking here.
var originalVal = $.fn.val;
$.fn.val = function (value) {
if (typeof value == 'undefined') {
// Getter
if ($(this).is("textarea")) {
return originalVal.call(this)
.replace(/\r\n/g, '\n') // reduce all \r\n to \n
.replace(/\r/g, '\n') // reduce all \r to \n (we shouldn't really need this line. this is for paranoia!)
.replace(/\n/g, '\r\n'); // expand all \n to \r\n
// this two-step approach allows us to not accidentally catch a perfect \r\n
// and turn it into a \r\r\n, which wouldn't help anything.
}
return originalVal.call(this);
}
else {
// Setter
return originalVal.call(this, value);
}
};
If you want to get remaining content length of text area then you can use match on the string containing the line breaks.
HTML:
<textarea id="content" rows="5" cols="15" maxlength="250"></textarea>
JS:
var getContentWidthWithNextLine = function(){
return 250 - content.length + (content.match(/\n/g)||[]).length;
}
var value = $('#textarea').val();
var numberOfLineBreaks = (value.match(/\n/g)||[]).length;
$('#textarea').attr("maxlength",500+numberOfLineBreaks);
works perfectly on google already in IE have to avoid the script! In IE the 'break-line' is counted only once, so avoid this solution in IE!
Textareas are still not fully in sync among browsers. I noticed 2 major problems: Carriage returns and Character encodings
Carriage return
By default are manipulated as 2 characters \r\n (Windows style).
The problem is that Chrome and Firefox will count it as one character. You can also select it to observe there is an invisivle character selected as a space.
A workaround is found here:
var length = $.trim($(this).val()).split(" ").join("").split('\n').join('').length;
Jquery word counts when user type line break
Internet explorer on the other hand will count it as 2 characters.
Their representation is :
Binary: 00001101 00001010
Hex: 0D0A
, and are represented in UTF-8 as 2 characters and counted for maxlength as 2 characters.
The HTML entities can be
1) Created from javascript code:
<textarea id='txa'></textarea>
document.getElementById("txa").value = String.fromCharCode(13, 10);
2) Parsed from the content of the textarea:
Ansi code:
<textarea>Line one.
Line two.</textarea>
3) Inserted from keyboard Enter key
4) Defined as the multiline content of the textbox
<textarea>Line one.
Line two.</textarea>
Character Encoding
Character encoding of an input field like textarea is independent than the character encoding of the page. This is important if you plan to count the bytes. So, if you have a meta header to define ANSI encoding of your page (with 1 byte per character), the content of your textbox is still UTF-8 with 2 bytes per character.
A workaround for the character encoding is provided here:
function htmlEncode(value){
// Create a in-memory div, set its inner text (which jQuery automatically encodes)
// Then grab the encoded contents back out. The div never exists on the page.
return $('<div/>').text(value).html();
}
function htmlDecode(value){
return $('<div/>').html(value).text();
}
HTML-encoding lost when attribute read from input field

Javascript text divide by tabs problem (multiline-cells)

I have a strange issue in my Web Page, specifically with a text area element that obtains the clipboard from the user.
The user perform a CTRL+V; and I created a event to get the data with the event KeyUp.
...this works fine...
But, when I try to divide by each "row" of this textarea; start the problems...
The input can be like this example:
The data reads something like that:
Row1[0][HT]Row1[1][LF]"Row2[0] Comment line 1[LF]Row2[0] Comment line 2"[HT]Row2[1]
Where:
[HT] means {Tab}
[LF] means {New line}
I use:
var myData = document.getElementById("TextAreaElement").value;
var vArray = myData.split(/\n/);
But this array return me 3 lines...
Somebody knows any solution or alternative way?
You get a text containing three lines, split it on the line breaks and get an array with three items. Seems like it works. :) Now, what you need to do, is take each of these items and split them on a tab ( = \t)
[edit]
Nope, now I see what you mean. You won't get there by using this splitting. You'll have to parse the string. A field value can contain an enter, in which case it will be enclosed in double quotes. So you'll have to parse the string and don't split it on a break when you're still within a set of quotes.
Regarding the problem of '.' not matching newlines, the standard method of doing that in JS is [\S\s] which will match anything.
It looks like all you want to do for starters is split the string by tabs, right? Then...
result = string.split(/\t/)
Then you'll have an array with each of the rows' data separate. Note that this only works if your data can't have extra erroneous tabs in it.
Whatever tool is getting the information into the clipboard should really do some escaping before it is copied out and parsed by your JS. If it can't do that, then really anything goes - you can't in that case guarantee that your string won't have double-quotes, tabs, or any other character you might try to use as a delimiter.
Well, I don't find the way to work with some regular expression or a javascript method (I believe that can do it). I worked a different way to split the info.
I used AJAX to send this information to the server and perform the split in VB.
In resume:
I get the max columns (split by tabs).
Get and evaluate each value of the array of tabs.
If start with double quotes, tried to find the end of the double quotes (before that, replaced the mid double quotes with a unique text)
Every time that evaluated an item of the original Array, I deleted each item (Always evaluate the item 0)...
If find a new line (final of the row), only removed the text of the "final column" of the previous row.
I hope help to someone with the same problem. Cheers.
Here is the code:
Public Function TEST(ByVal pText As String) As String
Try
Dim vText As String = pText
Dim vArray As New ArrayList
vArray.AddRange(vText.Split(vbNewLine))
Dim vActualIndex As Integer = 0
Dim vMaxColumns As Integer = 0
For Each vArrayItem In vArray
If vArrayItem.Split(vbTab).Length > vMaxColumns Then
vMaxColumns = vArrayItem.Split(vbTab).Length
End If
Next
Dim vActualArray(vMaxColumns - 1) As String
vArray = New ArrayList
vArray.AddRange(vText.Split(vbTab))
Dim vLen As Integer = vArray.Count
Dim vNewArray As New ArrayList
vActualIndex = 0
Do While vArray.Count <> 0
If vArray(0).Split(vbNewLine).Length = 1 Then
vActualArray(vActualIndex) = vArray(0)
vActualIndex += 1
Else
If vArray(0).Split(vbNewLine)(0).ToString.StartsWith("""") Then
vArray(0) = Mid(vArray(0), 2).Replace("""""", "*_IDIDIDUNIQUEID_*")
If InStr(vArray(0), """" & vbNewLine) <> 0 Then
vActualArray(vActualIndex) = Mid(vArray(0), 1, InStr(vArray(0), """" & vbNewLine) + 1)
vArray(0) = Mid(vArray(0), InStr(vArray(0), """" & vbNewLine) + 3)
vActualArray(vActualIndex) = vActualArray(vActualIndex).ToString.Replace("*_IDIDIDUNIQUEID_*", """""")
vArray(0) = vArray(0).ToString.Replace("*_IDIDIDUNIQUEID_*", """""")
vActualIndex += 1
GoTo Skip_remove
End If
vArray(0) = vArray(0).ToString.Replace("*_IDIDIDUNIQUEID_*", """""")
vActualArray(vActualIndex) = vArray(0)
vActualIndex += 1
Else
vActualArray(vActualIndex) = vArray(0).Split(vbNewLine)(0)
vActualIndex += 1
vArray(0) = vArray(0).ToString.Substring(vArray(0).Split(vbNewLine)(0).ToString.Length + 2)
GoTo Skip_remove
End If
End If
vArray.RemoveAt(0)
' This is a label in VB code
Skip_remove:
If vActualIndex >= vMaxColumns Then
vNewArray.Add(vActualArray)
ReDim vActualArray(vMaxColumns - 1)
vActualIndex = 0
End If
Loop
Catch ex As Exception
Return ""
End Try
End Function

Categories