I am trying to create a web app that will allow a user to define a custom JavaScript function and then add a button to their user interface that well preform that function.
Here is a sample of the code
var customCommands = {
command1: {
text: 'Hello Console',
cFunctionRun: function() {
console.log('hello Console!');
}
},
command2: {
text: 'Hello World',
cFunctionRun: function() {
alert('hello World!');
}
}
}
Then I wrote a small function that loops though and builds the buttons and adds them to the user interface. The problem is when I append the elements to the user interface than click on the buttons nothing works...
Here is one of the methods I tried
for (var cmd in customCommands) {
command = customCommands[cmd];
button = $('<button/>').html(command.text).on('click',
function(){
console.log(command.text);
command.cFunctionRun();
}
);
}
buttonContainer.append(button);
Now my loop builds everything just fine and even the .on('click') works, but it always displays the text of the lasted added command?
here is http://jsfiddle.net/nbnEg/ to show what happens.
When you actually click, the command variable points to last command (as the whole loop has already run). You should maintain data state per button which tells it which command to invoke. You should do this.
for(var i in customCommands) {
if(customCommands.hasOwnProperty(i)){ //this is a pretty important check
var command = customCommands[i];
button = $('<button/>').html(command.text).data("command_name", command).on('click', function(){
console.log($(this).data("command_name").text);
$(this).data("command_name").cFunctionRun();
});
$("body").append(button);
}
}
JSFiddle
all you need is passing the parameter with function, you should try this
It's a (missing) closure problem. The event handler will keep a reference to the value of command on the last iteration of the loop. To solve it you can create a new scope, using an immediately invoked function:
for(var cmd in customCommands) {
(function(command){
button = $('<button/>').html(command.text).on('click',
function(){
console.log(command.text);
command.cFunctionRun();
}
);
buttonContainer.append(button);
}(customCommands[cmd]));
}
Since the buttons should be unique (no reason for creating duplicates), I'm setting the button id to the name of the customCommands (command1 and command2 in this example). This example could easily be adapted to use any of the relative attributes (data-*, name, etc...).
Create a click event listener on document for whenever one of your buttons are pressed. Then call the function associated with the given id.
$(document).on("click", "button", function(){
customCommands[this.id].cFunctionRun();
});
for(var command in customCommands){
var button = $('<button id="' + command +'"/>').html(customCommands[command].text);
$("body").append(button);
}
EXAMPLE
Related
So i decided to make a wordle clone today and wrote some basic javascript after doing some html and css. I wanted to add an event to all the elements of the array at once using forEach using the code below.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}
It immediately throws me an error in the console. The problem is that it is supposed to do that when I click on an element of the array, not automatically. I tried it with other elements, like displaying hello world in the console after clicking but it also executed automatically.(btw i do not need help with the code itself, I just want know why the added event is being executed automatically and also i'm a beginner so please don't judge my code :)).
eventHandler should be a function, you passed a function call by mistake. Function calls are run immediately.
const letterCounter = 0;
Array.from(document.querySelectorAll("button")).forEach(elem => {
elem.addEventListener('click', () => inputLetter(elem.innerText))
})
function inputLetter(letter){
Array.from(document.querySelectorAll("letter-box"))[letterCounter].innerText = letter
letterCounter++
}
As an exercise I have to do a little online bike reservation app. This app begins with a header which explains how to use the service. I wanted this tutorial be optional so I wrote a welcome message in HTML and if the user doesn't have a var in his cookies saying he doesn't want to see the tutorial again, the welcome message is replaced by a slider that displays the information.
To achieve that is fetch a JSON file with all the elements I need to build the slider (three divs : the left one with an arrow image inside, the central one where the explanations occur and the right one with another arrow). Furthermore I want to put "click" events on the arrows to display next or previous slide. However, when I do so, only the right arrow event works. I thought of a closure problem since it is the last element to be added to the DOM that keeps its event but tried many things without success. I also tried to add another event to the div that works ("keypress") but only the click seems to work. Can you look at my code give me an hint on what is going on?
Here is the init function of my controller:
init: function() {
var load = this.getCookie();
if(load[0] === ""){
viewHeader.clearCode();
var diapoData = ServiceModule.loadDiapoData("http://localhost/javascript-web-srv/data/diaporama.json");
diapoData.then(
(data) => {
// JSON conversion
modelDiapo.init(data);
// translation into html
controller.initElementHeader(modelDiapo.diapoElt[0]);
controller.hideTuto();
}, (error) => {
console.log('Promise rejected.');
console.log(error);
});
} else {
viewHeader.hideButton();
controller.relaunchTuto();
}
}
There is a closer look at my function translating the JSON elements into HTML and adding events if needed :
initElementHeader: function(data){
data.forEach(element => {
// Creation of the new html element
let newElement = new modelHeader(element);
// render the DOM
viewHeader.init(newElement);
});
}
NewElement is a class creating all I need to insert the HTML, viewHeader.init() updates the DOM with those elements and add events to them if needed.
init: function(objetElt){
// call the render
this.render(objetElt.parentElt, objetElt.element);
// add events
this.addEvent(objetElt);
},
Finally the addEvent function:
addEvent: function(objet){
if(objet.id === "image_fleche_gauche"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do things
});
}
else if(objet.id === "image_fleche_droite"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do stuff
});
};
},
I hope being clear enough about my problem. Thank You.
Ok, I found the problem, even if the element was actually created, it somehow stayed in the virtual DOM for some time, when the "getElementById" in "addEvent" was looking for it in the real DOM it didn't find the target and couldn't add the event. This problem didn't occur for the last element since there was nothing else buffering in the virtual DOM.
On conclusion I took out the function adding events out of the forEach loop and created another one after the DOM is updated to add my events.
I have isolated the issue, see and try the full source here.
Steps to reproduce:
Press Ctrl+Enter to run the snippet
Click on 'Say Hello' custom command button, and check if the event
handler runs
Click on top left 'Save State' button
Click on 'Load State' button, and restore the previous state.
Now click again on 'Say Hello' button and demonstrate the event handle will not run, instead something weird is happening.
Notes: Please do not search for the solution around the localStorage. The issue can be reproduced by using different server side state persisting solution. (as my original app does)
Any idea where to patch? ... or workaround?
Hopefully this will help you out.
http://dojo.telerik.com/EDUCO/4
I have added the following piece of code for you:
dataBound: function (e) {
$(".k-grid-SayHello").on('click', function (a) {
console.log(e);
a.preventDefault();
alert('Hello');
});
},
When the rebind occurs I suspect that it is losing the connection to the event handler so all I have done if looked for the button based on it's class name and reattached it.
Obviously you can adapt the solution to meet your needs but this is something I do for my projects when I need to "invoke" custom actions on buttons/ dynamically create things on the fly.
Any issues let me know.
To keep function references after calling grid.setOptions()
I added the function references back to the deserialized configuration object before passing it to the setOptions method.
( http://docs.telerik.com/kendo-ui/api/javascript/ui/grid#methods-setOptions )
$(document).ready(function () {
var grid = $("#myGrid").data("kendoGrid");
var originalOptions = grid.getOptions();
var savedOptions = JSON.parse(localStorage["myGrid-options"]);
if (savedOptions) {
var detaylarFunc = originalOptions.columns[3].command[0].click;
savedOptions.columns[3].command[0].click = detaylarFunc;
grid.setOptions(savedOptions);
} else {
grid.dataSource.read();
}
});
//Custom command
function Detaylar(e) {
e.preventDefault();
var grid = $("#myGrid").data("kendoGrid");
options = grid.getOptions();
localStorage["myGrid-options"] = kendo.stringify(grid.getOptions());
}
So I have a group of events like this:
$('#slider-1').click(function(event){
switchBanners(1, true);
});
$('#slider-2').click(function(event){
switchBanners(2, true);
});
$('#slider-3').click(function(event){
switchBanners(3, true);
});
$('#slider-4').click(function(event){
switchBanners(4, true);
});
$('#slider-5').click(function(event){
switchBanners(5, true);
});
And I wanted to run them through a loop I am already running something like this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners(i, true);
});
}
In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...
What am I missing?
This is a very common issue people encounter.
JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.
To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.
In the code below, we reference it with the function parameter j.
// Invoke generate_handler() during the loop. It will return a function that
// has access to its vars/params.
function generate_handler( j ) {
return function(event) {
switchBanners(j, true);
};
}
for(var i = 1; i <= totalBanners; i++){
$('#slider-' + i).click( generate_handler( i ) );
}
Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).
The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.
UPDATE: Added var before i to be sure it is declared properly.
Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".
Some links:
http://davidwalsh.name/event-delegate
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://lab.distilldesign.com/event-delegation/
Study this. It is a quite important thing to learn about event management in javascript.
[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]
$(function() {
$('.someAncestor').on('click', '.slider', function(e) {
// code to do stuff on clicking the slider. 'e' passed in is the event
});
});
Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.
Original answer maintained for more thorough explanation and legacy sample code:
I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:
$(function() {
$('body').delegate('.slider', 'click', function() {
var sliderSplit = this.id.split('-'); // split the string at the hyphen
switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
});
});
Liddle Fiddle: http://jsfiddle.net/2KrEk/
I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.
It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).
Try this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners($(this).attr('id').replace('slider-', ''), true);
});
}
That way you're getting the number from the id of the element that's actually been clicked.
Use jQuery $.each
$.each(bannersArray, function(index, element) {
index += 1; // start from 0
$('#slider-' + index).click(function(event){
switchBanners(index, true);
});
});
You can study JavaScript Clousure, hope it helps
I need my script to do something on the first time an element is clicked and continue to do something different on click 2,3,4 and so on
$('selector').click(function() {
//I would realy like this variable to be updated
var click = 0;
if (click === 0) {
do this
var click = 1;
} else {
do this
}
});//end click
really I think it should rely on the variables but I can't think of how to update the variable from here on out any help would be awesome.
Have a look at jQuery's .data() method. Consider your example:
$('selector').click(function() {
var $this = $(this),
clickNum = $this.data('clickNum');
if (!clickNum) clickNum = 1;
alert(clickNum);
$this.data('clickNum', ++clickNum);
});
See a working example here: http://jsfiddle.net/uaaft/
Use data to persist your state with the element.
In your click handler,
use
$(this).data('number_of_clicks')
to retrieve the value and
$(this).data('number_of_clicks',some_value)
to set it.
Note: $(this).data('number_of_clicks') will return false if it hasn't been set yet
Edit: fixed link
Another alternative might be to have two functions, and bind one using the one function in $(document).ready() (or wherever you are binding your handlers), and in that function, bind the second function to be run for all subsequent clicks using bind or click.
e.g.
function FirstTime(element) {
// do stuff the first time round here
$(element.target).click(AllOtherTimes);
}
function AllOtherTimes(element) {
// do stuff all subsequent times here
}
$(function() {
$('selector').one('click', FirstTime);
});
This is super easy in vanilla Js. This is using proper, different click handlers
const onNextTimes = function(e) {
// Do this after all but first click
};
node.addEventListener("click", function onFirstTime(e) {
node.addEventListener("click", onNextTimes);
}, {once : true});
Documentation, CanIUse
If you just need sequences of fixed behaviors, you can do this:
$('selector').toggle(function(){...}, function(){...}, function(){...},...);
Event handlers in the toggle method will be called orderly.
$('#foo').one('click', function() {
alert('This will be displayed only once.');
});
this would bind click event to Corresponding Html element once and unbind it automatically after first event rendering.
Or alternatively u could the following:
$("#foo").bind('click',function(){
// Some activity
$("#foo").unbind("click");
// bind it to some other event handler.
});