How to improve this if else structure in JavaScript? - javascript

How to improve the following if-else structure in JavaScript?
if(isIE())
if(termin != "")
TargetElement.onclick = function() {merkzettelRemove(this, id, termin)};
else
TargetElement.onclick = function() {merkzettelRemove(this, id)};
else
if(termin != "")
TargetElement.setAttribute("onclick","merkzettelRemove(this, " + id + ",
'" + termin + "')");
else
TargetElement.setAttribute("onclick","merkzettelRemove(this, " + id + ")");

// get a cross-browser function for adding events
var on = (function(){
if (window.addEventListener) {
return function(target, type, listener){
target.addEventListener(type, listener, false);
};
}
else {
return function(object, sEvent, fpNotify){
object.attachEvent("on" + sEvent, fpNotify);
};
}
}());
// add the event listener
on(TargetElement, "click", function(){
// if termin is empty we pass undefined, this is the same as not passing it at all
merkzettelRemove(this, id, (termin) ? termin : undefined);
});

First things first, put some more braces in. It'll make it clearer what's going on, as well as saving untold heartache when you come to edit this in future.
Yes, you can get away without the braces if they wrap a single statement. When you come along three months from now and add something else into one of those blocks, your code will break unless you remember to wrap the whole block in braces. Get into the habit of doing it from the beginning.

First, use { } everywhere, it will make your loops more readable.
Second: I think this does the same
if(isIE()) {
TargetElement.onclick = function() {
merkzettelRemove(this, id, termin || null);
};
} else {
TargetElement.setAttribute("onclick",
"merkzettelRemove(this, " + id + ",'" + termin || null + "')");
}
Third, but you could try using unobtrusive javascript to add handlers to TargetElement

Firstly, lose the browser-sniffing. onclick= function... works everywhere; setAttribute is not only broken in IE, but also ugly. JavaScript-in-a-string is a big code smell; avoid. setAttribute on HTML attributes has IE problems and is less readable than simple DOM Level 1 HTML properties anyway; avoid.
Secondly, it would be ideal to make merkzettelRemove accept an out-of-band value (null, undefined, or even '' itself) as well as an omitted argument. It is possible it already allows undefined, depending on what mechanism it is using to support optional arguments. If so you could say simply:
TargetElement.onclick= function() {
merkzettelRemove(this, id, termin || undefined);
};
If you must completely omit the argument and you don't want to use an if...else, there's another way around although IMO the clarity is worse:
TargetElement.onclick= function() {
merkzettelRemove.apply(null, termin==''? [this, id] : [this, id, termin]);
};

I think using a Javascript framework is the best solution, but you can try something like this:
var src="merkzettelRemove(this, " + id + (termin && (",'" + termin + "'")) + ")";
if (isIE()) {
TargetElement.onclick = new Function(src);
} else {
TargetElement.setAttribute("onclick", src);
}

I know this is not an answer for your question, but if you don't have any reason for not doing so you should definitely use a library that cares about compatibility issues for you, such as jQuery. Then you could write something like this:
var el = $(TargetElement);
if (termin != "")
el.click(function() {merkzettelRemove(this, id, termin)});
else
el.click(function() {merkzettelRemove(this, id)});

how about using short circuiting operators. So the following code
if(A)
{
if(B) {
C; }
else{
D;}
else{
if(E)
F;
else
G;
}
}
Will become
A && ((B&&C) || D || ((E&&F) || G));

Related

How to replace jQuery $.trim()?

I'm using jQuery in a website has a polyfill for the built-in String.trim(). Site used to run in IE 8 a lot and needed the polyfill, but it doesn't anymore. Unfortunately I can't remove the polyfill from the page -- I don't have permissions to touch that and there is no possible way for me to remove it -- so this bit of code runs before anything I can control:
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
Then jQuery comes along and does this, not realizing that the native String.trim has already by messed with:
// Use native String.trim function wherever possible
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
function( text ) {
return text == null ?
"" :
core_trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
Up to now this hasn't really been much of a problem, but I'm using the Datatables plugin for jQuery and it has many places where it calls $.trim() on data that isn't a string, like number or arrays. The native code in IE 11 and Chrome (the browsers we target) knows to just return the value of $.trim(6), but the polyfill doesn't.
I tried redefining the the prototype with a function that should work:
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
} else {
return this.valueOf(this);
}
}
But that didn't work because jQuery has already extend using the polyfill and further changes to the prototype won't change what jQuery is using.
I tried following this thread to redefine $.trim(), but that didn't work.
Is there a way to return String.prototype.trim() to its native code?
Is there a way to redefine $.trim()?
Some other idea I haven't thought of?
You can override jQuery core methods
(function(){
var string = " TRIM ME ";
console.log(jQuery.trim(string));
// Define overriding method.
jQuery.trim = function(string){
return string;
}
console.log(jQuery.trim(string));
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Simply override jQuery's $.trim() using String.prototype.trim(), then override String.prototype.trim() with your function:
var trimText = " Some text ";
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
$.trim = function(string) {
return String.prototype.trim(string);
}
trimText = trimText.trim();
console.log(trimText);
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
Based on Jack Bashford's answer I came up with this.
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
} else {
return this.valueOf(this);
}
};
$.trim = function(e){
return String.prototype.trim.call(e);
};
Part of the original problem was that I needed to fix it so that if $.trim(number) or $.trim(array) was called it wouldn't throw and error.

Override JQuery Selector to support Dollar Sign ($) in element id

I am trying to write a JQuery plugin to support $ in element id, my code is:
DollarSignPlugin.js:
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
var jQueryInit = $.fn.init;
$.fn.init = function(arg1, arg2, rootjQuery){
arg2 = arg2 || window.document;
if (arg1 && arg1.replace) {
var newArg1 = escapeJQueryElementName(arg1);
return new jQueryInit(newArg1, arg2, rootjQuery);
}
return new jQueryInit(arg1, arg2, rootjQuery);
};
It is working great, but i faced one issue with current line:
var $anyselector = $("#");
JQuery throw error "Syntax error, unrecognized expression: #".
This line is from bootstrap when clicking on any tab with href="#".
This error doesn't appear when removing my plugin, also if i copy paste the replace function to directly the jquery file it works fine.
So is there a better way to override the selector or i have some issue with my code, please help?
I would strongly recommend you don't do this (but if you want to, keep reading, I do have a fix below), because:
It means you're using invalid selectors in your code, which is a maintenance issue — at some point, someone doing maintenance won't understand what's going on, or you'll hit an edge condition, etc.
Your current implementation will mess up things like
$("<div>$12</div>").appendTo(document.body);
(by putting a backslash in front of the dollar sign). Who knows what else it's messing up?
Instead, I'd just have a simple:
function $gid(id) {
var e = document.getElementById(id);
if (!e) {
return $();
}
if (e.id === id) {
return $(e);
}
// Fallback processing for old IE that matches name attributes
return $('[id="' + id.replace(/"/g, '\\"') + '"]').filter(function() {
return this.id === id;
}).first();
}
Example:
// Plugin
function $gid(id) {
var e = document.getElementById(id);
if (!e) {
return $();
}
if (e.id === id) {
return $(e);
}
// Fallback processing for old IE that matches name attributes
return $('[id="' + id.replace(/"/g, '\\"') + '"]').filter(function() {
return this.id === id;
}).first();
}
// Use
$gid("a$b").css("color", "blue");
$gid("$c").css("color", "green");
<div id="a$b">a$b</div>
<div id="$c">$c</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
However: If you're going to do it, you have to be careful to tread as softly as possible. That means calling the original with exactly the same number of arguments, the same thisvalue, etc.; and making sure that $.fn.init.prototype has the same value after you replace $.fn.init as before.
Doing both of those things seems to solve the problem:
// Plugin
(function () {
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
var slice = Array.prototype.slice;
var jQueryInit = $.fn.init;
$.fn.init = function (arg1) {
var args = slice.call(arguments, 0);
if (arg1 && arg1.replace) {
args[0] = escapeJQueryElementName(arg1);
}
return jQueryInit.apply(this, args);
};
$.fn.init.prototype = $.fn;
})();
// Use
$("#a$b").css("color", "blue"); // Using special version that handles $ without \\
$("#$c").css("color", "green"); // Using special version that handles $ without \\
$("<div>Checking '#' selector, should get 0: " + $("#").length + "</div>").appendTo(document.body);
<div id="a$b">a$b</div>
<div id="$c">$c</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Instead of trying to attack this problem by overriding something that should really only be used internally, a nicer solution is a function that will create a new alias of jQuery with support for escaping selectors.
Btw, keep in mind that escaping should not be done for things that are HTML, e.g. $('<span>give me $$$</span>'), so I've made a crude check for that.
(function(jQuery) {
jQuery.withSelectorEscaping = function() {
function escapeJQueryElementName(elementName) {
elementName = elementName.replace(/\\\$/g, "$");
return elementName.replace(/\$/g, "\\$");
}
return function(selector, context) {
// avoid doing this for HTML
if (selector && selector.replace && selector[0] !== '<') {
selector = escapeJQueryElementName(selector);
}
return jQuery(selector, context);
};
}
}(jQuery));
// create new alias here
$ = jQuery.withSelectorEscaping();
// use new alias
console.log($('#'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This code snippet adds a new function to jQuery itself called jQuery.withEscaping; it returns a function which you can then assign to an alias of your choosing, I picked $ here but you could also do:
$foo = jQuery.withSelectorEscaping();
It leaves the behaviour of jQuery itself alone and only exposes your escaping feature for things that need it.

how to create methods in Javascript and OOP

I'm trying to create an object in JavaScript and I'm following Mozilla's tutorial . the tutorial works just fine, but when I apply that technique to my code it doesn't work. (I'm doing something wrong but I don't see it). I coded all my methods and I don't get any errors, I initialize my object and I don't get any errors either, I even call my methods and I don't get errors, but the return value is a string with my code instead of the value that I'm expecting
function JavaScriptObj(id, datatype) {
function initialize(id, datatype) {
if (typeof id === 'number' && id > 1) {
this.theID = id;
} else {
console.error("ERROR: JavaScriptObj.initialize" + id + "is NOT a valid argument");
}
if (typeof datatype === 'string') {
this.data_type = datatype;
} else {
console.error("ERROR: JavaScriptObj.initialize" + datatype + "is NOT a valid argument");
}
}
}
JavaScriptObj.prototype.getSectionName = function(){
var SectionName = "section-" + this.theID;
return SectionName;
};
var person2 = new JavaScriptObj(2, "texteditor");
alert(person2.getSectionName);
this is my jsfiddle
thanks in advance! :-)
Remove the initialize nested function:
function JavaScriptObj(id, datatype) {
if (typeof id === 'number' && id > 1) {
this.theID = id;
} else {
console.error("ERROR: JavaScriptObj: " + id + "is NOT a valid argument");
}
if (typeof datatype === 'string') {
this.data_type = datatype;
} else {
console.error("ERROR: JavaScriptObj: " + datatype + "is NOT a valid argument");
}
}
JavaScriptObj.prototype.getSectionName = function(){
var SectionName = "section-" + this.theID;
return SectionName;
};
var person2 = new JavaScriptObj(2, "texteditor");
alert(person2.getSectionName()); // need to call it too
It looks like you're not actually executing/calling your method. In order to call your method, you need to append parenthesis to the call:
alert(person2.getSectionName());
Small aside -- using console.log() instead of alert() tends to save you a few keystrokes and makes development a bit faster. Also, alert() is a blocking call that stops all other code execution on the page. While that won't make a difference when you're first starting out, it could potentially be a pain point down the road as your javascript ninja skills increase. :)

Pass html object through string into function

What I have is kinda unusual I guess. I have this function deleteItem which is triggered onclick and has the following parameters
function dItem(type,id,element,confirmed){
if(confirmed){
handle delete function
}else{
var c = ',';
popup('Are you sure you want to delete this item?',
{
"Yes":"dItem('"+type+"'"+c+id+c+element+c+true+")",
"Cancel":"popupClose()"
}
)
}
}
.. onclick='dItem("comment",15,this,false)' ..
In popup()'s second parameter are passed the buttons that are to be displayed in the popup and the functions they call respectively. The problem is that element is a HTMLDIV element and I cannot figure out a neat way to pass that through a string. The only solution I could come to think of is to have a global variable holding the element in question and not passing it at all, although I don't really want to do that since it's more of a hack rather than a solution. Does anybody have any idea how I can pass that element through a string? Thanks in advance!
EDIT:
This is how the buttons object b is being processed and turned into HTML. Do you see how I can supply it with an actual function instead of just a name in the form of string?
var _b = '';
for(var i in b){
_b+="<div onclick='"+b[i]+"'>"+i+"</div>";
}
It's more common to handle this situation with callbacks. You will need to alter your popup function for that to work.
Example:
popup('Are you sure you want to delete this item?', {
"Yes": function () {
dItem(type, id, element, confirmed);
},
"Cancel": function () {
popupClose();
}
});
As a workaround you could simply generate an unique ID for the element and use that to identify the element later on. Because your function is recursive you need to deal with the fact that element can be either a ELEMENT_NODE or a string.
for(var i in b){
var generatedId = i /* TODO: not sure this generates an unique id */;
_b += "<div id='" + generatedId + "' onclick='" + b[i] + "'>" + i + "</div>";
}
function dItem (type, id, element, confirmed) {
if (confirmed) {
// handle delete function
}else{
var elementId;
// find the elementId
if (element && element.nodeType && element.nodeType == 1) {
elementId = element.id;
}else if (typeof element == 'string') {
elementId = element
}else{
throw Error('Argument [element] is not a ELEMENT_NODE or string');
}
var args = [type, id, elementId, true];
popup('Are you sure you want to delete this item?', {
"Yes": "dItem(" + args.join(', ') + ")",
"Cancel": "popupClose()"
});
}
}

Does JavaScript have an equivalent way to implement c# style auto properties without massive overhead?

I'm curious if JavaScript has a way to bind a function when a property is changed without an enormous overhead like watching all the auto-properties with a timer, but without setting them via a function call. For example, I know you could do something like:
var c = new (function () {
this.Prop = 'test';
this.Prop_get = function (value) {
return('Prop = ' + this.Prop);
};
this.Prop_set = function (value) {
if (value != 'no') {
this.Prop = value;
}
};
})();
document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('no');
document.write(c.Prop_get());
document.write('<BR />');
c.Prop_set('yes');
document.write(c.Prop_get());
document.write('<BR />');
But I'm looking for some way to allow the following to produce the same result:
document.write(c.Prop);
document.write('<BR />');
c.Prop = 'no';
document.write(c.Prop);
document.write('<BR />');
c.Prop = 'yes';
document.write(c.Prop);
document.write('<BR />');
With any changes to the pseudoclass other than adding a timer to watch the Prop property for changes or similarly high-overhead solutions.
Any solution to this issue comes down to what it is that you need to support.
If you require IE6-IE8, it's probably more sane to resort to timers or horrible abuse of the DOM to make changes to hidden DOM objects, which will fire listenable events, etc...
There are a few blogs which have talked about their efforts to squeeze these browsers into conformity with some kind of mutation-aware library.
Results and caveats vary.
If you're talking about ES5-compliant browsers, most support "get" and "set" keywords directly inside of objects.
This might lead to even cleaner constructors/interfaces than C#, because constructors can be as simple as var a = {};, but you also get the magic-methods, rather than the Java list of getX, getY, z, and the headache of trying to remember what's a method and what's a property, when you get to the interface.
Seriously, this is kinda pretty:
var person = {
person_name : "Bob",
get name () { return this.person_name; },
set name (value) {
console.log("But my parents named me " + this.person_name + "!");
}
};
person.name;
person.name = "Mark";
But there's an issue here: person.person_name isn't private at all.
Anybody could swoop in and change that.
Not to fret -- get and set don't actually have to operate on properties of the object.
var Person = function (name, age) {
// we don't need to save these; closures mean they'll be remembered as arguments
// I'm saving them as `private_*` to illustrate
var private_name = name,
private_age = age;
var public_interface = {
get name () { return private_name; },
set name (value) { console.log("Nope!"); },
get age () { return private_age; },
set age (value) { console.log("Nope!"); },
set court_appointed_name (value) {
console.log("If I must...");
private_name = value;
}
};
return public_interface;
};
var mark = Person("Mark", 32);
mark.name; // "Mark";
mark.name = "Bubba"; // log: "Nope!";
mark.name; // "Mark";
mark.court_appointed_name = "Jim-Bob"; // log: "If I must..."
mark.name; // "Jim-Bob"
You could also force assignments to pass in objects, with auth-tokens, et cetera.
mark.name = {
value : "Jimmy Hoffa",
requested_by : system.user.id,
auth : system.user.auth.token
};
This is all fantastic, isn't it?
Why aren't we doing it?
Browser support.
Problem is this requires brand new syntax: all objects are defined as key-value pairs.
Messing with the syntax means any non-supporting browser will crash and burn unless you wrap your entire program in a try/catch (which is performance-suicide).
You could do one try-catch test, and lazy-load the awesome interface, versus fugly workarounds, at page-load, which is the right way to go about it, but now you're developing two versions of the application.
Or three versions, as case may be (new browsers, intermediate-browsers like FF3, and hacks for Ghetto_IE).
Intermediate browsers used {}.__defineGetter__ and {}.__defineSetter__.
Object.prototype.defineProperty (/.defineProperties) are the methods which instill hope of IE compatibility, until you realize that older versions of IE only supported the mutations on DOM objects (attached to the actual DOM tree), hence the headaches. Hooray.
I found the solution to this after coming across this link relating to getters and setters. Here is a generic method of applying properties to objects I put together as a result if anyone is interested in it:
Object.prototype.Property = function (name, fn) {
if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
};
function C() {
var _Field = 'test';
this.Property('Field', {
get: function () {
return ('Field = ' + _Field);
},
set: function (value) {
if (value != 'no') {
_Field = value;
}
}
});
};
C.prototype.constructor = C;
var c = new C();
document.write(c.Field);
document.write('<BR />');
c.Field = 'no';
document.write(c.Field);
document.write('<BR />');
c.Field = 'yes';
document.write(c.Field);
document.write('<BR />');
Edit: A JQuery-friendly Object.prototype.Property function like the above:
Object.defineProperty(Object.prototype, 'Property', {
enumerable: false,
value: function (name, fn) {
if (fn.hasOwnProperty('get')) { this.__defineGetter__(name, fn.get); }
else { this.__defineGetter__(name, function () { throw ('Cannot read property ' + name + '.'); }); }
if (fn.hasOwnProperty('set')) { this.__defineSetter__(name, fn.set); }
else { this.__defineSetter__(name, function () { throw ('Cannot write property ' + name + '.'); }); }
}
});
And a working JSFiddle.

Categories