I've constructed a rather useful function to identify data-types; however, while coding happily I was rudely interrupted with a rather worrying dilemma.
As you may know, after calling .bind({foo:'bar'}) on a closure, you cannot access said foo property "externally"; however, inside the closure, this.foo works.
Also, when assigning something in such a way, you often face a throw: intermediary ... blah blah is undefined when you try access a property - directly after defining it. The code below fixes these issues but...
The problem is explained after the code:
"use strict";
if ('undefined' == typeof global)
{
Object.defineProperty
(
window,'global',
{
writable:false,
configurable:false,
enumerable:false,
value:window
}
);
}
Object.defineProperty
(
Function.prototype, 'wrap',
{
writable:false,
enumerable:false,
configurable:false,
value:function(jsob)
{
this.bind(jsob);
for (var i in jsob)
{ this[i] = jsob[i]; }
return this;
}
}
);
global.typeOf = function(data)
{
if ((data === null) || (data === undefined))
{ return 'void'; }
if ((data === true) || (data === false))
{ return 'bool'; }
var tpof = (({}).toString.call(data).match(/\s([a-zA-Z]+)/)[1].toLowerCase());
if ((tpof == 'array') || (tpof == 'htmlcollection') || (tpof == 'namednodemap'))
{ return 'list'; }
if ((tpof == 'global') || (tpof == 'window'))
{ return 'glob'; }
switch (tpof.substr(0,6))
{
case 'number': return 'unit';
case 'string': return (/[^\x20-\x7E\t\r\n]/.test(data) ? 'blob' : 'text');
case 'object': return 'jsob';
case 'functi': return 'func';
default: return 'node';
}
}
.wrap
({
list:'void bool unit text blob list jsob func node glob'.split(' '),
init:function()
{
this.list.forEach(function(item)
{
global[(item.toUpperCase())] = item;
global[('is'+(item[0].toUpperCase() + item.substr(1,item.length)))] = function(data)
{
return ((typeOf(data) == this.text) ? true : false);
}
.bind({text:item.toLowerCase()}); // <-- ISSUE
});
return this;
}
}).init();
So the little wrapper above takes care of such weirdness; however, have a look on the line where <-- ISSUE is; see, I cannot use wrap() there, I have to use bind(), else - inside the function - this is undefined!!
Let me clarify: If you use the entire code just as it is above in <script> tags inside a brand-spanking-new html file; just change that ISSUE line's bind word to: wrap; then try something like: isText("bite me!");
You will see an error that specifies something like:
cannot read property "text" from undefined ..
so; if you do a console.log(this) inside that function definition there; you will see undefined.
If anyone could help fixing this, or at least explain why this is happening, I'd really appreciate the input.
I see absolutely no purpose for this wrap function. In fact there's no reason to use this or bind at all for this use case. Just do
global.typeOf = function(data) {
if (data == null) return 'void';
switch (typeof data)
case "boolean": return 'bool';
case "number": return 'unit';
case "string": return /[^\x20-\x7E\t\r\n]/.test(data) ? 'blob' : 'text';
}
switch (Object.prototype.toString.call(data).slice(8, -1).toLowerCase()) {
case "array":
case "htmlcollection":
case "namednodemap": return 'list';
case "global":
case "window": return 'glob';
case "object": return 'jsob';
case "function": return 'func';
default: return 'node';
}
};
global.typeOf.list = 'void bool unit text blob list jsob func node glob'.split(' ');
global.typeOf.list.forEach(function(item) {
global[item.toUpperCase()] = item;
global['is'+item[0].toUpperCase()+item.slice(1)] = function(data) {
return typeOf(data) == item;
}
});
Related
Elegant way to return null. Don't want to return it twice.
Option 1:
function readSessionStorage(key) {
try {
if (typeof window !== 'undefined') {
return JSON.parse(window.sessionStorage.getItem(key));
}
return null;
} catch {
return null;
}
}
Option 2:
function readSessionStorage(key) {
try {
return JSON.parse(window.sessionStorage.getItem(key));
} catch {
return null;
}
}
Option 3:
If we pick this option, why should we do this?
function readSessionStorage(key) {
try {
if (typeof window !== 'undefined') {
return JSON.parse(window.sessionStorage.getItem(key));
}
} catch {}
return null;
}
Why do I need to do this?
I'm getting DOMException if I try to get window.sessionStorage, and hence I need to use try...catch.
function readSessionStorage(key) {
if (typeof window !== 'undefined' || !window.sessionStorage) {
return JSON.parse(window.sessionStorage.getItem(key));
}
return null;
}
Original Code:
function readSessionStorage(key) {
if (typeof window !== 'undefined') {
return JSON.parse(window.sessionStorage.getItem(key));
}
return null;
}
Well, that's odd. No response in seven months? Well, I was looking for something like this myself, and couldn't find an eloquent solution. I created my own.
Edit: upon further work, I found that the null safety operators do exist in JS! Yay! The function below is still quite helpful, perhaps more so now since one can use the safety operator to get to the precise value desired, then handle it correctly.
Consider using a function similar to this:
const defineCheck = (item, defaultVal = '') => {
try {
return (item !== null && item !== undefined) ? item : defaultVal;
} catch {
return defaultVal;
}
}
This might not exactly fit your needs, but you can make your function however you want. This will keep the clutter out of your important business or UI logic, so you and others can focus on what is important.
Sample usage (if account is known to have an expected value):
tempForm.ExpirationDate = defineCheck(account.expirationDate);
tempForm.LicenseType = defineCheck(account.licenseType, 'Nada');
With null safety:
tempForm.ExpirationDate = defineCheck(account?.license?.expireDate);
tempForm.LicenseType = defineCheck(account?.license?.licenseType, 'Nada');
I hope you and others find this useful, or better yet, share a much better way to deal with this problem. I'd hoped the C# null safety check operator made its way to JS, but no luck (e.g., "account?.license?.licenseType ?? 'Nada'")
I'm using apps script/ES5. I have:
function returnText(message) {
switch(message.indexOf(mykey)!== -1) {
case true:
var mykey = 'ziptest'
break;
case true:
var mykey = 'setnum'
break;
default:
console.log('default');
}
}
function returnText1() {
returnText('ziptest 19886991201')
}
returnText1();
It defaults to the default option. How can I fix this so that it selects the option where:
var mykey = 'ziptest'
You're misunderstanding how a switch statement works. The code in a JavaScript function is largely step-by-step, in order from beginning to end. The condition in switch () is evaluated once, when that point of the code is reached.
If you want to check for two different strings in message, although you could use a switch, it would be inappropriate and confusing. Instead, use if/else if/else:
function returnText(message) {
if (message.indexOf("ziptest")!== -1) {
return "ziptest";
} else if (message.indexOf("setnum") !== -1) {
return "setnum";
} else {
return "default";
}
}
(Since each branch returns, you don't actually need else there, but...)
In a comment you've said:
right now I have only 3 options but I might want to add 5-6 more later
In that case, use a loop. If you could use ES2015+ features, I'd use find:
const strings = ["ziptest", "setnum", /*...*/];
function returnText(message) {
return strings.find(str => message.indexOf(str) !== -1) || "default";
}
find returns the first entry for which the callback returns a truthy value (and stops looping), or undefined if it runs out of entries without the callback returning a truthy value. So with the above, we check each str returning true if the string exists in message. If find returns undefined, the || "default" kicks in and supplies the default string instead.
But since you can't, you can use some as TheMaster shows, or just a simple loop:
var strings = ["ziptest", "setnum", /*...*/];
function returnText(message) {
for (var i = 0; i < strings.length; ++i) {
var str = strings[i];
if (message.indexOf(str) !== -1) {
return str;
}
}
return "default";
}
Side note: I see you've tagged this google-apps-script, so you're probably stuck with .indexOf(str) !== -1. But I hear they're updating it, so you might have .includes(str) now.
You should probably use arrays, if you have many keys.
var myKeys = ['ziptest', 'setnum'];
function returnText(message) {
var selectKey;
myKeys.some(function(key){
if(message.indexOf(key) !== -1) {
selectKey = key;
return true;
}
})
return selectKey;
}
function returnText1() {
return returnText('ziptest 19886991201')
}
console.log(returnText1());
If you really want to use switch then you can do something like this.
function returnText(message) {
switch (true) {
case message.indexOf('ziptest') > -1:
console.log('ziptest');
break;
case message.indexOf('setnum') > -1:
console.log('setnum');
break;
default:
console.log('default');
}
}
function returnText1() {
returnText('ziptest 19886991201')
}
returnText1();
For some reason, objects that have been returned from the server end of a Google Apps Script project have any member functions replaced by null. Here's some sample code demonstrating this:
server.gs
function A() {
this.a = 'a string';
this.toString = function() { return this.a; }
this.innerObj = { b : "B", toString : function(){ return 'inner object'; } }
}
function getA() { return new A(); }
clientJS.html; /* or console, if you prefer... */
google.script.run.withSuccessHandler(console.log).getA();
Object, when printed raw, looks something like this:
{ "a": "a string", "toString": null, "innerObj": { "b": "B", "toString": null } }
Live demo of the problem
what can I do about this?!
This is by design, as noted in the documentation
Legal parameters and return values are JavaScript primitives like a Number, Boolean, String, or null, as well as JavaScript objects and arrays that are composed of primitives, objects and arrays. [...] Requests fail if you attempt to pass a Date, Function, DOM element besides a form, or other prohibited type, including prohibited types inside objects or arrays.
As a workaround, you can stringify an object with its methods:
JSON.stringify(obj, function(key, value) {
if (typeof value === 'function') {
return value.toString();
} else {
return value;
}
});
and then reconstruct the functions from strings on the receiving side.
My answer extends Desire's answer. I was able to get this to work, by stringifying the member functions, but for reconstruction, instead of use eval(), I used these:
function shouldBeFunction(str)
{
str = str.toString().trim();
// str should *not* be function iff it doesn't start with 'function'
if (str.indexOf('function') !== 0) return false;
// str should *not* be function iff it doesn't have a '(' and a ')'
if ((str.indexOf('(') === -1) || (str.indexOf(')') === -1)) return false;
// str should *not* be function iff it doesn't have a '{' and a '}'
if ((str.indexOf('{') === -1) || (str.indexOf('}') === -1)) return false;
return true;
}
var myObjectWithFunctions = JSON.parse(objectWithStringsAsFunctions,
function (key, value) {
var DEBUG = false;
if ((typeof(value) === 'string') && (shouldBeFunction(value))) {
if (DEBUG) {
console.log('function string detected on property named : ' + key);
console.log('function text: " ' + value + '"');
}
// get arguments list, if there is one to get
var argsList = value.substring(value.indexOf('(') + 1, value.indexOf(')')).trim();
if (DEBUG) console.log('argsList == ' + argsList);
// get function body
var functionBody = value.substring(value.indexOf('{') + 1, value.lastIndexOf('}')).trim();
if (DEBUG) console.log('functionBody == ' + functionBody);
if (argsList)
return new Function(argsList, functionBody);
return new Function(functionBody);
}
return value;
}
);
The reason being that I don't know if eval() is evil, or a sign of bad programming practice.
UPDATE: I learned that eval() may be OK if the strings came from the server and are being turned back into functions on the client-side
I have a server response that sends back an object like so:
{
success: integer
}
On the client, I have
return body && body.success;
the problem is that the integer might be zero, in which case the above would return body instead of body.success.
What is the best shorthand I can use to always return the value?
You should always initialize your property in the back end. I would set as "-1".
function ajaxCall() {
return { success: true, id: 10 };
//return null;
}
function GetData() {
var myData = ajaxCall();
if (typeof myData === "undefined"
|| myData == null
|| typeof myData.success === "undefined"
|| myData.success == null) {
return { success: false, id: 0 };
}
return myData;
}
var body = GetData();
// init
(function(){
document.getElementById("MyMessage").innerHTML = ((body.success) ? "Success - " + body.id : "Failed") ;
})();
<div id="MyMessage"></div>
Would this work:
return body && (typeof body.success !== "undefined" ? body.success : false);
The parenthesis may be incorrect, but this should check is body defined? If so, is body.success defined? If not return body.
If body.success is defined, return body.success. If not, return body.
It could be written this way:
If (body)
return (typeof body.success !== "undefined" ? body.success : body);
else return false;
I have a method that takes a language abbreviation and matches it using a .constant dictionary, and returns the matching language name.
How can I do an evaluation with .filter to check whether the passed isoCode/language abbreviation exists?
Here is my method:
angular.module('portalDashboardApp')
.service('ISOtoLanguageService', ['Languages', function(Languages) {
this.returnLanguage = function(isoCode) {
var categoryObject = Languages.filter(function ( categoryObject ) {
return categoryObject.code === isoCode;
})[0];
return categoryObject.name;
};
}]);
Here is the method with some error catching I have tried:
angular.module('portalDashboardApp')
.service('ISOtoLanguageService', ['Languages', function(Languages) {
this.returnLanguage = function(isoCode) {
var categoryObject = Languages.filter(function (categoryObject) {
if (isoCode != null || isoCode != undefined) {
return categoryObject.code === isoCode;
}
else {
return categoryObject.code === 'und';
}
})[0];
if (categoryObject.name != undefined || categoryObject.name != null) {
return categoryObject.name;
}
else {
return "undefined";
}
};
}]);
Thank you!
I would recommend you organize your data at Languagesin an object or map, it'll be much faster and simpler when you fetch your translation by an abbreviation. A short example:
angular.module('portalDashboardApp')
.factory('Languages', function(){
var dictionary = {
ISO: {name: 'International Organization for Standardization'}
};
return {
get: function(abbr){
return dict[abbr];
}
};
}).service('ISOtoLanguageService', ['Languages', function(Languages) {
this.returnLanguage = function(isoCode) {
if(!isoCode) {
return "Answer for empty isoCode";
}
var categoryObject = Languages.get(isoCode);
return (categoryObject || {}).name || "I don't know this abbr";
};
}]);
I'm not sure that this JS works without any syntax error (I've not try to launch it) but idea is that you don't need array and filter on big dictionaries and you are able to get any abbreviation from dict with O(1) complexity even with huge dictionary.
If you don't want to have a refactoring with your code you can do something like this:
angular.module('portalDashboardApp')
.service('ISOtoLanguageService', ['Languages', function(Languages) {
this.returnLanguage = function(isoCode) {
if (!isoCode) {
return;
}
var resultAbbrs = Languages.filter(function (categoryObject) {
return categoryObject.code === isoCode;
});
if (resultAbbrs.length > 0) {
return resultAbbrs[0].name;
}
};
}]);
In this case if isoCode is null, undefined or empty string or this key is not found in dictionary return undefined will be by default. Outside you should check a result of this function with if (result === undefined) ...
I hope it helped you)