Passing arguments to encapsulated in object jquery function - javascript

Learning on CodeSchool, they advice to store functions in objects and to call them later.
That works absolutely fine until I want to pass an argument to the function. At this point, I'm lost with either the syntax or the logic behind.
A sample code is worth a thousand words :
var subscriptionMaster = {
init: function() {
$('.choose_offer').on('click', this.toggleOfferClass);
$('.nextFirstPage, .nextSecondPage, .prevThirdPage, li:not(".firstLink")').on('click', this.changeDisplayTransForm('none', false);
$('.prevSecondPage, .firstLink').on('click', this.changeDisplayTransForm('block', true);
},
toggleOfferClass: function(event) {
$('.choose_offer').removeClass('active');
$(this).addClass('active');
},
changeDisplayTransForm: function(sState, bVerifyStep1) {
$('#transformClientToMaster').css('display', sState);
if (bVerifyStep1) {
if ($('#step1_done').val() != '1') {
changeDisplayTransForm(sState);
}
}
}
};
this :
$('.choose_offer').on('click', this.toggleOfferClass);
is working fine, while this :
$('.nextFirstPage, .nextSecondPage, .prevThirdPage, li:not(".firstLink")').on('click', this.changeDisplayTransForm('none', false);
$('.prevSecondPage, .firstLink').on('click', this.changeDisplayTransForm('block', true);
does not work at all.
Any help ?

Related

JQuery call popup

I am an Apprentice and never worked with Javascript.
My Javascript function calls a popup. This works on the first button but doesn't work on all the following and since the application is constantly adding buttons(same class) I cannot hardcode. I guess ther will be a solution with JQuery...
("open") and ("openPopupUseExisting") are the two buttons.
<script type="text/javascript">
window.onload = function () {
document.getElementById('blackout').addEventListener('click', function () {
document.getElementById('popup').className = "";
document.getElementById('blackout').className = "";
document.getElementById('popupUseExisting').className = "";
}, false);
document.getElementsByClassName("open")[0].addEventListener('click', function () {
document.getElementById('popup').className = 'visable';
document.getElementById('blackout').className = 'visable';
}, false);
document.getElementsByClassName("openPopupUseExisting")[0].addEventListener('click', function () {
document.getElementById('popupUseExisting').className = 'visable';
document.getElementById('blackout').className = 'visable';
}, false);
document.getElementsByClassName("close")[0].addEventListener('click', function () {
document.getElementById('popup').className = "";
document.getElementById('blackout').className = "";
document.getElementById('popupUseExisting').className = "";
}, false);
};
</script>
document.getElementsByClassName("close")[0]
See that 0?
getElementsByClassName returns an array-like object. You are getting the first item off it.
Loop over it with a for loop.
Friend, you don't need to add an entire library just in order to bind dynamically added elements.
You can bind the document for click event, and then check if the clicked element is the one you want. It prevent dynamically added elements to be unbound, since it aims for the entire document.
document.addEventListener('click', function (e) {
if (e.target.classList.contains('blackout')) {
// your fancy magic with .blackout
} else if (e.target.classList.contains('open')) {
// your fancy magic with .open
}
}, false);
If you really want to use jQuery, as you ordered, it's quite simple, use the on method
$('.open').on('click', function(){
// your fancy magic with .open
});

Is there a way to write JavaScript so it is more readable when dealing with many callbacks?

I am working in JavaScript. I get more and more frustrated because of the way the code looks. The code is so nested that I very soon need to invest in a third 37" monitor to prevent damaging my fingers for all time.
Here is some working code I am working on right now:
$(function () {
var mainContainer = $('#mainContainer'),
countyContainer = $('#countyContainer'),
workAreaContainer = $('#workAreaContainer');
$('body').fadeIn(400, function() {
mainContainer.slideDown(400, function() {
ShowLoader();
ListCounties(function(counties,success) {
if (success) {
HideLoader();
for (var i = 0; i < counties.length; i++) {
if (counties[i] != "") {
countyContainer.append(
'<div class="col-md-3 county-result-item">'+
'<h3>'+counties[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
}
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function(event) {
event.preventDefault();
var county = $(this).text().split("(")[0];
if (county != "") {
ShowLoader();
countyContainer.slideUp(400);
FetchWorkAreas(county,function(workAreaData,success) {
if (success) {
for (var i = 0; i < workAreaData.workAreas.length; i++) {
workAreaContainer.append(
'<div class="col-md-3 workArea-result-item">'+
'<h3>'+workAreaData.workAreas[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
HideLoader();
workAreaContainer.slideDown(400, function() {
var workAreaResultItem = $('.workArea-result-item');
workAreaResultItem.on('click', function(event) {
event.preventDefault();
var selectedWorkArea = $(this).text().split("(")[0];
FetchJobListings(workAreaData.countyID,selectedWorkArea,function(jobListings,success) {
if (success) {
console.log(jobListings);
}
});
});
})
}
});
}
});
}
});
});
});
function FetchJobListings(countyID,selectedWorkArea,callback) {
$.post('Fetch.php', {fetch: 'jobListings',countyID : countyID, selectedWorkArea: selectedWorkArea}, function(data) {
if (data) {
callback(data,true);
}
});
}
function FetchWorkAreas(county,callback)
{
$.post('Fetch.php', {fetch: 'workAreasID',county:county}, function(data) {
if (data && data.workAreas.length > 0) {
callback(data,true);
}
});
}
function ListCounties(callback)
{
$.post('Fetch.php', {fetch: 'counties'}, function(data) {
if (data) {
if (data.length > 0) {
callback(data,true);
}
}
});
}
function RandomColor() {
var colors = ["#cea547","#7e8b58","#002c44","6da6a7"];
var rand = Math.floor(Math.random()*colors.length);
return colors[rand];
}
function FA(icon,size) {
var iconSize = '';
if (typeof size != undefined) {
iconSize = size;
}
return 'fa '+ icon+' fa-'+size;
}
function ShowLoader() {
if ($('.imgLoader').length === 0) {
var loaders = ["loading1.gif","loading2.gif","loading3.gif","loading4.gif"];
var rand = Math.floor(Math.random()*loaders.length);
$('#mainContainer').append('<div class="imgLoader"><img class="imgLoader img-responsive img-center" src="imgs/'+loaders[rand]+'" /><h3 class="text-center">Laster</3></div>');
}
}
function HideLoader() {
$('.imgLoader').fadeOut(400,function() {
$(this).remove();
});
}
});
This just hurt my eyes and I get a little sad really since it is important for me that code is readable and pretty, and not ending up looking like a fish ladder:
}
});
});
})
}
});
}
});
}
});
});
});
I am still what I call a novice when it comes to JavaScript and it may be something fundamentally I have not noticed. But basically, is there a way to keep the code so it doesn't indent 1000 times by the time I am done with it?
This is an example of borderline Spaghetti Code.
There are a few things you can do, the first being to split out all your functions so that they exist independently as smaller processes. For example:
ListCounties(function(counties,success) {
if (success) {
Rather than create an inline function (that isn't reusable), ask yourself what is it that I want to do with a list of counties, and build that independently.
// write a nice comment here to explain what the function does and what the arguments mean
function handleCountyList(countries, success){
}
Now you can call your ListCounties as follows:
ListCounties(handleCountyList);
By splitting out all your functions into more manageable pieces, you will avoid the ladder.
This should also make your code much more maintainable, as it becomes easier to read, think about, debug and update. Empathise with other developers, and ask, "If I showed my friend this code, could she easily understand what it does?"
If you want to get a bit more advanced, and callbacks are what is annoying you, try promises. They are semantically, and functionally better in my opinion, and allow you to do things like:
ListCounties().then(function(counties){
// do something with the counties
});
Promises come in different flavours, but this library is a good start: https://github.com/petkaantonov/bluebird
Name your callbacks and separate the logic, e.g.
ListCounties(function(counties,success) {
if (success) {....
becomes:
ListCounties(processCounties);
function processCounties(counties,success)
if (success) {...
}
Looking at your code there are a couple of things you could do, but before that it's much more important to be consistent across your codebase - with most IDEs allowing you to collapse blocks it'd be a little bit more difficult to deal with a shift in coding style than deep nesting
Guard Clauses
You could replace your
ListCounties(function(counties,success) {
if (success) {
...
with
ListCounties(function(counties,success) {
if (!success)
return;
...
to reduces 1 level of nesting. You can apply this for most of the checks that'd you do before deciding to proceed with the bulk of the code.
Be warned that this would this would make the code less readable if the guard condition is not very obvious. For instance while this is a good place for a guard clause (the guard clauses serves more like a filter)
KillWasps(function(bug) {
if (bug !== "wasp")
return
// code to deploy nuclear warheads
...
this would NOT be a good place for a guard clause (the guard block actually has program logic and 2nd block which executes for wasps is more of a fall-through block, just like a fall-through block for switch cases)
HandleInsect(function(bug) {
if (bug !== "wasp") {
// code to sign up bug for insects anonymous
return
}
// code to deploy nuclear warheads
...
An else block (and a thick magazine) is what I would use in this case.
Related reading : http://blog.codinghorror.com/flattening-arrow-code/
Delegated Event Handlers
I see you have one places where you do (for when you just added some .county-result-item elements)
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function (event) {
...
You might want to consider moving this to a delegated event handler
countyContainer.on('click', '.county-result-item', function(event) {
...
And you can move this from inside your current handler that does the append to an outer scope.
Of course, if you don't have any .county-result-item in your DOM this is a good way to perplex someone if they can't easily figure out where .county-result-item are / are added - this would be BAD, so keep your append logic close your delegated click handlers if that is the case.
Related reading : http://api.jquery.com/on/ (see the section on delegated events)
Other methods are already listed in other answers.

Sinon Spy not working with Javascript call or apply

I'm trying to use sinon.spy to check if the play function is being called for a component. The problem is that the spy counter is not updating, even though I've confirmed that my component's function is indeed being called.
I've tracked it down to the use of Javascript's call function:
handleDone: function(e) {
for (var i = 0; i < this.components.length; i++) {
if (this.components[i].element === e.target) {
if (this.components[i].hasOwnProperty("play")) {
// This won't trigger the spy.
this.components[i]["play"].call(this.components[i].element);
}
break;
}
}
}
A similar thing happens when swapping call for apply.
[UPDATED]
Here is the relevant test:
var page = document.getElementById("page"),
demo = document.getElementById("demo"),
playSpy;
suiteSetup(function() {
playSpy = sinon.spy(demo, "play");
});
suiteTeardown(function() {
demo.play.restore();
});
suite("done", function() {
test("rise-component-done fired from element with play property", function() {
assert(playSpy.notCalled); //true
demo.sendDone();
assert(playSpy.calledOnce); //false
});
});
And the play and sendDone functions in the demo component:
play: function() {
console.log("play");
},
sendDone: function() {
this.dispatchEvent(new CustomEvent("rise-component-done", { "bubbles": true }));
}
The page component is registered to listen for this event:
this.addEventListener("rise-component-done", this.handleDone);
Anyone know of a workaround?
Thx.
So the problem wasn't actually related to the call method at all as Ben illustrated. After much tinkering, I realized I had to change this line:
playSpy = sinon.spy(demo, "play");
to this:
playSpy = sinon.spy(page.components[0], "play");
Now my tests pass. Hooray! I just wish it hadn't taken me the better part of a day to debug.

Multiple click handlers for a single element

I've written a few events to handle opening and closing of a snap js drawer. This code below works, but I feel it could be written more efficiently. Any suggestions?
function openMobileMenu() {
event.preventDefault();
snapper.open('left');
$('#btn-menu').off('click', openMobileMenu);
$('#btn-menu').on('click', closeMobileMenu);
}
function closeMobileMenu() {
event.preventDefault();
snapper.close('left');
$('#btn-menu').on('click', openMobileMenu);
$('#btn-menu').off('click', closeMobileMenu);
}
$('#btn-menu').on('click', openMobileMenu);
Make your code modular and your concepts explicit.
You can start by creating a MobileMenu object which encapsulates the logic.
Note: The following code was not tested.
var MobileMenu = {
_snapper: null,
_$button: null,
_direction: 'left',
init: function (button, snapper, direction) {
this._$button = $(button);
this._snapper = snapper;
if (direction) this._direction = direction;
this._toggleSnapperVisibilityWhenButtonClicked();
},
_toggleSnapperVisibilityWhenbuttonClicked: function () {
this._$button.click($.proxy(this.toggle, this));
},
toggle: function () {
var snapperClosed = this._snapper.state().state == 'closed',
operation = snapperClosed? 'open' : 'closed';
this._snapper[operation](this._direction);
}
};
Then in your page you can just do the following to initialize your feature:
var mobileMenu = Object.create(MobileMenu).init('#btn-menu', snapper);
Modularizing your code will make it more maintainable and understandable in the long run, but also allow you to unit test it. You also gain a lot more flexibily because of the exposed API of your component which allows other code to interact with it.
E.g. you can now toggle the menu visibility with mobileMenu.toggle().
Use a variable to keep track of the state:
var menu_open = false;
$("#btn-menu").on('click', function(event) {
event.preventDefault();
if (menu_open) {
snapper.close('left');
} else {
snapper.open('left');
}
menu_open = !menu_open; // toggle variable
});
snap has a .state() method, which returns an object stuffed with properties, one of which is .state.
I think you want :
$('#btn-menu').on('click', function() {
if(snapper.state().state == "closed") {
snapper.open('left');
} else {
snapper.close('left');
}
});
Or, in one line :
$('#btn-menu').on('click', function() {
snapper[['close','open'][+(snapper.state().state == 'closed')]]('left');
});
Also, check How do I make a toggle button? in the documentation.

jQuery pass $this to function parameter

I have:
<img id="leftBubble" class="bubbles" src="left.png" />
<img id="rightBubble" class="bubbles" src="right.png" />
And a hover event like so:
$(".bubbles").each(function(){
$(this).hover(function() {
pause($(this));
}, function() {
play(4000, $(this));
});
});
My pause() function does not seem to be working
function pause(pauseMe) {
if (pauseMe == $("#leftBubble")) {
clearTimeout(timer1); //this is never reached
} else if (pauseMe == $("#rightBubble")) {
clearTimeout(timer2); //nor this
}
}
Any idea to make the hover event pass $this as the parameter for the pause function?
Each time you call $, it returns a different result set object, even if the result contents are the same. The check you have to do is:
if (pauseMe.is("#leftBubble")) {
Try like below,
function pause(pauseMe) {
if (pauseMe == "leftBubble") {
clearTimeout(timer1);
} else if (pauseMe == "rightBubble") {
clearTimeout(timer2);
}
}
and in the caller,
$(".bubbles").each(function(){
$(this).hover(function() {
pause(this.id);
}, function() {
play(4000, $(this));
});
});
In javascript, this is redefined each time you enter a new function definition. If you want to access the outside this, you need to keep a reference to it in a variable (I used the self) variable.
$(".bubbles").each(function(){
var self = this;
$(this).hover(function() {
pause($(self));
}, function() {
play(4000, $(self));
});
});
I don't know if your comparison between jQuery objects will work though. Maybe you can compare the DOM elements: pauseMe[0] == $("#leftBubble")[0], or, as mentioned, the ids.
When you call $( ... ) it generates new object that not the same that was genereted when you call $( ... ) last time, with same parametrs.
Anyway, you can't compare objects with == in javascript. It returns true only if it liks on same object.
a = {b:1}
c = {b:1}
d = c
a == b // false
d == c // true

Categories

Resources