Code review? Is there a way to write this better? - javascript

I have some code.
$(this).find('.change--button__approve').click(function(){
if ($(this).hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'approved').attr('data-changeRule', 'once');
$(this).parent().parent().next().find('p').attr('class','approved').addTemporaryClass('bounceIn', 500);
$(this).parent().parent().next().find('.changeStatus').text('approved for this document');
}
});
$(this).find('.change--button__approveAlways').click(function(){
if ($(this).hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'approved').attr('data-changeRule', 'always');
$(this).parent().parent().next().find('p').attr('class','approved').addTemporaryClass('bounceIn', 500);
$(this).parent().parent().next().find('.changeStatus').text('approved for every document');
}
});;
$(this).find('.change--button__reject').click(function(){
if ($(this).hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'rejected').attr('data-changeRule', 'once');
$(this).parent().parent().next().find('p').attr('class','rejected').addTemporaryClass('bounceIn', 500);
$(this).parent().parent().next().find('.changeStatus').text('rejected for this document');
}
});;
$(this).find('.change--button__rejectAlways').click(function(){
if ($(this).hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'rejected').attr('data-changeRule', 'always');
$(this).parent().parent().next().find('p').attr('class','rejected').addTemporaryClass('bounceIn', 500);
$(this).parent().parent().next().find('.changeStatus').text('rejected for every document');
}
});;
Because of the repetitive nature of the code, I figured there must be a better way to write this. I'm still a js beginner so I'd love to know how to write this cleaner/better.
Thanks!

Here is an optimized code :
var _this = $(this);
_this.find('.change--button__approve').click(function() {
var sibling, _this = $(this);
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'approved').attr('data-changeRule', 'once');
sibling = _this.parent().parent().next();
sibling.find('p').attr('class','approved').addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text('approved for this document');
}
});
_this.find('.change--button__approveAlways').click(function() {
var sibling, _this = $(this);
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'approved').attr('data-changeRule', 'always');
sibling = _this.parent().parent().next();
sibling.find('p').attr('class','approved').addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text('approved for every document');
}
});
_this.find('.change--button__reject').click(function() {
var sibling, _this = $(this);
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'rejected').attr('data-changeRule', 'once');
sibling = _this.parent().parent().next();
sibling.find('p').attr('class','rejected').addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text('rejected for this document');
}
});
_this.find('.change--button__rejectAlways').click(function() {
var sibling, _this = $(this);
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', 'rejected').attr('data-changeRule', 'always');
// Store into a variable to optimize execution speed
sibling = _this.parent().parent().next();
sibling.find('p').attr('class','rejected').addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text('rejected for every document');
}
});
As you see, the main problem is the using of $(this).parent().parent().next() on two simultaneous lines, which cause speed issue. By storing it in a local variable, this function chain is not runned two times, and the local variable is deleted after usage.
The second problem (less important) is the repetitive usage of $(this) which needs to create a jQuery instance each time you use it. By storing it in a local variable _this you can use at as often as you want without causing a instanciation that takes time.
EDIT : Another more complex but better way is this one :
var _this = $(this);
var details = {
'approve': ['once', 'approved', 'approved for this document'],
'approveAlways': ['always', 'approved', 'approved for every document'],
'reject': ['once', 'rejected', 'rejected for this document'],
'rejectAlways': ['always', 'rejected', 'rejected for every document']
}, keys = Object.keys(details);
for(var i = 0; i < keys.length; i++)
// Create a lambda function to use local variable 'scope'
(function(scope) {
// Here is the content of 'scope' :
// scope[0] : value of 'data-changeRule' ('once' or 'always')
// scope[1] : value of 'data-type' and 'class' ('approved' or 'rejected')
// scope[2] : message to display 'approved for this document'...
_this.find('.change--button__' + scope[3]).click(function() {
var sibling, _this = $(this);
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', scope[1]).attr('data-changeRule', scope[0]);
sibling = _this.parent().parent().next();
sibling.find('p').attr('class', scope[1]).addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text(scope[2]);
}
});
}).call(this, details[keys[i]].concat([keys[i]])); // Call the lambda function with its informations
Please note that this code is not optimized as best I can but if I optimize it it will be ugly.
EDIT 2 : This is, I think, the faster and better way to do your stuff :
var _this = $(this),
keys = ['approve', 'approveAlways', 'reject', 'rejectAlways'];
for(var i = 0; i < keys.length; i++)
// Create a lambda function to use local variable 'scope'
(function(name) {
_this.find('.change--button__' + name).click(function() {
var sibling, _this = $(this),
approve = (name.indexOf('approve') !== -1 ? 'approved' : 'rejected'), // Is the button an approving or rejecting button ?
always = (name.indexOf('Always') !== -1); // Is the button setting always or only for THIS document ?
if (_this.hasClass('disabled')) {
return false;
} else {
$(thisChange).attr('data-type', approve).attr('data-changeRule', (always ? 'always' : 'once'));
sibling = _this.parent().parent().next();
sibling.find('p').attr('class', approve).addTemporaryClass('bounceIn', 500);
sibling.find('.changeStatus').text(approve + ' for ' + (always ? 'every' : 'this') + ' document');
}
});
}).call(this, keys[i]); // Call the lambda function with its informations
Only a few strings are stored and your function _this.find('.change--button__...').click( is writed one time only and runned by the loop multiple times. If you want to add buttons, you can simply add their name in the array keys.
I hope I helped you :)

try storing your strings
'.change--button__approve', '.change--button__approveAlways',...
in an array like
var strings = new Array('.change--button__approve', '.change--button__approveAlways', '.change--button__reject','.change--button__rejectAlways');
and use a for-loop to avoid code duplication:
for(int i=0; i<strings.length;i++){
$(this).find(strings[i]).click(function(){
...
}
}
your changing string 'once'/'always' also need an array if this basically works
(not completely sure)

Related

Preventing Jquery .click toggle function from running over and over with excess clicking

Im building a .clicktoggle function in jQuery and for the life of me i can't get a .stop like effect on it, basically i don't want it to play over and over if mash clicked.
I want it to be applied the the function so its self contained, that's where im stuck.
JS fiddle link
(function($) {
$.fn.clickToggle = function(func1, func2) {
var funcs = [func1, func2];
this.data('toggleclicked', 0);
this.click(function() {
var data = $(this).data();
var tc = data.toggleclicked;
$.proxy(funcs[tc], this)();
data.toggleclicked = (tc + 1) % 2;
});
return this;
};
}(jQuery));
$('div').clickToggle(function() {
$('.testsubject').fadeOut(500);
}, function() {
$('.testsubject').fadeIn(500);
});
<div class="clickme">click me fast</div>
<div class="testsubject">how do i stop it playing over and over if you click alot</div>
Toggle .click seems like something alot of people would use so i thought it might be useful to ask it here
By adding a check to a boolean variable fadeInProgress, you can choose to only queue the animation if fadeInProgress is false. It then sets the value to true and executes the animation. When the animation is completed, set the value to false.
var fadeInProgress = false;
$('div').clickToggle(function() {
if (!fadeInProgress) {
fadeInProgress = true;
$('.testsubject').fadeOut(700, function(){fadeInProgress = false;});
}
}, function() {
if (!fadeInProgress) {
fadeInProgress = true;
$('.testsubject').fadeIn(700, function(){fadeInProgress = false;});
}
});
var clicked = false;
var doing = false;
$(".clickme").click(function(e) {
if (doing) {
return;
} else {
doing = true;
}
doing = true;
clicked = !clicked;
if (clicked) {
$('.testsubject').fadeOut(700, function() {
doing = false
});
} else {
$('.testsubject').fadeIn(700, function() {
doing = false;
});
}
});
This example is a simple toggle which only allows you to click when it is not doing anything. I explained on IRC, but as an example here, the function only runs when doing is set to false, which only happens when it's set after fadeIn() or fadeOut's callback function thingymajigger.

AngularJs+Bootstrap+Typehead+Ajax is working only if i put alert box but only in chrome

i am using bootsrap typehead with angularjs given at this link http://angular-ui.github.io/bootstrap/
In my controller
$scope.getUser = function(val) {
//alert("hi");
return $http.get('user/getUserNames.do', {
params: {
userName: val,
}
}).then(function(response){
return response.data;
});
};
my html code
<input type="text" ng-model="asyncSelected" typeahead-wait-ms="300" typeahead="user for user in getUser($viewValue)" class="form-control">
if remove the alert the typehead will not work
if i keep the alert the typehead will work only in chrome
if i place a break point at "return $http.get('user/getUserNames.do'" and step out using
fire bug it works in firefox
i am receiving the data in this formate ['name1','name2'] from server
some one please help
thanks in advance
thats because the time taken to close the alert the async data is recieved. you should store the data on $scope rather then calling a function on $scope
$scope.users= {};
$scope.getUser = function(val) {
return $http.get('user/getUserNames.do', {
params: {
userName: val,
}
}).then(function(response){
$scope.users= response.data;
});
};
html
<input type="text" ng-model="asyncSelected" ng-change="getUser($viewValue)"
typeahead-wait-ms="300" typeahead="user for user in users" class="form-control">
your cods logic is incorrect,you cant return data like that from a async function, that need time to complete,
dont return anything from this getUser function. you have 2 option :
1 - store the responce.data in a global variable to be used later
$scope.users = [];
$scope.getUser = function (val) {
$http.get('user/getUserNames.do', {
params: {
userName: val
}
}).then(function (response) {
$scope.users.push(response.data);
});
};
2 - call another function when get function is complete to handle the data recived
$scope.getUser = function (val) {
$http.get('user/getUserNames.do', {
params: {
userName: val
}
}).then(function (response) {
$scope.userLoaded(response.data);
});
};
By the simple hack in angular-ui-bootstrap i solved the problem
before..........
var getMatchesAsync = function(inputValue) {
var locals = {$viewValue: inputValue};
isLoadingSetter(originalScope, true);
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
if (onCurrentRequest && hasFocus) {
if (matches.length > 0) {
scope.activeIdx = focusFirst ? 0 : -1;
scope.matches.length = 0;
//transform labels
for(var i=0; i<matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = inputValue;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
element.attr('aria-expanded', true);
} else {
resetMatches();
}
}
if (onCurrentRequest) {
isLoadingSetter(originalScope, false);
}
}, function(){
resetMatches();
isLoadingSetter(originalScope, false);
});
};
i just removed '&& hasFocus' this sipneet from the code
after ........
var getMatchesAsync = function(inputValue) {
var locals = {$viewValue: inputValue};
isLoadingSetter(originalScope, true);
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
if (onCurrentRequest) {
if (matches.length > 0) {
scope.activeIdx = focusFirst ? 0 : -1;
scope.matches.length = 0;
//transform labels
for(var i=0; i<matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = inputValue;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
element.attr('aria-expanded', true);
} else {
resetMatches();
}
}
if (onCurrentRequest) {
isLoadingSetter(originalScope, false);
}
}, function(){
resetMatches();
isLoadingSetter(originalScope, false);
});
};

Returning true on Link Click Event isn't working

I am trying to execute some code that has a callback when clicking on specific links. The callback is to click the link again, but on the second pass, the method returns true to follow normal behavior. But for some reason, it's not working? I use clickedLink for proper scope. In the event callback, this was referring to window.
UPDATE
For a little more clarity, I agree normally this wouldn't be an optimal solution, but I am using Google's Tag Manager to monitor eCommerce traffic. I am trying to make sure product clicks get pushed to their dataLayer, and using their event callback to resume normal behavior. More info here: https://developers.google.com/tag-manager/enhanced-ecommerce#product-clicks
This is my updated method based on the answers below, but it still doesn't work.
var shopCartBtnClicked = false;
var clickedLink;
jQuery('#pdp_add_cart, .add_to_cart_button').click(function(e) {
if (shopCartBtnClicked === true) {
shopCartBtnClicked = false; //I make it here just fine
} else {
e.preventDefault();
clickedLink = this;
var pdp = false;
if (mmProduct) {
//On detail page
mmProduct.qty = jQuery('input[name="quantity"]').val();
pdp = true;
} else {
//on a shopping page
mmProduct = findProductClicked(this);
mmProduct.qty = 1;
}
dataLayer.push({
'event': 'addToCart',
'ecommerce': {
'currencyCode': 'USD',
'add': {
'products': [{
'name': mmProduct.name,
'id': mmProduct.id,
'price': mmProduct.price,
'quantity': mmProduct.qty
}]
}
},
'eventCallback': function () {
//Are we on a product detail page with a form, or just a shopping page with a link?
if (pdp) {
jQuery('form.cart').submit(); //This part works just fine
} else {
mmProduct = null;
shopCartBtnClicked = true;
$(clickedLink).trigger('click'); //This doesn't
}
}
});
}
});
Its not very well done, but this should work:
var shopCartBtnClicked = false;
var clickedLink;
jQuery('.add_to_cart_button').click(function(e) {
if (shopCartBtnClicked === true) {
shopCartBtnClicked = false;
// dont return, just let javascript handle this one
} else {
// only execute when you have not set it to true
e.preventDefault();
clickedLink = this;
shopCartBtnClicked = true;
$(clickedLink).trigger('click');
}
});
I do have to wonder why you don't just execute your other logic first and then not prevent default anyway.
Taking #somethinghere's answer, your code can further be simplified to improve readability:
var shopCartBtnClicked = false;
jQuery('.add_to_cart_button').click(function(e) {
if( shopCartBtnClicked ) {
shopCartBtnClicked = false;
// dont return, just let javascript handle this one
} else {
// only execute when you have set it to true
e.preventDefault();
shopCartBtnClicked = true;
this.click();
}
});
Or, as suggested by #Regent:
var shopCartBtnClicked = false;
jQuery('.add_to_cart_button').click(function(e) {
shopCartBtnClicked = !shopCartBtnClicked;
if( shopCartBtnClicked ) {
e.preventDefault();
this.click();
}
});
OK guys, thank you for helping me get there. Normally, all of the other answers would work great, but for this specific tag manager instance, it appears (for some unknown reason), document.location works in the event callback fine here. This works.
It's weird because I used $(this).('form.cart').submit(); in a callback earlier in the code.
'eventCallback': function () {
//Are we on a product detail page with a form, or just a shopping page with a link?
if (pdp) {
jQuery('form.cart').submit();
} else {
mmProduct = null;
document.location = $(clickedLink).attr('href');
//$(clickedLink).trigger('click');
}
}

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

Create a jQuery special event for content changed

I'm trying to create a jQuery special event that triggers when the content that is bound, changes. My method is checking the content with a setInterval and check if the content has changed from last time. If you have any better method of doing that, let me know. Another problem is that I can't seem to clear the interval. Anyway, what I need is the best way to check for content changes with the event.special.
(function(){
var interval;
jQuery.event.special.contentchange = {
setup: function(data, namespaces) {
var $this = $(this);
var $originalContent = $this.text();
interval = setInterval(function(){
if($originalContent != $this.text()) {
console.log('content changed');
$originalContent = $this.text();
jQuery.event.special.contentchange.handler();
}
},500);
},
teardown: function(namespaces){
clearInterval(interval);
},
handler: function(namespaces) {
jQuery.event.handle.apply(this, arguments)
}
};
})();
And bind it like this:
$('#container').bind('contentchange', function() {
console.log('contentchange triggered');
});
I get the console.log 'content changed', but not the console.log 'contentchange triggered'. So it's obvious that the callback is never triggered.
I just use Firebug to change the content and to trigger the event, to test it out.
Update
I don't think I made this clear enough, my code doesn't actually work. I'm looking for what I'm doing wrong.
Here is the finished code for anyone interested
(function(){
var interval;
jQuery.event.special.contentchange = {
setup: function(){
var self = this,
$this = $(this),
$originalContent = $this.text();
interval = setInterval(function(){
if($originalContent != $this.text()) {
$originalContent = $this.text();
jQuery.event.handle.call(self, {type:'contentchange'});
}
},100);
},
teardown: function(){
clearInterval(interval);
}
};
})();
Thanks to Mushex for helping me out.
also take a look to James similar script (declaring as jquery object method and not as event)
jQuery.fn.watch = function( id, fn ) {
return this.each(function(){
var self = this;
var oldVal = self[id];
$(self).data(
'watch_timer',
setInterval(function(){
if (self[id] !== oldVal) {
fn.call(self, id, oldVal, self[id]);
oldVal = self[id];
}
}, 100)
);
});
return self;
};
jQuery.fn.unwatch = function( id ) {
return this.each(function(){
clearInterval( $(this).data('watch_timer') );
});
};
and creating special event
jQuery.fn.valuechange = function(fn) {
return this.bind('valuechange', fn);
};
jQuery.event.special.valuechange = {
setup: function() {
jQuery(this).watch('value', function(){
jQuery.event.handle.call(this, {type:'valuechange'});
});
},
teardown: function() {
jQuery(this).unwatch('value');
}
};
Anyway, if you need it only as event, you script is nice :)
I know this post/question is a little old, but these days I was behind a similar solution and I found this:
$('#selector').bind('DOMNodeInserted', function(e) {
console.log(e.target);
});
Source: http://naspinski.net/post/Monitoring-a-DOM-Element-for-Modification-with-jQuery.aspx
Hope this help someone!
The finished code in the original question worked for me, thank you! I would just like to note that I am using jquery 1.9.1 and $.event.handle seems to have been removed. I changed the following to get it to work.
jQuery.event.handle.call(self, {type:'contentchange'});
to
jQuery.event.dispatch.call(self, {type:'contentchange'});
maybe you could try Mutation Observer
Here are the code:
mainArea = document.querySelector("#main_area");
MutationObserver = window.MutationObserver;
DocumentObserver = new MutationObserver(function() {
//what you want to run
});
DocumentObserverConfig = {attributes: true, childList: true, characterData: true, subtree: true};
DocumentObserver.observe(mainArea, DocumentObserverConfig);

Categories

Resources