I have a small Issue Tracker which uses a couple of functions to save and fetch issues to localStorage, all of which works fine, using the Chance uid generator to generate ids. However on making a third function to set the status to closed after getting the id which was generated previously. In console I get an error as below.
(function(event){setStatusClosed(fdf7622a-8738-5384-98b6-9ff9c47b2be0)
}) invalid or unexpected token. index.html:1
It's using onclick to run the function via the close button generated in the fetchIssues() function.
The first 2 functions fetchIssues() and saveIssues() have been working as expected with no errors and correctly converting the array to JSON object on push and back to array on retrieve, but when I try the setStatusClosed function I get the error. I also get similar errors when using other uid generator methods so I decided to stick with Chance. I can't seem to find the error and the log just points me to the event as pasted above. The code is below
/*jslint browser:true */
//Fetch submitted issues or the status of localStorage
function fetchIssues() {
"use strict";
var i = 0;
var issues = JSON.parse(localStorage.getItem("issues"));
var issueList = document.querySelector('#issueList');
issueList.innerHTML = " ";
if(issues !== null) {
for(i = 0; i < issues.length; i++) {
var id = issues[i].id,
desc = issues[i].description,
severity = issues[i].severity,
assignedTo = issues[i].assignedTo,
status = issues[i].status;
issueList.innerHTML += "<div class='well'>" +
"<h6>Issue ID: " + id + "</h6>" +
"<p><span class='Label label-info'>" + status + "</span></p>" +
"<h3>" + desc + "</h3>" +
"<p><span class='glyphicon glyphicon-time'></span>" + severity + "</p>" +
"<p><span class='glyphicon glyphicon-user'></span>" + assignedTo + "</p>" +
"<a href='#' onclick='setStatusClosed("+ id +")' class='btn btn-warning'>Close</a>" +
"<a href='#' onclick='deleteIssue(" + id + ")' class='btn btn-danger'>Delete</a>" +
"</div>";
}
console.log(issues);
} else {
issueList.innerHTML = "<h6 class='text-center'>There are no issues at present</h6>";
}
}
//Save a submitted issue
function saveIssue(e) {
"use strict";
var chance = new Chance();
var issueDesc = document.querySelector("#issueDescInput").value,
issueSeverity = document.querySelector("#issueSeverityInput").value,
issueAssignedTo = document.querySelector("#issueAssignedToInput").value,
issueStatus = "Open",
issueId = chance.guid(),
issues = [],
issue = {
id: issueId,
description: issueDesc,
severity: issueSeverity,
assignedTo: issueAssignedTo,
status: issueStatus
};
//Check if entry already there
if(localStorage.getItem("issues") === null) {
//Push the issue object to issues array
issues.push(issue);
//Set the new issues array as a converted JSON object to localStorage
localStorage.setItem("issues", JSON.stringify(issues));
} else {
//Request the issues object and convert to array
issues = JSON.parse(localStorage.getItem("issues"));
//Push new object to issues array
issues.push(issue);
//Set the new issues array as a converted JSON object to localStorage
localStorage.setItem("issues", JSON.stringify(issues));
}
//Reset submit element
document.querySelector("#issueInputForm").reset();
//Run fetchIssue to reflect the new item
fetchIssues();
//Stop default submit
e.preventDefault();
}
//Submit event for the saveIssue function
document.querySelector("#issueInputForm").addEventListener("submit", saveIssue);
//Set the issue status to closed
function setStatusClosed(id) {
var i;
var issues = JSON.parse(localStorage.getItem("issues"));
for(i = 0; i < issues.length; i++) {
if(issues[i].id == id) {
issues.status = "Closed";
}
}
localStorage.setItem("issues", JSON.stringify(issues));
fetchIssues();
}
It all compiles fine in the Closure compiler
The error appears only when hitting the close issue button, so does anyone know why the function is not being properly called in the onclick event (generated via innerHTML method) ?
Any tips welcome, Thanks
Update, I have tried altering the call in the generated innerHTML to escape the quotes but now I get a slightly different error that setStatusClosed() is not defined for the onclick event.(Also I removed Bootstrap so its just using the lib js file.), so there seems to be an error with the onclick call.
issueList.innerHTML += '<div class="well">' +
'<h6>Issue ID: ' + id + '</h6>' +
'<p><span class="Label label-info">' + status + '</span></p>' +
'<h4>' + desc + '</h4>' +
'<p><span class="icon icon-clock"></span> ' + severity + '</p>' +
'<p><span class="icon icon-user"></span> ' + assignedTo + '</p>' +
'Close' +
'Delete' +
'</div>';
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!)
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;
}
first time posting here, but god know's I use this site to search for problems all the time :P Well, I'm having a problem of my own now that I can't seem to figure out easily searching around Google, and after playing with it for about 2 hours, I've finally decided to post a question and see what you guys think.
What I'm trying to accomplish here is to have a button that appears over a div when you hover over it that, when clicked, opens an editing pane. The button appears over the div correctly, but for some reason I cannot seem to make the onclick function work to save my life lol. Here's the code I'm working with. If it's not enough, please let me know and I'll add a little more sauce. :P
function place_widget(name, properties){
//bbox
var pleft = properties[0];
var ptop = properties[1];
var width = properties[2];
var height = properties[3];
var pright = pleft + width;
var pbottom = pleft + height;
var bbox = [pleft, ptop, pright, pbottom];
boxes[name] = bbox;
//ID's
var id = 'widget_' + name;
var editspanid = id + "_editspan";
var editbuttonid = id + "_editbutton";
var editpaneid = id + "_editpane";
//Creating Elements
var div = "<div id='" + id + "' class='widget' onmouseover='widget_hover(event);' onmouseout='widget_out(event);'>";
var editbutton = "<a id='" + editbuttonid + "' href='#'><img onclick='toggleEdit;' src='../images/edit_button.png'></a>";
var editspan = "<span id='" + editspanid + "' class='editspan'>" + editbutton + "</span>";
var editpane = "<span id='" + editpaneid + "' class='editpane'>:)</span>";
div += editspan + editpane + "</div>";
body.innerHTML += div;
//Configuring Elements
var editspanelement = document.getElementById(editspanid);
var editbuttonelement = document.getElementById(editbuttonid);
editbuttonelement.onclick = alert; //Does nothing.
var editpaneelement = document.getElementById(editpaneid);
var mainelement = document.getElementById('widget_' + name);
mainelement.style.left = (pleft + leftmost) + "px";
mainelement.style.top = (ptop + topmost) + "px";
mainelement.style.width = width;
mainelement.style.height = height;
getContentsAJAX(name);
}
Sorry for the ugly code :P Anyone have any idea why the onclick function isn't working?
Also, a bit of extra info: If I open up firebug and put in :
document.getElementById('widget_Text_01_editbutton').onclick = alert;
When I click the button, I get:
uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "native frame :: <unknown filename> :: <TOP_LEVEL> :: line 0" data: no]
I'm not exactly sure what that means off hand.
Thanks!
Can you try changing:
<img onclick='toggleEdit;' src='../images/edit_button.png'>
to:
<img onclick='toggleEdit();' src='../images/edit_button.png'>
Also, is "alert" a function you wrote?
Start by changing this:
editbuttonelement.onclick = alert; //Does nothing.
to this:
editbuttonelement.onclick = function() {alert("Got a click");};
and then change this:
var editbutton = "<a id='" + editbuttonid + "' href='#'><img onclick='toggleEdit;' src='../images/edit_button.png'></a>";
to this:
var editbutton = "<a id='" + editbuttonid + "' href='#'><img onclick='toggleEdit();' src='../images/edit_button.png'></a>";
What you are clicking on? The image or the link or the span? Do you know which is getting the click event?
document.getElementById('widget_Text_01_editbutton').onclick = alert;
causes illegal invocation because it is trying to set this to something else (the element) than window inside alert.
alert.call( {} )
//TypeError: Illegal invocation
alert.bind( window ).call( {} )
//works
So either:
document.getElementById('widget_Text_01_editbutton').onclick = alert.bind( window );
or even better as the above will only work in some browsers:
document.getElementById('widget_Text_01_editbutton').onclick = function(){alert();};
It will help you in executing.
<img onclick='toggleEdit;' src='../images/edit_button.png'>
I've written the following code to get JSON data with a POST request.
$.post("http://example.com/songs/search_api/index.php",
"data[Song][keyword]=Stereophonics",
function(data){
/*$("#results").append(data);*/
alert("test");
var songdata = JSON.parse(data);
//$("#results").empty();
var i = 0;
for (i=0;i<=songdata.total;i++)
{
//alert(i);
var songhtml = "<ul><li><img src=\"" + songdata.data[i].artwork + "\" /></li><li>" + songdata.data[i].title + "</li><li>" + songdata.data[i].artist + "</li><li>" + songdata.data[i].length + "</li><li>" + songdata.data[i].listen + "</li></ul>";
//alert(songhtml);
$("#results").append(songhtml);
}
//var objectasstring = concatObject(songdata);
//alert(objectasstring + "\n\n" + songdata);
}
);
The problem is as soon as I put in a function (this works without the above code) the function fails to run;
function postRequest() {
alert("hello??");
}
This is for mobile Safari on the iPhone.
Thanks in advance.
Solved! My problem? The form. I was using a form so the page was being refreshed when the function was run.