As seen below, one function shows two things and the other function hides those two things.
Somehow they are so similar that I really want to see if there's a way I can combine them together but I just couldn't think of a way to do it.
var btns = {
filterBtn: function () {
$('.filter-btn').on('click', function () {
$('.cover').show();
$('.popup').show();
})
},
popupClose: function () {
$('.popup-x-btn').on('click', function () {
$('.cover').hide();
$('.popup').hide();
})
}
}
Anyone has any suggestions?
Or it's the best to just leave it as is?
The best refactoring I can think of would be to use jQuery.toggle(), which you can use true to show the element or false to hide it:
$('.showImage, .hideImage').on('click', function() {
$('img').toggle($(this).hasClass('showImage'));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="showImage">Show</button>
<button class="hideImage">Hide</button>
<br>
<img src="https://placeimg.com/250/250/any">
Or in your situation:
$('.filter-btn, .popup-x-btn').on('click', function() {
$('.cover, .popup').toggle($(this).hasClass('filter-btn'));
});
Its JS, you have so many possibilities:
function toggle(selector, dir) {
return function() {
$(selector).on('click', function() {
$('.cover')[dir]();
$('.popup')[dir]();
});
};
}
var btns = {
filterBtn: toggle('.filter-btn', 'show'),
popupClose: toggle('.popup-x-btn', 'hide'),
}
But is this better? Well, thats a personal opinion.
The important thing about JS is that you have so many ways to do everything, that the difficult thing is to find a consistent way to do things. Thats why people use more complex frameworks like react, angular, ember, vue, and so many more.
You can take advantage of jQuery's toggle method.
function toggleCoverAndPopup(showOrHide) {
$('.cover, .popup').toggle(showOrHide);
}
var btns = {
filterBtn: function() {
$('.filter-btn').on('click', function() {
toggleCoverAndPopup(true);
})
},
popupClose: function() {
$('.popup-x-btn').on('click', function() {
toggleCoverAndPopup(false);
})
}
}
you can do this with a simple if else statement.
$('.filter-btn .popup-x-btn').on('click', function() {
if ($(this).hasClass("filter-btn")) {
$('.cover').show();
$('.popup').show();
} else {
$('.cover').hide();
$('.popup').hide();
}
});
Related
Hi I am very new to javascript and I have made a lot of buttons on a site which have different calls. They are all the same minus the results which are pretty much like hide.1 and show.2 etc. Its pretty big but there must be a way to make it smaller? Its no biggy just bad coding that i would like not to get in the habbit of doing. And the only reason for (a) after some of them is that the call doesnt works twice, so when i set it show in a full size screen and a mobile screen it does not work right? Thanks
The code is :
$(document).ready(function() {
$('#btn-continue').on('click', function() {
$('#loginbox').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next1').on('click', function() {
$('#1box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next2').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next2a').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2a').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next3').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next3a').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3a').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next4').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-next4a').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4a').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back5').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back5a').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
And the calls work like this :
<div class="col-sm-6 controls hidden-xs">
<div><button id='btn-back2' name ='back2' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-xs"><button id='btn-next2' name ='next2' type='button' class='btn btn-primary'>Next</button></div>
</div>
<div class="col-xs-6 controls hidden-sm hidden-md hidden-lg">
<div><button id='btn-back2a' name ='back2a' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-sm hidden-md hidden-lg"><button id='btn-next2a' name ='next2a' type='button' class='btn btn-primary'>Next</button></div
>
Just make it more generic. You can use classes or data attributes. There is a lot of different ways to do this.
jsfiddle - https://jsfiddle.net/vouvcedj/4/
There is still a lot of cleanup that can be done but this should be pretty clear as to what you can do.
JS
$(function() {
var handleFirstLast = function() {
if ($('.shown').is('.section:first')) {
$('#btn-back').hide();
} else {
$('#btn-back').show();
}
if ($('.shown').is('.section:last')) {
$('#btn-forward').hide();
} else {
$('#btn-forward').show();
}
}
handleFirstLast();
$('#btn-back').on('click', function(e) {
// find the currently shown section and get the previous
// https://api.jquery.com/next/
var $showing = $('.section.shown');
$showing.prev().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
$('#btn-forward').on('click', function(e) {
var $showing = $('.section.shown');
$showing.next().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
});
HTML
<div class="sections">
<div class="section shown">
section 1
</div>
<div class="section hidden">
section 2
</div>
<div class="section hidden">
</div>
</div>
<div class="actions">
<button id="btn-back">Back</button>
<button id="btn-forward">Forward</button>
</div>
Perhaps something like this would make sense:
$(document).ready(function() {
const buttons = [
['btn-continue', 'loginbox', '1box'],
['btn-next1', '1box', '2box'],
['btn-next2', '2box', '3box'],
['btn-back2', '2box', '1box'],
['btn-next2a', '2box', '3box'],
['btn-back2a', '2box', '1box'],
['btn-next3', '3box', '4box'],
['btn-back3', '3box', '2box'],
['btn-next3a', '3box', '4box'],
['btn-back3a', '3box', '2box'],
['btn-next4', '4box', '5box'],
['btn-back4', '4box', '3box'],
['btn-next4a', '4box', '5box'],
['btn-back4a', '4box', '3box'],
['btn-back5', '5box', '4box'],
['btn-back5a', '5box', '4box']
]
buttons.forEach(function(b) {
$('#' + b[0]).on('click', function() {
$('#' + b[1]).hide();
$('#' + b[2]).show();
});
});
});
This captures the repetitive data in a single structure, and replaces the repetitive code with a loop.
Note that this is entirely untested.
This refers to DRY (Don't Repeat Yourself) principle and it is one of the core principles in programming. If you find yourself repeating in code, means trouble, a small change can be catastrophic for you if you need to go to each place and implement it.
With DRY principle you write code once and reuse it via numerous techniques. A change can be applied once and to one place only.
When the DRY principle is applied successfully, a modification of any
single element of a system does not require a change in other
logically unrelated elements. Additionally, elements that are
logically related all change predictably and uniformly, and are thus
kept in sync.
You code can be changed to something like this:
var myNs = myNs || {};
$(document).ready(function() {
$(myNs.map).each(function(index, value) {
$(value.clickedElement).on('click', clickHandler.bind(this, value));
});
function clickHandler(value) {
$(value.hideElement).hide();
$(value.showElement).show();
}
});
What about the myNs? This is a configuration object, a namespace to load your configuration for elements. This can be a completely different JavaScript file, which of course is loaded before this particular script.
Example:
// This can be in a completely different file
var myNs = {
map: [{
clickedElement: '#btn-continue',
hideElement: '#loginbox',
showElement: '#1box'
}, {
clickedElement: '#btn-next1',
hideElement: '#1box',
showElement: '#2box'
}, {
clickedElement: '#btn-next2',
hideElement: '#2box',
showElement: '#3box'
}, {
clickedElement: '#btn-back2',
hideElement: '#2box',
showElement: '#1box'
},
...
]
};
You can take this a step forward and use the new ES6 modules or even AMD modules. Of course you need a module loader for that, like SystemJS, Webpack or RequireJS.
Check out the following plunk to peak on some code.
I am new to Jasmine and seem to be struggling to get what I think is a fairy standard kind of thing running.
I am loading an HTML file via a fixture and trying to call a click on an element on the dom. This I would expect result in the call to the method of the JS file I am trying to test. When I try and debug this in developer tools the method that should be called in my js file never hits a breakpoint. As such I assume that code is not being called and therfore does not toggle the expand/collapse class.
My test:
describe("userExpand", function () {
beforeEach(function () {
loadFixtures('user-expand.html');
//userControl();
//this.addMatchers({
// toHaveClass: function (className) {
// return this.actual.hasClass(className);
// }
//});
});
//this test works ok
it("checks the click is firing", function () {
spyOnEvent($('.expanded'), 'click');
$('.expanded').trigger('click');
expect("click").toHaveBeenTriggeredOn($('.expanded'));
});
//this doesn't
it("checks the click is changing the class", function () {
//spyOnEvent($('.collapsed'), 'click');
var myElement = $('.collapsed');
myElement.click();
expect(myElement).toHaveClass('.expanded');
});
Part of the fixture:
<div class="wrapper">
<div class="row group">
<div class="col-md-1" data-bordercolour=""> </div>
<div class="collapsed col-md-1"> </div>
<div class="col-md-9">None (1)</div>
The JS I am trying to test:
var userControl = function () {
"use strict";
var collapse = '.collapsed';
var expand = '.expanded';
var userList = $(".userList");
function toggleState() {
var currentControl = $(this);
if (currentControl.hasClass('all')) {
if (currentControl.hasClass('expanded')) {
toggleIcon(currentControl, collapse);
userList.find(".user-group-summary").hide()
.end()
.find(".user-group-info").show();
} else {
toggleIcon(currentControl, expand);
userList.find(".user-group-summary").show()
.end()
.find(".user-group-info").hide();
}
} else {
currentControl.parent().nextUntil('.group').toggle();
currentControl.toggleClass("expanded collapsed");
currentControl.parent().find(".user-group-summary").toggle()
.end()
.find(".user-group-info").toggle();
}
};
function toggleIcon(ctrl, currentState) {
var details = ctrl.closest('div.row').siblings('.wrapper');
details.find(currentState).toggleClass('expanded collapsed');
if (currentState === expand) {
details.find('.detail').hide();
} else {
details.find('.detail').show();
}
}
userList.on('click', '.expanded, .collapsed', toggleState);
$('[data-bordercolour]').each(function () {
$(this).css("background-color", $(this).data('bordercolour'))
.parent().nextUntil('.group')
.find('>:first-child').css("background-color", $(this).data('bordercolour'));
});
return {
toggleState: toggleState
};
}();
The code works fine in normal use so I am sure I am missing something obvious with the way Jasmine should be used. Any help would be appreciated.
Update:
I can make the togglestate method fire by using call in the test rather than triggering a click event:
it('checks on click of icon toggles that icon', function () {
var myElement = $('.collapsed');
userControl.toggleState.call(myElement);
expect(myElement).toHaveClass('expanded');
});
This seems a little strange as all the examples I can find are quite happy with click. Gets me off the hook but I would still like to know what I am missing.
It's hard to give a precise hint without the source code. Does click on .collapsed involve asynchronous action(s)? If so, wrapping the test in runs(...); waitsFor(...); runs(...); may solve the problem. Check the Jasmine introduction for how to do this.
I creating jquery Plugin with simple alert option. I did this way see below is my code.But it doesn't work.
Below code is the seperate js file.
(function($) {
$.fn.gettingmessage = function() {
var element = this;
$(element).load(function() {
alertingcontent();
function preventnextpage() {
return false;
}
function alertingcontent() {
alert("nicely done");
});
};
})(jQuery);
I called this function as
this way
$(function(){
$("body").gettingmessage();
});
I am not sure how can i fix this any suggestion would be great.
JSFIDDLE
Thanks
First, you're missing a closing bracket.
Second, the load() function doesn't do what you're searching for, use ready() instead of.
Updated code :
(function($) {
$.fn.gettingmessage = function() {
var element = this;
$(element).ready(function() {
alertingcontent();
function preventnextpage() {
return false;
}
function alertingcontent() {
alert("nicely done");
}
});
};
})(jQuery);
$(function(){
$("body").gettingmessage();
});
Updated jsFiddle
How would I rewrite this to use a common function, pretending that the common function would eventually have more than the 1 line of code in it:
$('.insert').hover(function() {
$(this).css('cursor','pointer');
});
$('.delete').hover(function() {
$(this).css('cursor','pointer');
});
Or if you just want to avoid duplicating code
$('.insert,.delete').hover(function() {
$(this).css('cursor','pointer');
});
Just pull your current code into a separate function:
var f = function() {
$(this).css('cursor','pointer');
};
and:
$('.insert').hover(f);
$('.delete').hover(f);
this is just a variable, and like any variable you can pass it as a parameter to another function:
function my_func(obj) {
$(obj).css('cursor','pointer');
}
$('.insert').hover(function() {
my_func(this);
});
$('.delete').hover(function() {
my_func(this);
});
If I understand correctly...
function(elem) {
$(elem).hover(function() {
$(this).css('cursor','pointer');
});
}
I've got following JavaScript functions but want to refactor the $(document).ready() as I've got 2 instance of it. How can I achieve this?
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(document).ready(FlashMessenger.init);
$(document).ready(SelectLanguage.init);
It’s perfectly acceptable to set multiple handlers for $(document).ready, although you may have a good reason to do otherwise that I’m not aware of. You might be interested in knowing that $(handler) can be used as shorthand for $(document).ready(handler):
$(FlashMessenger.init);
$(SelectLanguage.init);
If you really want them in one call though, try this:
$(function() {
FlashMessenger.init();
SelectLanguage.init();
});
First off, there's no reason you have to combine them.
But if you want to:
$(document).ready(function(jq){
FlashMessenger.init(jq);
SelectLanguage.init(jq);
});
Breaking it down:
Create a function to do all your init (it can be named or anonymous; the one above is anonymous).
Have it call the other init functions, passing in the jQuery instance that jQuery passes you just in case they use it.
You might choose to wrap each init call in a try/catch block as well, so that errors in one init don't prevent the next init from occuring, but that depends on your needs.
Just combine them into one call with an anonymous function:
$(document).ready(function()
{
FlashMessenger.init();
SelectLanguage.init();
});
$(document).ready(function() {
FlashMessenger.init();
SelectLanguage.init();
});
Option 1
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(function(){
FlashMessenger.init();
SelectLanguage.init();
});
Option 2
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(document).ready(function(){
FlashMessenger.init();
SelectLanguage.init();
});
Option 3
You actually don't need those 2 objects since the only hold the init methods, so here's the ultimate solution, in my opinion, unless you use those objects elsewhere.
$(function(){
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
})
I prefer 2 and 3 for this reason.
I think what the op is saying is, "If in the future I have a third function to be invoked at document.ready, then how do I do it without touching that piece of code?"
If you do not want multiple $(document).ready() calls, you could just create an array called startupHooks and add each method to it:
startupHooks[ startupHooks.length ] = myNewStartupHook;
and your startup script could look like
$(document).ready(function() {
for( var i=0; i<startupHooks.length; i++ ) {
startupHooks[i]();
}
}
I know that is not mighty useful, but if that appeals to you, you can do it this way.
Personally, I'd go with multiple $(document).ready() calls.
Personally I'd go for not using document.ready at all.
If you place the scripts at the end of your html-page(just before the tag) you can just write in any way you like.
Maybe this doesn't work for 0.01% of the scripts but it never failed to work for me.
Positive effect of this is that the initial HTML+CSS rendering goes faster.
You can also read about it on yahoo. http://developer.yahoo.com/performance/rules.html#js_bottom