In the avatar generator I'm working on I have several button events that do the same thing but with different body parts. I don't want to have different functions doing the same thing, so I want to use one function for all body parts.
Line 14 below uses the object propery 'AvGen.eyes.currEyesNr', but instead I want to use the one for the clicked button. What can I put in as an argument and how can I use the parameter in the function to be able to use the correct object parameter?
1. prevBodyBtn.on('click', function(){
2. if (AvGen.theBody.currBodyNr > 0) {
3. changePart(-1);
4. }
5. });
6.
7. prevEyesBtn.on('click', function(){
8. if (AvGen.eyes.currEyesNr > 0) {
9. changePart(-1);
10. }
11. });
12.
13. function changePart(direction) {
14. AvGen.eyes.currEyesNr += direction; // <-- this is now always 'AvGen.eyes.currEyesNr' but should be dynamic
15.
16. var body = AvGen.theBody.bodies[AvGen.theBody.currBodyNr],
17. eyes = AvGen.eyes.eyes[AvGen.eyes.currEyesNr],
18. nose = AvGen.nose.noses[AvGen.nose.currNoseNr];
19. mouth = AvGen.mouth.mouths[AvGen.mouth.currMouthNr];
20.
21. AvGen.addSVG(body, eyes, nose, mouth);
22. }
Change names of the properties indicating current index values from currBodyNr, currEyesNr etc to currNr.
Then you can address required property of AvGen by name:
function changePart(direction, bodyPartName) {
var part = AvGen[bodyPartName];
part.currNr += direction;
...
and call it:
changePart(-1, "eyes");
or
changePart(-1, "theBody");
Another way to do it is to simply pass the body part that needs to change as a second parameter:
function changePart(direction, bodyPart) {
bodyPart.currNr += direction;
and call it:
changePart(-1, AvGen.eyes);
You can pass an argument to the 'click' event callback, and check which item was clicked.
Than use it to pass whatever you want to the 'changePart' function. ( you can read some data you have in the clicked element for example to pass them to your function )
prevEyesBtn.on('click', function(e){
e.currentTarget; // this is the element that was clicked for example
if (AvGen.eyes.currEyesNr > 0) {
changePart(-1);
}
});
Related
I've seen lots of questions about passing objects by reference in Javascript, but not the object and properties by reference. Is it possible?
Right now I only found a way to do it by going through some type of logic like this, which is terribly inconvenient:
let multipliers = {
none:1,
sin:2,
cos:3,
tan:4,
atan:5,
}
incMultiplier(shapesMovements[index], "rotation", "x", "sin")
function incMultiplier(shapeMovement, kind, dimension, multiplier){
var numOfKeys = Object.keys(multipliers).length;
if(kind === "rotation"){
if(dimension === "x"){
if(multiplier === "sin"){
if(shapeMovement.rotation.x.multiplier !== numOfKeys){
shapeMovement.rotation.x.multiplier += 1
}else{
shapeMovement.rotation.x.multiplier = 1
}
}
}
}
}
I'd just like to increase the property value by one with whatever object and property I've thrown into that function.
I've seen another post where you can pass parameters, but this looks to assemble a new object, and is not by reference. I need to actually edit the values on the object's properties.
Originally, this is what I was trying, and it did not seem to alter the object on a global level. Only locally to the function:
incMultiplier(shapesMovements[index].rotation.x.multiplier)
function incMultiplier(multiplier){
var numOfKeys = Object.keys(multipliers).length;
if(multiplier !== numOfKeys){
multiplier = multiplier + 1
}else{
multiplier = 1
}
// always results in the same number.
// Does not keep increasing every time the function is called.
console.log(multiplier);
}
Originally, this is what I was trying
You're not passing an object with its properties there. You're passing the value of a single property, and assignments to multiplier do indeed just overwrite the local variable in the function. You need to pass an object and explicitly assign to its property:
function incMultiplier(valueObj) {
var numOfKeys = Object.keys(multipliers).length;
if (valueObj.multiplier !== numOfKeys) {
valueObj.multiplier++;
} else {
valueObj.multiplier = 1
}
}
incMultiplier(shapesMovements[index].rotation.x)
incMultiplier(shapesMovements[index].position.x)
incMultiplier(shapesMovements[index].rotation.y)
incMultiplier(shapesMovements[index].rotation.z)
It's not necessary to pass the whole shapesMovements objects and everything nested within them, passing a single mutable object is enough.
I have an array that contains dates. and for some reason I can't get it to show on my screen I've been debugging for a few days now and I've tracked it down to a single line, but the line has worked before and I can't figure out what the issue might be.
The array looks like this:
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"...];
It's passed as an argument from another function, but that's how it's showing in console.log().
I might be going about this the wrong way, maybe even a lot further around then I need to but this is what I've come up with:
1. function setTHead(selectItems) {
2 var formatString;
3. for (var x = 0; x < 12; x++) {
4. formatString = selectItems[x].replace(/[^0-9/-]/g, "").toString();
5. console.log(selectItems);
6. $('#datTab').append("<div id='col" + x + "' class='column'>'" + formatString + "'</div>");
7. }
8. }
the array up top is what's showing from the console.log 5 lines down.
the sixth line is what is seeming to give me issues. Nothing is put on the page at all.
I'm getting a console error saying:
jQuery.Deferred exception: selectItems is undefined setTHead#http://localhost/mySite/script.js:136:9
startUp2#http://localhost/mySite/script.js:146:5
#http://localhost/mySite/table.php:19:9
mightThrow#http://localhost/mySite/lib/jquery.js:3586:52
resolve/</process<#http://localhost/mySite/lib/jquery.js:3654:49
setTimeout handler*resolve/<#http://localhost/mySite/lib/jquery.js:3692:37
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
fire#http://localhost/mySite/lib/jquery.js:3458:21
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
ready#http://localhost/mySite/lib/jquery.js:3923:13
completed#http://localhost/mySite/lib/jquery.js:3933:9
EventListener.handleEvent*#http://localhost/mySite/lib/jquery.js:3949:9
#http://localhost/mySite/lib/jquery.js:39:9
#http://localhost/mySite/lib/jquery.js:17:3
undefined
followed by:
TypeError: selectItems is undefined
and thats pointing to line 6.
if anyone has any advice I would be very much appreciative. Thank you in advance.
EDIT: A little more code:
function startTblView(defSel) {
if (defSel === true) {
setCookie('defSel', true, 7);
} else{
setCookie('defSel', false, 7);
}
saveSelected();
window.open('table.php', '_self');
defSel = getCookie('defSel');
if (defSel) {
selectItems = getDefDates();
}else {
selectItems = reGetSelected();
}
setTHead(selectItems);
}
defSel, is a boolean passed from my last page stating whether I'm doing a default view or a custom view, the custom view is passed from saveSelected();
saveSelected is a function for just saving the selected global value as a cookie so I can pull it out on the next page.
getDefDates pulls the default values for the array
reGetSelected, gets the selected array from the cookie.
I apologize for wonky naming conventions. I'm the only one working on this site and I'm just making sure the names don't overlap.
You can do this :
HTML code
<div id="datTab"></div>
JS code
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"];
function setTHead(selectItems) {
var formatString;
$.each( selectItems, function( index, value ){
formatString = value.replace(/[^0-9/-]/g, "").toString();
$('#datTab').append("<div id='col" + index + "' class='column'>'" + value + "'</div>");
});
};
You can use $.each, its better than 'for' with javascript.
The .each() method is designed to make DOM looping constructs concise
and less error-prone. When called it iterates over the DOM elements
that are part of the jQuery object. Each time the callback runs, it is
passed the current loop iteration, beginning from 0. More importantly,
the callback is fired in the context of the current DOM element, so
the keyword this refers to the element.
I did a JsFiddle
Here.
I have a lot of images that are individually updating a drop down selection box. I've been using the following: (changing the replaceContent(number) and a different selectIndex = (number) inside a separate function for each image).
function replaceContent9() {
document.getElementById("ecwid-productoption-8840317-Backgrounds")
.selectedIndex = 0 ; }
I call the function like this:
javascript:replaceContent9
How would I do this with an array so that I might have just one function that is used for all the images. So far not so good at figuring out arrays. Maybe someone could point me in the right direction or suggest a code to try.
I'd go with a hash/lookup object:
var replaceContent = (function() {
var info = {
0: 'ecwid-productoption-8840317-Backgrounds',
1: 'foobar-product',
9: 'whhooott rage'
};
return function( which ) {
document.getElementById( info[ which ] ).selectedIndex = 0 ;
};
}());
This object in its current form pretty much looks like a javascript array because of indexed key names, but for a more general use you should do it like this.
Now you can just call that function like
replaceContent( 9 );
I have a set of variables that I use to tell if an question in a survey has been answered, and they are initially set to false: q0=false; q1=false; qX=false;. Then in the body, I have several input type="radio"s with .change(function(){ counter_radio(q#,INPUT'S_ID); }); binded to each. This is the code for function counter_radio():
function counter_radio(q,id){
if (q===false) {
q=true;
count++;
$("#counter").css("width", count);
$("#percent").text(count + "%");
}
$("span#"+id).append(", "+q);
$("span#"+id).fadeIn();
}
I expect that the counter to only increment once (the first time a radio from a set is selected). However, the counter is incrementing every time the input type=radio changes. My problem is that, q returns true, but q# remains false. In php, I would make q a reference to q# so when q is set to true, q# is also set to true. Is there such a construct in javascript?
You could change your bound function to:
.change(function(){ q# = counter_radio(q#,INPUT'S_ID); });
and add a return q; to the end of your counter_radio function.
As others have said, simple variables like booleans are passed by value, not by reference in Javascript so there is no way to change that.
If you have multiple variables like this:
var q0=false, q1=false, qX=false;
then, it might be easier to put them in an array:
var qArray = [false, false, false];
Then, because arrays and objects are passed in a way that you can modify the original, you can pass the array and an index to your function like this and you are then able to modify the original data from your function:
function counter_radio(q, index, id){
if (q[index] === false) {
q[index] = true;
count++;
$("#counter").css("width", count);
$("#percent").text(count + "%");
}
$("span#"+id).append(", " + q[index]);
$("span#"+id).fadeIn();
}
counter_radio(qArray, 2, id);
There's no native syntax for that, but you can e.g. use objects for that:
function counter_radio(q_,id){
if (q_.ref===false) {
q_.ref=true;
count++;
$("#counter").css("width", count);
$("#percent").text(count + "%");
}
$("span#"+id).append(", "+q);
$("span#"+id).fadeIn();
}
I would collect the values when the user submits the form:
jQuery('radio[name=...]:checked').val()
If you want to warn the user that not all questions have been answered, use the jQuery validation plugin.
I used Firefug to profile my web application and found that the following function is called, and needs to be called, literally hundreds of times per user visit. So I want to optimize it since Firebug says it uses the most resources/times.
function highlightChildDiv(parentDiv) {
/* find the closest (hlisting) home listing to the middle of the scrollwindow & highlight */
var scrollElemPos = parentDiv.offset();
var highlightDiv = $(document.elementFromPoint(
scrollElemPos.left + parentDiv.width() / 2,
scrollElemPos.top + parentDiv.height() / 2)
).closest('#parentDiv div.childClass');
if (highlightDiv.hasClass("HighlightRow")) {
return; // if the div is already highlighted, return
} else {
$('#parentDiv div.childClass').removeClass("HighlightRow");
highlightDiv.addClass('HighlightRow');
}
}
Seems to me that one of the most un-optimized statements is .closest('#parentDiv div.childClass');, but I'm sure there is other things to improve.
Question: Does anyone have any JQuery performance tips on how I can optimize the code above given that this function is run literally hundreds of times per user visit.
First thought, eliminate the dead statement in the if clause.
if (!highlightDiv.hasClass("HighlightRow")) {
$('#parentDiv div.childClass').removeClass("HighlightRow");
highlightDiv.addClass('HighlightRow');
}
In the selector #parentDiv div.childClass, can you guarantee that div will be a direct descendent of #parentDiv? In which case:
.closest('#parentDiv>div.childClass');
and
$('#parentDiv>div.childClass')
You already have parentDiv. I'm guessing this is a DOM object, so you may be able to do the following:
$(parentDiv).children("div.childClass")
Just hide the DIV that is currently highlighted:
$('#parentDiv div.HighlightRow').removeClass("HighlightRow");
My guess is this is the most unoptimized line:
$('#parentDiv div.childClass').removeClass("HighlightRow");
You should profile it to make sure (create a date object outside the call and output the getTime() value before and after each call).
Here you are asking jQuery to iterate over all DOM elements that match that selector and remove the class. If there are 1000 rows, jQuery will need to interogate each one to see if it needs to remove a class. Ugh. Here it is with that lookup removed:
// namespace scoped cache
var Hash_$_Cache = {
$parentDiv : $('#parentDiv'),
$tgt_row : $([]) // empty jq object to start
};
// find the closest (hlisting) home listing to the middle of
// the scrollwindow and highlight
//
var highlightChildDiv = function (parentDiv){
var
scrollElemPos = parentDiv.offset(),
$tgt_row
;
$tgt_row = $(document.elementFromPoint(
scrollElemPos.left + parentDiv.width() / 2,
scrollElemPos.top + parentDiv.height() / 2)
).closest('#parentDiv div.childClass')
;
// bail if row is already highlighted
if ($tgt_row.hasClass('HighlightRow')){ return; }
Hash_$_Cache.$tgt_row.removeClass('HighlightRow');
$tgt_row.addClass('HighlightRow');
// save highlighted row for later
Hash_$_Cache.$tgt_row = $tgt_row; // store new row in cache
};
Hope that helps!
I prefer to use the following methodology:
https://gist.github.com/3841424#file-domcache-js
Or, you may replace the DOM object with a method in this implementation:
var myNS = {
myEventHandler: function(event){
this.DOM.$el.doSomething();
},
cacheDOM: function(){
return {
$el: $("#matrix")
};
},
initialize: function(){
this.DOM = this.cacheDOM();
}
};