Simplify / elegantize this code? - javascript

Here's a bit of code which works fine but I think could be simplified / shortened. It's basically clicking on a list item, getting it's id and then showing / hiding / removing elements based on the id.
Suggestions on how to simplify this with a function or loop?
$("#btn_remove_event_type").click(function() {
var selectedId = $(".selected-type").attr("id");
if (selectedId == "temperature_event") {
$("#poplist_temp").show();
$(".temperature-params").hide();
$("#temperature_event").remove();
} else if (selectedId == "load_event") {
$("#poplist_load").show();
$(".load-params").hide();
$("#load_event").remove();
} else if (selectedId == "price_event") {
$("#poplist_price").show();
$(".price-params").hide();
$("#price_event").remove();
} else if (selectedId == "duty_event") {
$("#poplist_duty").show();
$(".duty-params").hide();
$("#duty_event").remove();
} else {
$("#poplist_program").show();
$(".program-params").hide();
$("#program_event").remove();
}
});

Something like this:
$("#btn_remove_event_type").click(function() {
var selectedId = $(".selected-type").attr("id");
switch(selectedId){
case "temperature_event":
$("#poplist_temp").show();
$(".temperature-params").hide();
$("#temperature_event").remove();
break;
case "load_event":
$("#poplist_load").show();
$(".load-params").hide();
$("#load_event").remove();
break;
case "price_event":
$("#poplist_price").show();
$(".price-params").hide();
$("#price_event").remove();
break;
case "duty_event"):
$("#poplist_duty").show();
$(".duty-params").hide();
$("#duty_event").remove();
break;
default:
$("#poplist_program").show();
$(".program-params").hide();
$("#program_event").remove();
}
});
Or even better:
$("#btn_remove_event_type").click(function() {
// Get id and split on "_"
var selectedId = $(".selected-type").attr("id").split("_");
// Set array of accepted input
var options = ["temperature","load","price","duty"];
// Handle default
if(options.indexOf(selectedId[0]) == -1){
selectedId = ["program"];
}
$("#poplist_" + selectedId[0]).show();
$("."+selectedId[0] + "-params").hide();
$("#"+selectedId[0] + "_event").remove();
});

For simplicity sake I would change the id of the buttons clicked to one word(tempeature,load,price,duty). You'll have to do some renaming of your reactionary ids/classes, but just looks cleaner to me. I also flipped some of your naming around to stay consistent.
You can switch it around to match your style, it just bothered me having selectors like $("#"+id+"-params").hide();
$("#btn_remove_event_type").click(function() {
var selectedId = $(".selected-type").attr("id");
var nonDefault = ["temperature","load","price","duty"];
if(nonDefault.indexOf(selectedId) > -1){
$("#poplist_"+selectedId).show();
$(".params-"+selectedId).hide();
$("#event_"+selectedId).remove();
} else {
$("#poplist_program").show();
$(".params-program").hide();
$("#event_program").remove();
}
});

I ended up changing my classes to ids and reordering them, so I ended up with this solution which doesn't require any if statements.
$("#btn_remove_event_type").click(function() {
var selectedId = $(".selected-type").attr("id").split("_");
$("#" + selectedId[0] + "_pop").show();
$("#" + selectedId[0] + "_params").hide();
$("#" + selectedId[0] + "_event").remove();
});

If you don't want to rely on the ID names matching other element names, you can use an object as a map to specify actions.
//using a map lets you easily add or remove cases without duplicating code, and doesn't require you to use standardized names for your elements and events.
var action_map = {temperature_event:{show:"#poplist_temp",
hide:".temperature-params",
remove:"#temperature_event"
},
load_event:{show:"#poplist_load",
hide:".load-params",
remove:"#load_event",
},
price_event:{show:"#poplist_price",
hide:".price-params",
remove:"#price_event",
},
duty_event:{show:"#poplist_duty",
hide:".duty-params",
remove:"#duty_event"
},
default_event:{show:"#poplist_program",
hide:".program-params",
remove:"#duty_event",
}
};
$("#btn_remove_event_type").click(function() {
var selectedId = $(".selected-type").attr("id");
if (!action_map[selectedId])
selectedId = "default_event";
$(action_map[selectedId].show).show();
$(action_map[selectedId].hide).hide();
$(action_map[selectedId].remove).remove();
}
});

Personally the cleanest and most configurable way i can see is using a map and looping of the methods. This is generic as different mappings can have different methods. You can even extend this further so you can have a method which will construct the mappings for you.. i.e. addNewType([{methodName:selector}, etc..]).
var configuration = {
temperature_event: {
show: "#poplist_temp",
hide: ".temperature-params",
remove: "#temperature_event"
},
load_event: {
show: "#poplist_load",
hide: ".load-params",
remove: "#load_event"
}
// etc etc.
};
$("#btn_remove_event_type").click(function() {
var fn, selector, typeConfig = $(".selected-type").attr("id");
for (fn in typeConfig)
$(typeConfig[fn])[fn]();
});
});

Related

How to optimize this javascript duplicates

I wrote this code, but since I'm just starting to learn JS, can't figure out the best way to optimize this code. So made a duplicates for every if statement.
$(function() {
var lang = $(".lang input[type='checkbox']");
var gender = $(".gender input[type='checkbox']");
if(lang.length == lang.filter(":checked").length){
$('.lang').hide();
$('.lang-all').click(function(){
$('.lang-all').hide();
$('.lang').slideToggle(200);
});
} else {
$('.lang').show();
$('.lang-all').hide();
}
if(gender.length == gender.filter(":checked").length){
$('.gender').hide();
$('.gender-all').click(function(){
$('.gender-all').hide();
$('.gender').slideToggle(200);
});
} else {
$('.gender').show();
$('.gender-all').hide();
}
});
So this is my code, as you can see on line 15 if(gender... I have a duplicate of previous code, just changed variable from "lang" to "gender". Since I have more that two variables, I don't want to make duplicate of code for every each of them, so I hope there is a solution to optimize it.
You can write a function to let your code more abstract, see:
function isChecked(obj, jq1, jq2){
if(obj.length == obj.filter(":checked").length){
jq1.hide();
jq2.click(function(){
jq2.hide();
jq1.slideToggle(200);
});
} else {
jq1.show();
jq2.hide();
}
}
//Your jQuery code, more abstract
$(function() {
var lang = $(".lang input[type='checkbox']");
var gender = $(".gender input[type='checkbox']");
isChecked(lang, $('.lang'), $('.lang-all'));
isChecked(gender, $('.gender'), $('.gender-all'));
});
make a function which had similar functionality, then pass a parameter as a class or id
$(function() {
call('.lang');
call('.gender');
function call(langp){
var lang = $(langp+" input[type='checkbox']");
if(lang.length == lang.filter(":checked").length){
$(langp).hide();
$(langp+'-all').click(function(){
$(langp+'-all').hide();
$(langp).slideToggle(200);
});
} else {
$(langp).show();
$(langp+'-all').hide();
}
}
});

jstree Enable a node and its children

I am using:
jstree("disable_node", "#" + NodeID);
to disable a node in jstree. and using:
jstree("enable_node", "#" + NodeID);
to enable a node.
Is there a simple way to disable/enable a node and it's children?
thank you
You can do it with the code as below. Check demo - Fiddle.
Write a recursive function to iterate multi-level structures
function changeStatus(node_id, changeTo) {
var node = $("#tree").jstree().get_node(node_id);
if (changeTo === 'enable') {
$("#tree").jstree().enable_node(node);
node.children.forEach(function(child_id) {
changeStatus(child_id, changeTo);
})
} else {
$("#tree").jstree().disable_node(node);
node.children.forEach(function(child_id) {
changeStatus(child_id, changeTo);
})
}
}
Call function depending on what you need
changeStatus(NodeID, 'enable');
or
changeStatus(NodeID, 'disable');
I write a simple JS function based on jstree documentation about get_node function and jstree JSON data format that enable/disable the input node and all it's child in any level:
NodeToggleEnable = function (node_id, enable) {
var tree = $("#jstree-locations");
var sub_tree = [node_id.toString()];
var index = 0;
while (index < children.length) {
var child = tree.jstree("get_node", "#" + children[index]).children;
sub_tree = sub_tree.concat(child);
if (enable == false)
tree.jstree("disable_node", "#" + sub_tree[index]);
else
tree.jstree("enable_node", "#" + sub_tree[index]);
index++;
}
}
this function uses children(array of strings or objects) property of node that selected by get_node function.

Transfer javascript to jQuery for the carousel

I am trying to convert the JavaScript to jQuery for my carousel.
The code I had is:
document.querySelectorAll('.indicators')[0].style.color = 'red';
document.querySelector('.toggler-prev').style.display = 'none';
document.addEventListener('postchange', function(event){
document.querySelectorAll('.indicators')[event.lastActiveIndex].style.color = 'white';
document.querySelectorAll('.indicators')[event.activeIndex].style.color = 'red';
var togglerPrev = document.querySelector('.toggler-prev');
var togglerNext = document.querySelector('.toggler-next');
if (event.activeIndex === 3) {
togglerPrev.style.display = 'block';
togglerNext.style.display = 'none';
} else if (event.activeIndex === 0) {
togglerNext.style.display = 'block';
togglerPrev.style.display = 'none';
} else {
togglerNext.style.display = 'block';
togglerPrev.style.display = 'block';
}
});
Which I transferred to jQuery:
$('.indicators').css('color','#000');
$('.toggler-prev').css('display','none');
$(window).on('postchange', function(event){
$('.indicators')[event.lastActiveIndex].css('color','#FFF');
$('.indicators')[event.activeIndex].css('color','#000');
var togglerPrev = $('.toggler-prev');
var togglerNext = $('.toggler-next');
if (event.activeIndex === 3) {
togglerPrev.css('display','block');
togglerNext.css('display','none');
} else if (event.activeIndex === 0) {
togglerNext.css('display','block');
togglerPrev.css('display','none');
} else {
togglerNext.css('display','block');
togglerPrev.css('display','block');
}
});
But it isn't working as I expected for example togglers are not being hidden and color of indicators not being changed.
This is the working JS example: http://codepen.io/anon/pen/eJYWQO
And my jQuery alternative: http://codepen.io/anon/pen/eJYWrq
Can anybody help me with editing my second codepen? Thanks in advance.
You forgot to mention somewhere that you are using AngularJS and onsenUI ;)
Your problem is, that the jQuery event does not have the properties activeIndex and lastActiveIndex, but they can be found in event.originalEvent.
Also, like #stalin said in his answer, your indicators are no jQuery objects, therefore you can't call the css-function on them. Wrap this stuff in another jQuery constructor like $() or even better, use the eq-function which returns a jQuery wrapped object from an array.
Then, to optimize some of the readability, you might want to consider the hide and show function instead of changing the CSS property display.
All in all your code should look like this
$('.indicators').css('color', '#fff');
$('.indicators').eq(0).css('color', '#000'); // highlight the first indicator
$('.toggler-prev').hide();
$(window).on('postchange', function(event) {
//store the indices for readability
var activeIndex = event.originalEvent.activeIndex;
var lastActiveIndex = event.originalEvent.lastActiveIndex;
$('.indicators').eq(lastActiveIndex).css('color', '#FFF');
$('.indicators').eq(activeIndex).css('color', '#000');
var togglerPrev = $('.toggler-prev');
var togglerNext = $('.toggler-next');
if (activeIndex === carousel.getCarouselItemCount() - 1) {
togglerPrev.show();
togglerNext.hide();
} else if (activeIndex === 0) {
togglerNext.show();
togglerPrev.hide();
} else {
togglerNext.show();
togglerPrev.show();
}
});
You have several problems in your implementation
First the event is not trigered in $(window) is in the $(document)
Your variable like lastActiveIndex are not in the event object are in event.originalEvent
$('.indicators')[event.lastActiveIndex] return a dom object not a jquery object you need to wrap this result in a jquery object to use the css() function
After you fix all this your code will work
Sorry that don't have time for create a fiddle for you :)

Modular way to use jQuery selectors

Is there a way to use jQuery selectors in modular way:
var logo = $('.logo', function() {
function show () {
console.log('logo has appeared');
$(this).addClass('logo-animated');
};
function hide() {
console.log('logo has been removed');
};
});
I want to be able to assign selector to variable and have some functions within it that I could be able access from outer it's scope.
NOTICE, that is pseudo code, I just drew you a picture of how I see it.
var selector = $('.someclass',
# here goes functions that I could access from outside;
);
UPDATE
var parallax = function() {
var images = ["http://localhost:8000/static/assets/images/hero-image-welcome.jpeg"];
var selector = $('.parallax-module');
var reload = function() {
console.log('reload')
};
$(selector).each(function(index) {
var image = {};
image.element = $(this);
image.height = image.element.height();
images.push(image);
$(this).css('background-image', 'url(' + images[index] + ')');
});
return {
images: images,
reload: reload()
}
}();
parallax.reload;
console.log(parallax.images[0])
// This goes without error, but isn't right;
var sParallax = $('.parallax-module');
sParallax.addClass('someClass');
// This would cause error;
parallax.addClass('someClass');
In this case I can use parallax public properties and methods, but I can't use selector (as I did in the beginning) without creating a new link to it. I know I can use public properties to access selector, but it's not the way I looking for.
You can just set the variable with your desired selector and then just add functions to that variable
var logo = $('.logo');
logo.show = function () {
console.log('logo has appeared');
$(this).addClass('logo-animated');
}
logo.hide = function () {
console.log('logo has been removed');
}
logo.show();
logo.hide();
JSFIDDLE
You can access through this:
logo.prevObject[0].show()//or hide()
I think I found the way, but it does not look right to me, it is working thought:
var parallax = function() {
var images = ["http://localhost:8000/static/assets/images/hero-image-welcome.jpeg"];
var selector = $('.parallax-module');
var reload = function() {
console.log('reload')
};
$(selector).each(function(index) {
var image = {};
image.element = $(this);
image.height = image.element.height();
images.push(image);
$(this).css('background-image', 'url(' + images[index] + ')');
});
return {
images: images,
reload: reload()
}
}().selector;
With selector reference in the end it is now valid to use parallax variable as simple link to DOM element, as well as to access functions that within it.

Match url folders with a tag href to make an active state

I made an active state for my menu on a certain urls. I have urls like this:
/products/other-clothing/sporting/adults-anzac-australia-polo
/products/other-clothing/sporting/adults-nz-tee
/products/bags/backpacks
My code gets the folder from after the / so other-clothing, sporting, etc.
It is working fine, I just assume there is a more efficient way to write the code.
Here is my code:
jQuery(".product-nav li a").each(function() {
// URL url
var cat = location.pathname.split("/")[2];
var subcat = location.pathname.split("/")[3];
var c = "/products/" + cat + "/" + subcat;
// A tag url
var acat = this.href.split("/")[4];
var asubcat = this.href.split("/")[5];
var e = "/products/" + acat + "/" + asubcat;
if(e == c) {
jQuery(this).parent().addClass("active");
jQuery(this).parent().parent().parent().addClass("active");
}
});
If anyone can provide a cleaner way of writing the code that'd be great. I probably dont need "/products/" +.
Notice the output of the following expressions:
$('')[0].href;
/*
* http://stackoverflow.com/questions/7564539/match-url-folders-with-a-tag-href-to-make-a-active-state
*/
$('').eq(0).attr('href');
/*
* /questions/7564539/match-url-folders-with-a-tag-href-to-make-a-active-state
*/
So, if your <a> tags contain URLs that start with / then you can compare the .attr('href') with location.pathname. For testing, try running this in console from this page:
$('a').each(function () {
if ($(this).attr('href') == location.pathname) {
$(this).css({
'font-size': '40px',
'background-color': 'lime'
});
}
});
Here's a brief whack at it:
jQuery(".product-nav li a").each(function() {
// URL url
var c = location.pathname.split('/').slice(2, 4)
// A tag url
, e = this.href.split('/').slice(4, 6)
;
if(e[0] == c[0] && e[1] == c[1]) {
jQuery(this).parentsUntil(
'div:not(.subnav)', // go up the tree until the 1st div that isn't .subnav
'.product-nav li, .subnav' // and only match these parents
).addClass('active');
}
});
.parent().parent().parent()... has a pretty bad code smell to it but can't be improved without a look at your markup. You should probably be using .closest() instead.
Interesting question. Here is my attempt to clean it up:
jQuery(function ($) {
function Category(outer, inner) {
this.outer = outer
this.inner = inner
}
Category.fromURL = function (url) {
var parts = url.replace(/^(https?:\/\/.*?)?\//, "").split("/")
return new Category(parts[1], parts[2])
}
Category.prototype.equals = function (other) {
return this.outer === other.outer
&& this.inner === other.inner
}
var category = Subcategory.fromURL(location.href)
$(".product-nav a").each(function () {
if (Category.fromURL(this.href).equals(category)) {
$(this).closest("li.inner").addClass("active")
$(this).closest("li.outer").addClass("active")
}
})
})

Categories

Resources