When are you supposed to use escape instead of encodeURI / encodeURIComponent?
escape()
Don't use it!escape()
is defined in section B.2.1.1 escape and the introduction text of Annex B says:
... All of the language features and behaviours specified in this annex have one or more undesirable characteristics and in the absence of legacy usage would be removed from this specification. ...
... Programmers should not use or assume the existence of these features and behaviours when writing new ECMAScript code....
Behaviour:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape
Special characters are encoded with the exception of: @*_+-./
The hexadecimal form for characters, whose code unit value is 0xFF or less, is a two-digit escape sequence: %xx
.
For characters with a greater code unit, the four-digit format %uxxxx
is used. This is not allowed within a query string (as defined in RFC3986):
query = *( pchar / "/" / "?" )
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded = "%" HEXDIG HEXDIG
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
A percent sign is only allowed if it is directly followed by two hexdigits, percent followed by u
is not allowed.
encodeURI()
Use encodeURI when you want a working URL. Make this call:
encodeURI("http://www.example.org/a file with spaces.html")
to get:
http://www.example.org/a%20file%20with%20spaces.html
Don't call encodeURIComponent since it would destroy the URL and return
http%3A%2F%2Fwww.example.org%2Fa%20file%20with%20spaces.html
Note that encodeURI, like encodeURIComponent, does not escape the ' character.
encodeURIComponent()
Use encodeURIComponent when you want to encode the value of a URL parameter.
var p1 = encodeURIComponent("http://example.org/?a=12&b=55")
Then you may create the URL you need:
var url = "http://example.net/?param1=" + p1 + "¶m2=99";
And you will get this complete URL:
http://example.net/?param1=http%3A%2F%2Fexample.org%2F%Ffa%3D12%26b%3D55¶m2=99
Note that encodeURIComponent does not escape the '
character. A common bug is to use it to create html attributes such as href='MyUrl'
, which could suffer an injection bug. If you are constructing html from strings, either use "
instead of '
for attribute quotes, or add an extra layer of encoding ('
can be encoded as %27).
For more information on this type of encoding you can check: http://en.wikipedia.org/wiki/Percent-encoding
Converting between strings and ArrayBuffers
Update 2016 - five years on there are now new methods in the specs (see support below) to convert between strings and typed arrays using proper encoding.
TextEncoder
The TextEncoder
represents:
The
TextEncoder
interface represents an encoder for a specific method,
that is a specific character encoding, likeutf-8
,An encoder takes a stream of code points as input andiso-8859-2
,koi8
,
cp1261
,gbk
, ...
emits a stream of bytes.
Change note since the above was written: (ibid.)
Note: Firefox, Chrome and Opera used to have support for encoding
types other than utf-8 (such as utf-16, iso-8859-2, koi8, cp1261, and
gbk). As of Firefox 48 [...], Chrome 54 [...] and Opera 41, no
other encoding types are available other than utf-8, in order to match
the spec.*
*) Updated specs (W3) and here (whatwg).
After creating an instance of the TextEncoder
it will take a string and encode it using a given encoding parameter:
if (!("TextEncoder" in window)) alert("Sorry, this browser does not support TextEncoder...");
var enc = new TextEncoder(); // always utf-8console.log(enc.encode("This is a string converted to a Uint8Array"));
What is the shortest function for reading a cookie by name in JavaScript?
Shorter, more reliable and more performant than the current best-voted answer:
const getCookieValue = (name) => (
document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''
)
A performance comparison of various approaches is shown here:
https://jsben.ch/AhMN6
Some notes on approach:
The regex approach is not only the fastest in most browsers, it yields the shortest function as well. Additionally it should be pointed out that according to the official spec (RFC 2109), the space after the semicolon which separates cookies in the document.cookie is optional and an argument could be made that it should not be relied upon. Additionally, whitespace is allowed before and after the equals sign (=) and an argument could be made that this potential whitespace should be factored into any reliable document.cookie parser. The regex above accounts for both of the above whitespace conditions.
Adding a parameter to the URL with JavaScript
A basic implementation which you'll need to adapt would look something like this:
function insertParam(key, value) {
key = encodeURIComponent(key);
value = encodeURIComponent(value);
// kvp looks like ['key1=value1', 'key2=value2', ...]
var kvp = document.location.search.substr(1).split('&');
let i=0;
for(; i<kvp.length; i++){
if (kvp[i].startsWith(key + '=')) {
let pair = kvp[i].split('=');
pair[1] = value;
kvp[i] = pair.join('=');
break;
}
}
if(i >= kvp.length){
kvp[kvp.length] = [key,value].join('=');
}
// can return this or...
let params = kvp.join('&');
// reload page with new params
document.location.search = params;
}
This is approximately twice as fast as a regex or search based solution, but that depends completely on the length of the querystring and the index of any match
the slow regex method I benchmarked against for completions sake (approx +150% slower)
function insertParam2(key,value)
{
key = encodeURIComponent(key); value = encodeURIComponent(value);
var s = document.location.search;
var kvp = key+"="+value;
var r = new RegExp("(&|\\?)"+key+"=[^\&]*");
s = s.replace(r,"$1"+kvp);
if(!RegExp.$1) {s += (s.length>0 ? '&' : '?') + kvp;};
//again, do what you will here
document.location.search = s;
}
Enabling 4-digit pincode autofill using cookies
For cookies I use my "simpleCookie" object with the set / getVal methods to read or save a cookie.
ex:
simpleCookie.setVal( 'my cookie', 'ab/kjf;c', 3 )
let valueX = simpleCookie.getVal('my cookie')) // return 'ab/kjf;c'
simpleCookie.setVal( 'my cookie', '', -1) remove the cookie
this object is achieved via an IIEF function, and I strongly advise you to use the mozilla documentation
Since automatic form validation exists, I no longer use a text box to indicate an input error, but I have diverted its "normal" use a bit because I find it very restrictive, as you will see in my code.
When at the base of your question, you just have to find a match between the name entered and a possible cookie with the same name, then save this cookie if the form is valid.
Oh, and I also put some css to simplify writing html (no more need for <br>
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login form</title>
<style>
fieldset { margin: 1em; width: 15em; }
fieldset * { display: block; float: left; clear: both; margin-top: 0.2em; }
fieldset label { margin-top: 0.7em; }
fieldset button { margin-top: 2em; }
fieldset button:last-of-type { clear: none; float: right; }
</style>
</head>
<body>
<form id="login-form" action="">
<fieldset>
<label>Name:</label>
<input type="text" name="name" autocomplete="off" pattern="[A-Za-z0-9]{1,20}">
<label>Password:</label>
<input type="password" name="pass" pattern="[A-Za-z0-9]{1,20}">
<label>4-digit PIN:</label>
<input type="text" name="pinCode" autocomplete="off" pattern="[0-9]{4}">
<button type="reset">clear</button>
<button type="submit">Log In</button>
</fieldset>
</form>
<script src="simpleCoolie.js"></script> <!-- the cookie object (IIFE) -->
<script src="login_form.js"></script>
</body>
</html>
simpleCoolie.js :
// the cookie object (IIFE)
const simpleCookie = (function()
{
const OneDay_ms = 24 *60 *60 *1000 // one Day in milliseconds
return {
setVal:(cName, cValue='', exDays=10)=> // 10 days is default cookie recovery,
{ // negative value remove the cookie
cName = encodeURIComponent(cName.trim())
cValue = encodeURIComponent(cValue.trim())
if (cName)
{
let dte = new Date()
dte.setTime(dte.getTime() + (exDays *OneDay_ms))
document.cookie = `${cName}=${cValue};expires=${dte.toUTCString()};SameSite=Strict;path=/`
} }
, getVal:cName=>
{
cName = encodeURIComponent(cName.trim())
let xCookie = document.cookie.split('; ').find(x=>x.startsWith(`${cName}=`))
return xCookie ? decodeURIComponent(xCookie.split('=')[1]) : ''
} }
})()
login_form.js :
const formLogin = document.getElementById('login-form')
, msgErrorDuration = 5000
, checkInputs =
[...formLogin.querySelectorAll('input[pattern]')]
.map(el=>
{
let pattern = el.pattern
el.removeAttribute('pattern')
return { name:el.name, pattern }
});
// generic set checking for report validyty
const getCheckingValidity=(formElement, patternValue)=>
{
formElement.pattern = patternValue
formElement.required = true
return formElement.reportValidity()
}
// generic checking remove after delay
const unCheckElement=(formElement,isOK)=>
{
formElement.removeAttribute('pattern')
formElement.required = false
if(!isOK)
{
formElement.setCustomValidity('')
if(document.activeElement === formElement ) // bugg fix: Firefox doesnt remove error message after delay
{ // (other browser do)
formElement.blur(); // double flip focus
formElement.focus(); // --> will remove message bubble
}
}
}
// client-side form validation mecanism to get error message for each input
formLogin.name.oninvalid=_=>
{
formLogin.name.setCustomValidity('Please enter a name')
setTimeout(unCheckElement, msgErrorDuration, formLogin.name, false)
}
formLogin.pass.oninvalid=_=>
{
formLogin.pass.setCustomValidity("can't do anything without password !")
setTimeout(unCheckElement, msgErrorDuration, formLogin.pass, false)
}
formLogin.pinCode.oninvalid=_=>
{
if (formLogin.pinCode.value==='')
{ formLogin.pinCode.setCustomValidity("PIN code can't be empty !") }
else
{ formLogin.pinCode.setCustomValidity('PIN code must be 4 digits') }
setTimeout(unCheckElement, msgErrorDuration, formLogin.pinCode, false)
}
formLogin.onsubmit=e=>
{
let validForm = true
for (let Elm of checkInputs)
{
validForm = validForm && getCheckingValidity(formLogin[Elm.name], Elm.pattern )
if (validForm)
{ unCheckElement(formLogin[Elm.name], true) }
else break
}
if (validForm)
{ simpleCookie.setVal( formLogin.name.value, formLogin.pinCode.value ) }
else
{ e.preventDefault() } // disable form submiting
}
formLogin.name.oninput=()=> // check for cookie pin code on name
{
formLogin.pinCode.value = simpleCookie.getVal(formLogin.name.value)
}
In 2009, session/localStorage arrived, which can replace cookies, especially for this kind of use.
To not have to redo all the previous logic, I created here a module called pseudoCookie which actually uses localStorage
here is the complete code to test with it:
// the pseudo cookie object (IIFE)
const pseudoCookie = (function() // use localStorage
{
return {
setVal:(cName, cValue='', exDays=10)=> // negative value remove the value in localstorage
{ // the values are kept until your browser or your system crashes
cName = encodeURIComponent(cName.trim())
cValue = encodeURIComponent(cValue.trim())
if (cName) {
if (exDays < 0) localStorage.removeItem(cName)
else localStorage.setItem(cName, cValue)
} }
, getVal:cName=>
{
cName = encodeURIComponent(cName.trim())
let xCookie = localStorage.getItem(cName)
return xCookie ? decodeURIComponent(xCookie) : ''
} }
})()
and the part to change in JS:
formLogin.onsubmit=e=>
{
let validForm = true
for (let Elm of checkInputs)
{
validForm = validForm && getCheckingValidity(formLogin[Elm.name], Elm.pattern )
if (validForm)
{ unCheckElement(formLogin[Elm.name], true) }
else break
}
if (validForm)
{ pseudoCookie.setVal( formLogin.name.value, formLogin.pinCode.value ) }
else
{ e.preventDefault() } // disable form submiting
}
formLogin.name.oninput=()=> // check for cookie pin code on name
{
formLogin.pinCode.value = pseudoCookie.getVal(formLogin.name.value)
}
Related Topics
Calling Node.Js Script from Rails App Using Execjs
Javascript:For Loop with Timeout
Rake Db:Create - Could Not Find a JavaScript Runtime
Programming Language Independent Model Validation
"Cannot Use Import Statement Outside a Module" Error When Importing React-Hook-Mousetrap in Next.Js
Array.Prototype.Splice in Ruby
Meteor: Calling an Asynchronous Function Inside a Meteor.Method and Returning the Result
How to Access Constants in the Lib/Constants.Js File in Meteor
Slow Assets Compilation in Development Mode
How to Plug a JavaScript Engine with Ruby and Nokogiri
How to Make Cross-Domain Ajax Calls to Google Maps API
JavaScript for "Add to Home Screen" on Iphone
Can Nokogiri Interpret JavaScript? - Web Scraping
How to Change the HTML Content as It's Loading on the Page
How to Update Window.Location.Hash Without Jumping the Document