Function added on link with jQuery - javascript

I am trying to create multiple html links to use as buttons. I am creating 15 at the moment, and in the future I might need to add more, so I want to do that through JS. The following code adds the objects on the screen, but the buttons don't do the function assigned. In fact, they don't do anything at all, they just go to index.html#.
function buttonCreate(){
var element = $(".left");
for (i in upgrades){
a = $('<a>',{
text: i,
href: '#',
id: i
});
var upgrade_button = $('#' + i);
upgrade_button.click(function(){upgrade(i);return false;});
a.addClass('myButton');
a.appendTo(element);
para = $('<p>',{
text: "",
id: i+"_upgrade"
});
para.appendTo(element);
para2 = $('<p>',{
text: "",
id: i+"_income"
});
para2.appendTo(element);
document.getElementById(i+"_upgrade").innerHTML = "You need " + Math.round(upgrades[i].cost).toLocaleString() + " to upgrade " + i;
document.getElementById(i+"_income").innerHTML = "Next " + i + " give " + Math.round(upgrades[i].income).toLocaleString() + " passive income";
}
}
I have also tried adding the function directly to the freshly created link element.
EDIT:
I am giving you the upgrades var and the upgrade function:
var upgrades = {
'A': {
cost: 100,
income: 10
},
'B': {
cost: 1000,
income: 100
},
'C': {
cost: 10000,
income: 1000
};
And the function:
function upgrade(type){
if ((points - upgrades[type].cost) >= 0) {
points -= upgrades[type].cost;
upgrades[type].cost *= upgrade_incr;
pincome += upgrades[type].income;
clearInterval(intervalVar);
intervalVar = setInterval(function(){pas_incr(pincome/8);},125);
//upgrades[type].income *= val_incr;
display = Math.round(points).toLocaleString();
document.getElementById("points").innerHTML = "Points: " + display;
document.getElementById(type+"_upgrade").innerHTML = "You need " + Math.round(upgrades[type].cost).toLocaleString() + " to upgrade " + type;
document.getElementById(type+"_income").innerHTML = "Each " + type +" gives " + Math.round(upgrades[type].income).toLocaleString() + " passive income";
document.getElementById("pincome").innerHTML = "You have " + Math.round(pincome).toLocaleString() + " passive income";
}
}
The fact that I am using both JS and Jquery is that I just learnt jquery, and started implementing that. As a note, I couldn't get it to work with simple JS either.

You are trying to query your button before inserting it into the DOM (upgrade_button). But you already have the button in your local scope (a). Simply add the click property during button creation.
Instead of:
a = $('<a>',{
text: i,
href: '#',
id: i
});
var upgrade_button = $('#' + i);
upgrade_button.click(function(){upgrade(i);return false;});
Try:
var a = $('<a>',{
text: i,
href: '#',
id: i,
click: function(){upgrade(i);return false;}
});
UPDATE
As TrueBlueAussie states in the comments, you cannot use i in your click closure because it will assume the final value of your for loop at the point in time when click is actually called.
New version:
var a = $('<a>',{
text: i,
href: '#',
id: i,
click: function(){ upgrade(this.id); return false; }
});

You're doing var upgrade_button = $('#' + i); before the element has been inserted into the DOM tree, so jQuery won't be able to look it up using the ID selector like that. Thus upgrade_button doesn't represent anything when you attach the click handler to it.
Also, upgrade_button will be identical to the a variable, so you can scrap it completely and just use a instead.

Related

JS/jQuery Recalculate object constructor values from button onclick->prompt

I am having trouble dynamically updating values in a jQuery-generated table.
I inserted a button next to values that the contents of adjacent <td> are calculated from, with the intent that the .click(function(){ prompt("...","...")} would not only update the value of interest (hours, avgCustomers, avgPurchase), but also recalculate the dependent <td>.
The contents of the table are calculated on $(document).ready from an object constructor, using arrays for the input values (see Shops.js # http://github.com/jacobshillman/Week2A2.git, and/or below). In the git, 'index.html' is the page of interest.
A click event updates the value displayed, but I haven't been able to figure out how to get the rest of the table to recalculate based on the new value.
//Donut Shop Constructor
function Shop (loc, hours, minCust, maxCust, avgDonCust, avgCust, donHr, donDay) {
this.loc = loc;
this.hours = hours;
console.log(this.loc + ", Hours: " + this.hours);
this.minCust = minCust;
this.maxCust = maxCust;
this.avgDonCust = avgDonCust;
//Random sample of average customers per hour:
this.avgCust = getCustpHr(this.hours, this.minCust, this.maxCust);
console.log(this.loc + ", Customers per hour: " + this.avgCust);
//Donuts to bake per hour:
this.donHr = getDon(this.avgCust, this.avgDonCust)
console.log(this.loc + ", Donuts to bake per hour: " + this.donHr);
//Donuts to bake per day:
this.donDay = getSum (this.donHr)
console.log(this.loc + ", Donuts to bake per day: " + this.donDay);
};
//Donut Shops declaration:
var Shops = [5];
Shops[0] = new Shop("Downtown", 8, 8, 43, 4.5);
Shops[1] = new Shop("Capitol Hill", 24, 4, 37, 2);
Shops[2] = new Shop("South Lake Union", 10, 9, 23, 6.33);
Shops[3] = new Shop("Wedgewood", 7, 2, 28, 1.25);
Shops[4] = new Shop("Ballard", 10, 8, 58, 3.75);
//Populate index.html with Donut shops stats:
$(document).ready(function(){
$.each(Shops, function(){
$row = $('<tr></tr>');
$('#shopGrid tbody').append($row);
$row.append($('<td><ul>' + this.loc + '<li>Hours Open: ' +
'<span id="Hour">' + this.hours + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li>'
+ '<li>Average Purchase:' +
'<span id="Purch">' + this.avgDonCust + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li>'
+ '<li>Store Traffic:' +
'<span id="Traffic">' + this.minCust + '-' + this.maxCust + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li></ul>'))
//.slice() is inserted to control formatting:
$row.append($('<td>' + this.avgCust.slice(0,8) + '<br>'
+ this.avgCust.slice(8,16) + '<br>'
+ this.avgCust.slice(16) +'</td>'));
$row.append($('<td>' + this.donHr.slice(0,8) + '<br>'
+ this.donHr.slice(8,16) + '<br>'
+ this.donHr.slice(16) + '</td>'));
$row.append($('<td>' + this.donDay + '</td>'));
});
//Shop stats EDIT buttons:
//If else for EDIT button fX:
$(".edit").click(function(){
var test = $('#Hour').text();
console.log(test);
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if (newHours <= 24) {
$('#Hour').text(newHours);
test = newHours
}else{
alert('Must be a number between 0 to 24.')
}
console.log(test);
console.log(document.getElementById('Hour'));
});
/*
$(".edit").click(function(){
if ($('#Hour')) {
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if (newHours <= 24) {
$('#Hour').value(newHours);
}else{
alert('Must be a number between 0 to 24.')
}
}else if ($('#Purch')) {
var newVal = prompt("Enter new value:", "Enter number");
$(this.avgDonCust).val(newVal);
}else if ($('#Traffic')) {
var newVal = prompt("Enter first value:", "Enter number");
$(this.minCust).value(newVal)
var val2 = prompt("Enter second value", "Enter number");
$(this.maxCust).value(Val2);
};
});
*/
/*
//Switch for EDIT buttons fX:
$(".edit").click(function (){
switch (n){
case $('#Hour')
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if newHours <= 24 {
$(this.hours).val(newHours);
}else{
alert('Must be a number between 0 to 24.');
break;
case $('Purch'):
var newVal = prompt("Enter new value:", "Enter number");
$(this.avgDonCust).val(newVal);
case $('#Traffic'):
var newVal = prompt("Enter first value:", "Enter number");
$(this.minCust).val(newVal);
function() {
var val2 = prompt("Enter second value", "Enter number");
$(this.maxCust).val(Val2);
};
break;
}
*/
});
The code commented out and in console.log() are my attempts at troubleshooting and getting the table to recalculate.
Best I can figure is that I need to update the corresponding value in the 2-D array Shops - Shops[this][[1] to update the "Hours Open:" of any given shop, then triggering the calculations again, without reloading the page.
I tried in an earlier (failed) attempt to use objects instead of arrays, but descended into defining jQuery var=s hell because I had to hand-jam all the object names into html and jQuery. This attempt can be seen in the 'Legacy' directory in the .git, above.
I've seen updateTo() and recompute() advice on Stack Overflow, but I'm not sure whether/how I'd be able to integrate them.
Any input is greatly appreciated! I'm not opposed to scrapping things and starting from scratch: this is a learning exercise.
I'm not able to make a working example as I'm pressed for time, but I can give you a few suggestions. These suggestions are only for if you're not coding a reusable library.
I'd create properties (functions) that recompute other properties and/or the html.
var _total = 0, _tax = 0, _grand = 0;
function totalChange() {
$('#prop1').val(_prop1);
tax(_total * .08)
_.debounce(_grand);
}
function total(val) {
if(val) {
_total = val;
totalChange();
}
return _total;
}
Then you can create a getter and setter for each of your properties that sets html or other properties.
I would actually recommend emberjs or angular or backbone as they provide the exact functionality you're looking for.
I was bored today and created a jsfiddle for you using ember. Let me know if you have an questions.

toMatch Not Working

Does anyone know why this isn't passing?
function correctColorDisplay(message, player_turn, selector) {
if ((message > 0) && (player_turn != 0)) {
return $(selector).append("<li>" + message + " " + "color(s) are present but not in the correct position in Round " + player_turn + ".</li>");
}
}
Jasmine:
describe('#correctColorDisplay', function(){
it('returns a message to the user displaying if a correct color (not positions) was chosen', function(){
var message = 2
var playerTurn = 2
var selector = $('<li></li>')
correctColorDisplay(message,playerTurn, selector)
expect(selector).toMatch("<li>" + message + " " + "color(s) are present but not in the correct position in Round " + playerTurn + ".</li>")
});
});
The error I keep getting is this giant message: Expected { 0 : HTMLNode, length : 1, jquery : '1.11.0', constructor : Function, selector : '', toArray : Function, get : Function, pushStack : Function, each, etc (it goes on much longer)
You are trying to match a newly created HTMLNode with a regular expression (that is basically just a string in this case).
The toMatch function of Jasmine is for regular expressions.
I'm not entirely familiar with Jasmine, but I'm guessing you're looking for something like:
describe('#correctColorDisplay', function(){
it('returns a message to the user displaying if a correct color (not positions) was chosen', function() {
var message = 2;
var playerTurn = 2;
var selector = $('<li></li>');
selector = correctColorDisplay(message, playerTurn, selector);
expect(selector).toEqual( $("<li><li>" + message + " " + "color(s) are present but not in the correct position in Round " + playerTurn + ".</li></li>") );
});
});
If that doesn't work, I suggest you look into jasmine-jquery.

go to the next index of an array using onclick in Javascript

I apologize in advance if I'm vague or my code is difficult to understand, I'm still learning this stuff. I'm trying to display information that is stored within an array. I want to display this information when a button is clicked and when it is clicked again, the next index in the array displays its information..
I need help setting up a function that advances to the next index of the array. Thanks!
(function(){
var students =[ //array of information
{name:'john',
address:{
address:'821 Imaginary St',
city:'Chicago',
state:'Il'},
gpa:[4.0,3.5,3.8]},
{name:'jim',
address:{
address:'127 fake Rd',
city:'Orlando',
state:'Fl'},
gpa:[2.5,3.3,3.6]}];
var redBut = document.querySelector('.buttonred');
redBut.onclick = getInfo;
var count = 0;
function getInfo(){
var stn = students[0];
if(count<3){
count++;
document.getElementById('name').innerHTML = 'Name: ' + stn.name; //this is what is to be displayed when the button is clicked
document.getElementById('address').innerHTML = 'Address: ' + stn.address.address + " " + stn.address.city + ", " + stn.address.state;
document.getElementById('gpa').innerHTML = 'GPA: ' + stn.gpa[0] +", " + stn.gpa[1] + ", " + stn.gpa[2];
document.getElementById('date').innerHTML = 'Date: ' + d.toLocaleDateString();
document.getElementById('gpaavg').innerHTML = 'Average GPA: ' + gpas;
}
}
I think you want: var stn = students[count];
And not: var stn = students[0];
(DOH!)

Avoiding use of eval() to dynamically build event handlers

I'm struggling with managing dynamically built event handlers in javascript.
In several places, I build forms, or controls in which specific events (mainly mouseovers, mouse-outs, clicks) need to be handled.
The trick is that in a significant number of cases, the event handler itself needs to incorporate data that is either generated by, or is passed-into the function that is building the form or control.
As such, I've been using "eval()" to construct the events and incorporate the appropriate data, and this has worked somewhat well.
The problem is I keep seeing/hearing things like "You should never use eval()!" as well as a couple of increasingly ugly implementations where my dynamically-built event handler needs to dynamically build other event handlers and the nested evals are pretty obtuse (to put it mildly).
So I'm here, asking if someone can please show me the better way (native javascript only please, I'm not implementing any third-party libraries!).
Here's a crude example to illustrate what I'm talking about:
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
eval("inp.onfocus = function() { this.style.cssText = '" + activeStyle + "'; }");
eval("inp.onblur = function() { this.style.cssText = '" + dormantStyle + "'; }");
eval("inp.onclick = function() { " + whenClicked + "; }");
return inp;
}
This function obviously would let me easily create lots of different INPUT tags and specify a number of unique attributes and event actions, with just a single function call for each. Again, this is an extremely simplified example, just to demonstrate what I'm talking about, in some cases with the project I'm on currently, the events can incorporate dozens of lines, they might even make dynamic ajax calls based on a passed parameter or other dynamically generated data. In more extreme cases I construct tables, whose individual rows/columns/cells may need to process events based on the dynamically generated contents of the handler, or the handler's handler.
Initially, I had built functions like the above as so:
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
inp.onfocus = function() { this.style.cssText = activeStyle; };
inp.onblur = function() { this.style.cssText = dormantStyle; };
eval("inp.onclick = function() { " + whenClicked + "; }");
return inp;
}
...but I found that whatever the last assigned value had been for "activeStyle", and "dormantStyle" became the value used by all of the handlers thusly created (instead of each retaining its own unique set of styles, for example). That is what lead me to using eval() to "lock-in" the values of the variables when the function was created, but this has lead me into nightmares such as the following:
(This is a sample of one dynamically-built event-handler that I'm currently working on and which uses a nested eval() function):
eval("input.onkeyup = function() { " +
"InputParse(this,'ucwords'); " +
"var tId = '" + myName + This.nodeName + "SearchTable" + uidNo + "'; " +
"var table = document.getElementById(tId); " +
"if (this.value.length>2) { " +
"var val = (this.value.indexOf(',') >=0 ) ? this.value.substr(0,this.value.indexOf(',')) : this.value; " +
"var search = Global.LoadData('?fn=citySearch&limit=3&value=' + encodeURI(val)); " +
"if (table) { " +
"while (table.rows.length>0) { table.deleteRow(0); } " +
"table.style.display='block'; " +
"} else { " +
"table = document.createElement('table'); " +
"table.id = tId; " +
"ApplyStyleString('" + baseStyle + ";position=absolute;top=20px;left=0px;display=block;border=1px solid black;backgroundColor=rgba(224,224,224,0.90);zIndex=1000;',table); " +
"var div = document.getElementById('" + divName + "'); " +
"if (div) { div.appendChild(table); } " +
"} " +
"if (search.rowCount()>0) { " +
"for (var i=0; i<search.rowCount(); i++) { " +
"var tr = document.createElement('tr'); " +
"tr.id = 'SearchRow' + i + '" + uidNo + "'; " +
"tr.onmouseover = function() { ApplyStyleString('cursor=pointer;color=yellow;backgroundColor=rgba(40,40,40,0.90);',this); }; " +
"tr.onmouseout = function() { ApplyStyleString('cursor=default;color=black;backgroundColor=rgba(224,224,224,0.90);',this); }; " +
"eval(\"tr.onclick = function() { " +
"function set(id,value) { " +
"var o = document.getElementById(id); " +
"if (o && o.value) { o.value = value; } else { alert('Could not find ' + id); } " +
"} " +
"set('" + myName + This.nodeName + "CityId" + uidNo + "','\" + search.id(i)+ \"'); " +
"set('" + myName + This.nodeName + "ProvId" + uidNo + "','\" + search.provId(i)+ \"'); " +
"set('" + myName + This.nodeName + "CountryId" + uidNo + "','\" + search.countryId(i) + \"'); " +
"set('" + input.id + "','\" + search.name(i)+ \"'); " +
"}\"); " +
"var td = document.createElement('td'); " +
"var re = new RegExp('('+val+')', 'gi'); " +
"td.innerHTML = search.name(i).replace(re,'<span style=\"font-weight:bold;\">$1</span>') + ', ' + search.provinceName(i) + ', ' + search.countryName(i); " +
"tr.appendChild(td); " +
"table.appendChild(tr); " +
"} " +
"} else { " +
"var tr = document.createElement('tr'); " +
"var td = document.createElement('td'); " +
"td.innerHTML = 'No matches found...';" +
"tr.appendChild(td); " +
"table.appendChild(tr); " +
"} " +
"} else { " +
"if (table) table.style.display = 'none'; " +
"} " +
"} ");
Currently, I'm having problems getting the nested eval() to bind the ".onclick" event to the table-row, and, as you can see, figuring out the code is getting pretty hairy (debugging too, for all the known reasons)... So, I'd really appreciate it if someone could point me in the direction of being able to accomplish these same goals while avoiding the dreaded use of the "eval()" statement!
Thanks!
And this, among many other reasons, is why you should never use eval. (What if those values you're "baking" in contain quotes? Oops.) And more generally, try to figure out why the right way doesn't work instead of beating the wrong way into submission. :)
Also, it's not a good idea to assign to on* attributes; they don't scale particularly well. The new hotness is to use element.addEventListener, which allows multiple handlers for the same event. (For older IE, you need attachEvent. This kind of IE nonsense is the primary reason we started using libraries like jQuery in the first place.)
The code you pasted, which uses closures, should work just fine. The part you didn't include is that you must have been doing this in a loop.
JavaScript variables are function-scoped, not block-scoped, so when you do this:
var callbacks = [];
for (var i = 0; i < 10; i++) {
callbacks.push(function() { alert(i) });
}
for (var index in callbacks) {
callbacks[index]();
}
...you'll get 9 ten times. Each run of the loop creates a function that closes over the same variable i, and then on the next iteration, the value of i changes.
What you want is a factory function: either inline or independently.
for (var i = 0; i < 10; i++) {
(function(i) {
callbacks.push(function() { alert(i) });
})(i);
}
This creates a separate function and executes it immediately. The i inside the function is a different variable each time (because it's scoped to the function), so this effectively captures the value of the outer i and ignores any further changes to it.
You can break this out explicitly:
function make_function(i) {
return function() { alert(i) };
}
// ...
for (var i = 0; i < 10; i++) {
callbacks.push(make_function(i));
}
Exactly the same thing, but with the function defined independently rather than inline.
This has come up before, but it's a little tricky to spot what's causing the surprise.
Even your "right way" code still uses strings for the contents of functions or styles. I would pass that click behavior as a function, and I would use classes instead of embedding chunks of CSS in my JavaScript. (I doubt I'd add an ID to every single input, either.)
So I'd write something like this:
function create_input(id, type, active_class, onclick) {
var inp = document.createElement('input');
inp.id = id;
inp.type = type;
inp.addEventListener('focus', function() {
this.className = active_class;
});
inp.addEventListener('blur', function() {
this.className = '';
});
inp.addEventListener('click', onclick);
return inp;
}
// Called as:
var textbox = create_input('unique-id', 'text', 'focused', function() { alert("hi!") });
This has some problems still: it doesn't work in older IE, and it will remove any class names you try to add later. Which is why jQuery is popular:
function create_input(id, type, active_class, onclick) {
var inp = $('<input>', { id: id, type: type });
inp.on('focus', function() {
$(this).addClass(active_class);
});
inp.on('blur', function() {
$(this).removeClass(active_class);
});
inp.on('click', onclick);
return inp;
}
Of course, even most of this is unnecessary—you can just use the :focus CSS selector, and not bother with focus and blur events at all!
You don't need eval to "lock in" a value.
It's not clear from the posted code why you're seeing the values change after CreateInput returns. If CreateInput implemented a loop, then I would expect the last values assigned to activeStyle and dormantStyle to be used. But even calling CreateInput from a loop will not cause the misbehavior you describe, contrary to the commenter.
Anyway, the solution to this kind of stale data is to use a closure. JavaScript local variables are all bound to the function call scope, no matter if they're declared deep inside the function or in a loop. So you add a function call to force new variables to be created.
function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked)
{
while ( something ) {
activeStyle += "blah"; // modify local vars
function ( activeStyle, dormantStyle ) { // make copies of local vars
var inp = document.createElement('input');
inp.id = controlName;
inp.type = type;
inp.style.cssText = dormantStyle;
inp.onfocus = function() { this.style.cssText = activeStyle; };
inp.onblur = function() { this.style.cssText = dormantStyle; };
inp.onclick = whenClicked;
}( activeStyle, dormantStyle ); // specify values for copies
}
return inp;
}

Another jQuery autocomplete issue

So, i have read at least 20-30 auto complete problems here on so and i cannot find any solutions. For some odd reason i keep getting value = undefined. Here is my code.
//Cycles through each input and turns it into a person searcher.
$.each(settings.input, function() {
var input = $(this);
input.autocomplete({
delay: 70,
minLength: 2,
source: function(req, add) {
var val = input.val();
$.post(VUI.SITE_URL + "scripts/autocomplete/_AutoComplete.php", {q: val, display_count: settings.displayCount, action: "user"}, function(data) {
data = eval("(" + data + ")");
if (data.length > 0) {
var results = new Array(data.length);
$.each(data, function(key, value) {
results[key] = {desc: value, value: value.firstname + " " + value.lastname};
});
add(results);
} else {
add(["No results..."]);
}
});
},
select: function(event, ui) {
alert(ui.item ? ("Selected: " + ui.item.value + " aka " + ui.item.id) : "Nothing selected, input was " + this.value);
}
}) // end auto complete.
.data("autocomplete")._renderItem = function($ul, item) {
var $li = $("<li></li>"),
$inner = $("<div class='st-display side-content clearfix'style='padding-top:6px'></div>"),
$a = $("<a></a>"),
$img = $("<div class='image fl'></div>").html(ST.Image.getImage({
uid: item.desc.uid,
type: ST.ST_IMAGE_TYPE_THUMBNAIL_SMALL
})),
$content = $("<div class='content fl'></div>").html(
item.desc.firstname + " " + item.desc.lastname + "<br/>" +
"<span class='color:#979797;font-weight:bold'>" + item.desc.city + ", " + item.desc.state + "</span>"
);
$inner.append($img).append($content);
$a.append($inner);
$li.append($a);
$ul.append($li);
return $ul;
} // end _renderItem */
I tried to make it so that its very straight forward. But it wont work! (its facebook like auto complete). The auto complete displays properly (item does not equal undefined at that point), but when i highlight it, item becomes undefined so item.value (line 6347 of jquery.ui.1.8.13) throws exception!
Anyone see problems?
Here is something interesting... When i do not use data("autocomplete")._renderItem (for custom completion) the selecting works! ... So why does overriding the custom rendering cause issues? I am even returning the UL.
The only thing in your code that's different from a working version I've got of something very similar is that I initialise $li with:
var $li = $( '<li></li>' ).data('item.autocomplete', item);
That attaches the data to the list item which I think the autocomplete plugin uses to get the value at selection time.
Hope it helps

Categories