Problem with dynamically generated JavaScript click events - javascript

I am trying to generate several divs which each should have a separate onclick handler. However, when I create multiple items in my application, all of the previous items have the same onclick event as the first one. Below is the code that generates the html based on an array or "Project" objects. How can I change this code to retain all individual click events?
var t1 = new Tracker(function(projects) {
var tempElem;
projectList.innerHTML = "";
for(i in projects) {
tempElem = document.createElement("div");
tempElem.setAttribute("id", projects[i].id);
tempElem.setAttribute("class", "project");
tempElem.innerHTML = projects[i].name;
projectList.appendChild(tempElem);
document.getElementById(projects[i].id).onclick = function() {
t1.setActiveProject(t1.getProjectById(projects[i].id)); };
}
}, setActiveProject);

Using jQuery will save you lots of time and headaches. I would recommend using it for what you are trying to accomplish.
http://docs.jquery.com/Main_Page

I need to accomplish this task without the use of frameworks. It is for a class that I am taking.

Related

Executing JavaScript on multi-row table rendering

I'm using a custom Primefaces-based framework to display a datatable, and it looks like that:
<xy:dataTable id="tableId" value="#{lazyTableBean.dates}" var="date">
<xy:column id="nameColumnId">
<xy:outputText id="nameOutputId" value="date.name"/>
</xy:column>
<xy:column id="actionColumnId">
<xy:actionButton id="actionButtonId" label="Button"
action="#{someBean.someAction(date.id)}"/>
</xy:column>
</xy:dataTable>
Now I want to set the tooltip of the button. Since the actionButton component of that framework doesn't have the title attribute, I'm using JavaScript to alter it:
var rows = // getting the table content row components here
// iterating through table rows and setting the button tooltip to the name of the corresponding date
for (const row of rows) {
var myTooltip = row.children.item(0).textContent;
row.children.item(1).firstChild.setAttribute("title", myTooltip);
}
This basically works as it should when I import the JS script at the end of the file.
However, there are several AJAX events (e.g. when sorting or filtering the table, or when using pagination...) that reprint the table content. Since the JS script isn't triggered again, the tooltips aren't set in that case.
Now I've planned to simply import the script at some appropriate place (e.g. inside the component that gets rerendered) so that it's executed whenever the button is rendered. However, I haven't found quite the right place to make it work. When I'm putting it inside the column:
<xy:dataTable id="tableId" value="#{lazyTableBean.dates}" var="date">
<xy:column id="nameColumnId">
<xy:outputText id="nameColumnId" value="date.name"/>
</xy:column>
<xy:column id="actionColumnId">
<xy:actionButton id="actionColumnId" label="Button"
action="#{someBean.someAction(date.id)}"/>
<h:outputScript library="js" name="addTooltipToTableButtons.js" />
</xy:column>
</xy:dataTable>
This results in only the first row to correctly set their tooltip, all other rows keep their generic one. But on AJAX events, the correct behavior takes place, all rows set their tooltip correctly. The same behavior takes place if the script is also imported at the end. I guess this has to do with the table format of dynamically printing a number of rows with the same column components, but this is just guessing.
Putting it inside the table (directly before </xy:dataTable>) results in no script execution at all.
I'm totally new to JavaScript and we're just using this approach until our custom framework supports setting arbitrary attributes. I hope you have an idea (or an explanation why it won't work like that) - thanks in advance!
Greetings
In case anyone's interested in my solution, I used a MutationObserver to handle the events, in addition to the "normal" JS at page load.
The whole JS file looked like that:
var table = ...; // get table by normal means
for (var i = 0, row; row = table.rows[i]; i++) {
var tooltip = row.cells[0].textContent;
row.cells[1].firstChild.setAttribute(tooltip);
}
var observer = new MutationObserver(function( mutations ) {
mutations.forEach(function( mutation ) {
var newNodes = mutation.addedNodes;
if( newNodes !== null ) {
var $nodes = $( newNodes );
$nodes.each(function() {
var tooltip = this.cells[0].textContent;
this.cells[1].firstChild.setAttribute(tooltip);
});
}
});
});
var config = {
attributes: true,
childList: true,
characterData: true
};
observer.observe(table.children.item(1), config);

How to have JavaScript functions work across different HTML pages?

I am building a website with several HTML pages, and going to fill up info on different pages through an API. I have added onclick listeners to HTML elements like this:
// ASSIGNING ELEMENTS AS VARIABLES
const EPL = document.getElementById('epl');
const bundesliga = document.getElementById('bundesliga');
const laliga = document.getElementById('laliga');
// ONCLICKS
EPL.onclick = function() {
getStandings('2021');
location.replace('standings.html');
}
bundesliga.onclick = function() {
getStandings('2088');
location.replace('standings.html');
}
laliga.onclick = function() {
getStandings('2224');
location.replace('standings.html');
}
When one of these is clicked, I call a function (getStandings) with its unique argument to fetch some data from the API. I also want to move to another HTML page, for which I used location.replace.
I'm caught in a dilemma: if I use the same JS file for every HTML page, when I get to the new HTML page, I get errors as the new HTML page does not have every element:
main.js:41 Uncaught TypeError: Cannot set property 'onclick' of null
But if I use different JS files, maybe one JS file for each HTML file, I cannot carry forward the bits of information I need. How can I get to the new HTML page, with its own JS file, without stopping and losing everything in the function I'm in currently, under the JS file of the old page? For example, the argument '2021' or '2088' are to be passed into the getStandings() function which will populate the new HTML page with data from an API. If I jump to a new HTML page with a new JS file, this is lost.
Is there a better way to organise my files? 😐😐😐😐😐
You can set your event listeners on the condition that the elements are not null e.g.
const EPL = document.getElementById('epl');
const bundesliga = document.getElementById('bundesliga');
const laliga = document.getElementById('laliga');
if(EPL){
EPL.onclick = function() {
getStandings('2021');
location.replace('standings.html');
}
}
etc...
Solved! As amn said, I can add URL parameters to the end of the URL of the new HTML page, then get the variables from its own URL once I'm on the new HTML page.
I think I would rather use classes instead of IDs to define the listener, and maybe IDs for dedicated action.

I can't access my current element

This is an instance of Rappid Toolkit which uses jointJS for building visual tools as for web development. http://i.stack.imgur.com/6XSis.png
In this toolkit you can make a graph which can become a website.
My problem is the following one:
In every single element of this graph there is a box below it with:x,y,width,height,angle.
I want to change this information of this boxcontent and to display some info from this element but the code in which I have to add my snippet is the following(var Halo is the var for my element in the graph):
var halo = new joint.ui.Halo({
cellView: cellView,
boxContent: function(cellView) {
return"Here I want to display my box content info instead of x,y,width,height, angle";
}
}).render();
If I try to add my code inside it to access in JSON format my current element info my full code is:
var halo = new joint.ui.Halo({
cellView: cellView,
boxContent: function(cellView) {
// Drawing
var selectedObjectDataText = JSON.stringify(this.cellView.toJSON());
var selectedObjectDataJSON = JSON.parse(selectedObjectDataText);
return(selectedObjectDataJSON[0].wi_name);
}
}).render();
where wi_name is the name of my element but in the first line I can't access the specific element of my graph.
var selectedObjectDataText = JSON.stringify(this.cellView.toJSON());
Is there any global way to access my halo(my graph element) since this.cellView.toJSON() doesn't work?
I tried this.model.toJSON() this.cellView.model.toJSON() etc with no result
Note that JointJS links and elements are Backbone Models (linkView and elementView are Backbone Views).
To get the current value of an attribute use get() method.
boxContent: function(cellView) {
return cellView.model.get('wi_name');
}
Alternatively you can use prop(), that can return also nested properties of a model.
boxContent: function(cellView) {
return cellView.model.prop('wi_name');
}
It worked for var selectedObjectDataText = JSON.stringify(cellView.model.toJSON());
Thank you all for your support.

iBooks Author/iAd Producer Widget User Text Disappears

I'm using iAd producer to create HTML widgets for iBooks I'm making using iBooks Author. I've done some research and learned that I can have user input saved into local storage by way of a variable that is called every time the widget is opened. This is great except for the new problem of having to create hundreds of text box widgets all with different variables so I can use these text boxes on multiple pages. Is there a way for me to automate this using Java Script? One idea I had was to use a "while" function to tell the script to ++ the variable if the one it tried to use was not empty. Example: the variable "001" was already used so the code would ideally set the next user text to variable "002". Preferably, I'd like to be able to create one widget with this code that I could reuse anywhere else.
Here is the code I'm currently using:
/*Widget code*/
this.onViewControllerViewWillAppear = function (event) {
var theValue = this.outlets.textField;
if (localStorage.getItem("theKey102") === null) {
theValue.value = "";
} else {
theValue.value = localStorage.getItem("theKey102");
}
};
/*This is the code for one of the text boxes I'm using */
this.onControlValueChange = function (event) {
var theValue = this.viewController.outlets.textField;
localStorage.setItem("theKey102", theValue.value);
};

Efficient OOP way to generate DOM elements dynamically with JavaScript?

I'm tinkering with writing a more efficient methodology in the creation of dynamically generated DOM elements via JavaScript. This is something I intend to add into my own JS framework later on. Looking for other OOP devs that could help better refine what I do have.
Here's a link to the working CodePen:
http://codepen.io/DaneTheory/pen/yeLvmm/
Here's the JS:
function CreateDOMEl() {};
CreateDOMEl.prototype.uiFrag = document.createDocumentFragment();
CreateDOMEl.prototype.elParent = function(elParent, index) {
this.elParent = document.getElementsByTagName(elParent)[index];
}
CreateDOMEl.prototype.elType = function(type) {
newEl = document.createElement(type);
this.uiFrag.appendChild(newEl);
}
CreateDOMEl.prototype.elContent = function(elContent) {
this.elContent = elContent;
newEl.textContent = elContent;
}
CreateDOMEl.prototype.buildEl = function() {
this.elParent.appendChild(this.uiFrag);
}
var div = new CreateDOMEl();
div.elParent('body', 0);
div.elType('DIV');
div.elContent('OK');
div.buildEl();
console.log(div);
var bttn = new CreateDOMEl();
bttn.elParent('body', 0);
bttn.elType('BUTTON');
bttn.elContent('SUBMIT');
bttn.buildEl();
console.log(bttn);
And some CSS to get elements to appear on page:
div {
width:100px;
height:100px;
border: 1px solid red;
}
My thoughts:
For performance, using the prototype to build methods versus placing all the logic in the constructor.
Rather than directly appending elements to the page, append to a single Document Fragment. Once the element is built out as a Doc Frag, appending the Doc Frag to to the DOM. I like this method for performance, but would like to improve upon it. Any useful implementations of requestnimationFrame, or using range and other versions of the document fragment method?
Silly, but I think for debugging it'd be nice to see the generated Element type within the Object property's on console log. As of right now, console logging a created element will show the elements parent and text content. It'd be great to show the elements type as well.
Creating more than one element at a time is another piece of functionality I'd like to offer as an option. For instance, creating a div element creates one div element. What's a good way to add another optional method to create multiple instances of div's.
div.elType('DIV');
// After calling the elType method, do something like this:
div.elCount(20);
// This would create 20 of the same divs
Lastly, a nice clean way to optionally add attributes (i.e: classes, an ID, value, a placeholder, custom attributes, data-* attributes, etc.). I've got a nice helper function I use that adds multiple attributes to an element in an object literal syntax looking way. Adding this as a method of the constructor would be ideal. Here's that function:
function setAttributes(el, attrs) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
// A use case using the above
// function would be:
var anInputElement = document.createElement("TEXTAREA");
setAttributes(anInputElement, {
"type": "text",
"id": "awesomeID",
"name": "coolName",
"placeholder": "Hey I'm some placeholder example text",
"class": "awesome"
});
// Which creates the following HTML snippet:
<textarea type="text" id="awesomeID" name="coolName" placeholder="Hey I'm some placeholder example text" class="awesome">
As a side note, realizing now that the above helper function needs rewritten so that multiple classes could be created.
Respectfully, I believe you may be overthinking it. Just use the tools available in JavaScript and get 'er done. In terms of performance, computers are so fast at running your JavaScript that you (and me) are unable to perceive, or even comprehend, the speed. Here's how I add a link to an MDL nav menu, for example. It's just vanilla JS. Don't forget to add event listeners.
function navMenuAdd(type,text){
var newAnchor = doc.createElement("anchor");
newAnchor.classList.add('mdl-navigation__link');
newAnchor.classList.add(type);
newAnchor.href = "javascript:void(0)";
var anchorContent = doc.createTextNode(text);
newAnchor.appendChild(anchorContent);
newAnchor.addEventListener('click', navMenuClickHandler, false);
//newAnchor.style.display = 'none';
if (type === 'Thingy A'){
//insertAfter(newAnchor, navMenuCredentials);
navMenuCredentialsPanel.appendChild(newAnchor);
} else if (type === 'Thingy B'){
//insertAfter(newAnchor, navMenuDevices);
navMenuDevicesPanel.appendChild(newAnchor);
}
}

Categories