I would like to understand why in this "loop" I cannot attach the key of element in an event with a closure/inner function. I know what happens with other loops like for,while ... I don't understand the real reason why in this situation this approach doesn't work.
Q: Why can I not pass the key directly?
item.transaction(["itens"], "readonly").objectStore("itens").openCursor()
.onsuccess = function(e) {
var info = e.target.result;
if (info) {
var div = document.createElement("div");
var img = document.createElement("img");
var h2 = document.createElement("h2");
img.src = "../imagens/misc/" + info.value.image;
h2.innerHTML = info.value.title;
div.appendChild(h2);
div.onclick = function() {
//always the lasy key.
console.log(info.key);
};
div.appendChild(img);
box.appendChild(div);
info.continue();
}
};
I know type of solution works...
bt.onclick = (function(index) {
return function (){console.log('iterator: ' + index);}
})(i);
with ({ n: i }) {
bt.onclick = function(index) {
console.log('iterator: ' + n);
};
}
In the example given, you're overwriting div.onclick for each iteration of the cursor.
What's going on might be clearer if you changed it to be:
console.log('overwriting onclick handler to log: ' + info.key);
div.onclick = function() {
console.log(info.key);
};
Or instead, don't overwrite:
console.log('adding onclick listener to log: ' + info.key);
div.addEventListener('click', function() {
console.log(info.key);
});
These will behave differently but perhaps help you to understand what's going on.
Related
In the following example, concerning closures, I see this:
function addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
var button = document.createElement('input');
button.type = 'button';
button.value = 'Button ' + (i + 1);
button.onclick = function(buttonIndex) {
return function() {
alert('Button ' + (buttonIndex + 1) + ' clicked');
};
}(i);
document.body.appendChild(button);
document.body.appendChild(document.createElement('br'));
}
}
window.onload = function() { addButtons(5); };
How come the onclick method isn't defined as follows?:
button.onclick = (function(buttonIndex) {
return function() {
alert('Button ' + (buttonIndex + 1) + ' clicked');
};
})(i);
Doesn't an IIFE require the following syntax: (function statement)(arguments)?
The original code works because the IIFE appears in an assignment, and so there is no ambiguity that the right part of the assignment is indeed an expression.
Without the assignment, the parser would misunderstand it to be a function declaration, and therefore would throw a syntax error, indicating that the declared function lacks a name. In that case the parentheses are necessary to make it an IIFE.
But it is common practice to always include the parentheses for an IIFE, even when used in an assignment.
In the concrete case you have at hand, you can also assign the click handler as using bind, which returns the function it needs:
button.onclick = function(buttonIndex) {
alert('Button ' + (buttonIndex + 1) + ' clicked');
}.bind(null, i);
You can use element's dataset. For example:
function addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
var button = document.createElement('input');
button.type = 'button';
button.value = 'Button ' + (i + 1);
button.dataset.buttonIndex = i + 1;
button.onclick = function() {
alert('Button ' + this.dataset.buttonIndex + ' clicked');
};
document.body.appendChild(button);
document.body.appendChild(document.createElement('br'));
}
}
window.onload = function() { addButtons(5); };
I've implemented simple todo list http://jsfiddle.net/tw9p9sw3/ with possibility add/remove todo's.
(function (todoApp) {
'use strict';
todoApp.todoItemValue = '';
function _appendListNode() {
var listNode = document.createElement('li');
listNode.className = 'list-group-item';
listNode.appendChild(document.createTextNode(todoApp.todoItemValue));
listNode.appendChild(_createRemoveItemButton());
todoList.appendChild(listNode);
}
function _createRemoveItemButton() {
var removeButtonNode = document.createElement('a'),
removeButtonLabel = 'Remove';
removeButtonNode.className = 'badge';
removeButtonNode.appendChild(document.createTextNode(removeButtonLabel));
return removeButtonNode;
}
function _addItem() {
todoApp.todoItemValue = document.getElementById('todoInput').value;
_appendListNode();
document.getElementById('todoInput').value = '';
}
function _removeItem() {
var nodeToRemove = this.parentNode;
this.parentNode.parentNode.removeChild(nodeToRemove);
}
function _addEventHandlers() {
document.getElementById('todoListForm').addEventListener('submit', function (event) {
event.preventDefault();
_addItem();
});
/*
* Used delegated event cause todo list is not static,
* so we don't need to add events for remove buttons each time after DOM of todo list have changed
*/
document.getElementById('todoList').addEventListener('click', function (event) {
if (event.target && event.target.nodeName === 'A') {
event.preventDefault();
_removeItem.call(event.target);
}
});
}
todoApp.init = function () {
_addEventHandlers();
};
}(window.todoApp = window.todoApp || {}));
//////////////////////////////////////////////////////////
// INITIALIZATION
//////////////////////////////////////////////////////////
document.onload = todoApp.init();
The question is how to re-make this todo list as independent component so for example I could inject/instanciate it twice on the same page and there should be no conflicts between todo list instances.
Basically you need to create TodoList class, so you can create instances of this class, as suggested in coments use OO programming.
Here is code:
var TodoListComponent = (function () {
'use strict';
var todoListTemplate = '<div>' +
'<div class="todo-list">' +
'<div class="form-container">' +
'<form role="form" class="todoListForm">' +
'<div class="form-group">' +
'<label for="todoInput">Add todo item</label>' +
'<input type="text" class="form-control todoInput" required>' +
'</div>' +
'<button type="submit" class="btn btn-success">Submit</button>' +
'</form>' +
'</div>' +
'<div class="list-container">' +
'<ul class="list-group todoList">' +
'<li class="list-group-item list-group-item-success">Todo list</li>' +
'</ul>' +
'</div>' +
'</div>' +
'</div>';
function TodoList() {
this.initialize = function() {
this.$el = $(todoListTemplate);
this.todoItemValue = '';
this._initEvents();
}
this._initEvents = function() {
var self = this;
this.$el.find('.todoListForm').on('submit', function (event) {
event.preventDefault();
self._addItem();
});
/*
* Used delegated event cause todo list is not static,
* so we don't need to add events for remove buttons each time after DOM of todo list have changed
*/
this.$el.find('.todoList').on('click', function (event) {
if (event.target && event.target.nodeName === 'A') {
event.preventDefault();
self._removeItem.call(event.target);
}
});
}
this._appendListNode = function () {
var listNode = $('<li>');
listNode.addClass('list-group-item');
listNode.text(this.todoItemValue);
listNode.append(this._createRemoveItemButton());
this.$el.find('.todoList').append(listNode);
}
this._createRemoveItemButton = function () {
var removeButtonNode = $('<a>'),
removeButtonLabel = 'Remove';
removeButtonNode.addClass('badge');
removeButtonNode.text(removeButtonLabel);
return removeButtonNode;
}
this._addItem = function() {
var input = this.$el.find('.todoInput');
this.todoItemValue = input.val();
this._appendListNode();
input.val('');
}
this._removeItem = function() {
var nodeToRemove = this.parentNode;
this.parentNode.parentNode.removeChild(nodeToRemove);
}
this.render = function() {
return this;
}
this.initialize();
}
return TodoList;
}());
//////////////////////////////////////////////////////////
// INITIALIZATION
//////////////////////////////////////////////////////////
var todoList1 = new TodoListComponent();
var todoList2 = new TodoListComponent();
$('body').append(todoList1.render().$el);
$('body').append(todoList2.render().$el);
And jsfiddle link: http://jsfiddle.net/tw9p9sw3/10/
This works, but what if you want to use data, send items somewhere or else, then you probably will get it via DOM which is bad and messy.
Try to use Backbone where DOM used only for representation, and data is held by Javascript.
Here is example:
http://backbonejs.org/docs/todos.html
This problem is with 1.0
I have the following html in a polymer element
<iron-selector attr-for-selected="step-number" selected="{{stepNumber}}">
<section step-number="1">
<page-1 id="page1"></page-1>
</section>
<section step-number="2">
<page-2 id="page2"></page-2>
</section>
<section step-number="3">
<page-3 id="page3"></page-3>
</section>`
page 1 fires an event - event1, page-2 fires event2. Here's my javascript for the polymer element
function () {
Polymer({
is: 'name',
ready: function() {
this.stepNumber = 1;
var firstStep = this.$.page1;
var secondStep = this.$.page2;
var thirdStep = this.$.page3;
var that = this;
firstStep.addEventListener('event1', function(e) {
that.stepNumber = 2; // WORKING
});
secondStep.addEventListener('event2', function(e) {
that.stepNumber = 3; // NOT WORKING
});
}
});
Here's the interesting part:
Both events get fired, the value for stepNumber gets updated inside event1 handler but not inside event2 handler. I also debugged this and inside the event2 handler the value of stepNumber DOES get updated (page-3 element gets the tag "iron-selected") but it doesn't persist. After a bunch of polymer code executes the value is back to 2.
Looks like a bug in polymer?
Some info from the debugger -
polymer code that's executing after the event method is basically the code that executes the event handler, followed by a "gesture recognition" code (which i guess is the on-click), where the tag iron-selector is added to page-2 section
var recognizers = Gestures.recognizers;
for (var i = 0, r; i < recognizers.length; i++)
{
r = recognizers[i];
if (gs[r.name] && !handled[r.name]) {
handled[r.name] = true; // in here for r.name = "tap"
r[type](ev); // type = click. ev = MouseEvent. ev.target = iron-selector
}
}
Digging deeper -
this creates an event handler
_createEventHandler: function(node, eventName, methodName) {
var host = this;
return function(e) {
if (host[methodName]) {
hostmethodName;
} else {
host._warn(host._logf("_createEventHandler", "listener method " + methodName + " not defined"));
}
};
host is iron-selector. methodName is _activateHandler where _itemActivate is called for section of page-2
_activateHandler: function(e) {
// TODO: remove this when https://github.com/Polymer/polymer/issues/1639 is fixed so we
// can just remove the old event listener.
if (e.type !== this.activateEvent) {
return;
}
var t = e.target;
var items = this.items;
while (t && t != this) {
var i = items.indexOf(t);
if (i >= 0) {
var value = this._indexToValue(i);
this._itemActivate(value, t);
return;
}
t = t.parentNode;
}
},
_itemActivate: function(value, item) {
if (!this.fire('iron-activate',
{selected: value, item: item}, {cancelable: true}).defaultPrevented) {
this.select(value);
}
_itemActivate is where the value of stepNumber changes to 2
Clear the activateEvent property (activate-event attribute) of the iron-selector, like this:
<iron-selector attr-for-selected="step-number" selected="{{stepNumber}}" activate-event="">
Try this:
Polymer({
is: 'name',
ready: function() {
this.stepNumber = 1;
var firstStep = this.$.page1;
var secondStep = this.$.page2;
var thirdStep = this.$.page3;
var that = this;
firstStep.addEventListener('event1', function(e) {
that.async(function(){
that.stepNumber = 2;
},0);
});
secondStep.addEventListener('event2', function(e) {
that.async(function(){
that.stepNumber = 3;
},0);
});
}
});
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 8 years ago.
I have a series of buttons loaded from a JSON, which in turn should disappear and append other buttons on click.
Like so (the first level of buttons is already on the page and reacting properly to other events like hover):
...
$(document).on('click', "#subcategoryButtons button", function () {
getTemplate('imgMenu.html');
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0')
{
subentryIndex = this.id.slice(-1);
}
else
{
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex)
{
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
}
;
...
This is the content of the template being loaded:
<div id="imgSpace">
<aside id="overlayRight">
Right Overlay space
</aside>
<div id="overlayBottom">
Bottom Overlay
</div>
</div>
<nav id="imgButtons" class="resizable">
<span></span>
</nav>
Here's the getTemplate code:
function getTemplate(templateUrl)
{
$.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
});
}
Even though it should append the buttons to the #imgButtons span, it seems as if it cannot select any of the elements in the template just loaded. If I try to append the buttons to another part of the page (say like the left menu, which is not recently loaded) or instead of getting a template I simply clear out the HTML in the main, the attachment works. So it appears that the issue is how to select elements that have been loaded. Any help would be greatly appreciated.
Thanks to everyone who pointed me in the right direction. In the end I didn't use Ajax but deferred.done like so:
$(document).on('click', "#subcategoryButtons button", function () {
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0') {
subentryIndex = this.id.slice(-1);
} else {
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$('main').html('');
$.get("templates/imgMenu.html", function (content)
{
$('#main').html(content);
}).done(function () {
$.each(imgs, function (imgIndex) {
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
console.log(entryIndex);
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
});
}
;
You're using the wrong selector #imgButtons button should be #imgButtons span to select the span in #imgButtons
Also your template is loaded asynchronously so you'll have to wait until it is loaded (via a callback function) to manipulate it. something like
$(document).on('click', "#subcategoryButtons button", function () {
getTemplate('imgMenu.html', callback);
function callback(){
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0')
{
subentryIndex = this.id.slice(-1);
}
else
{
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex)
{
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
}
});
...
function getTemplate(templateUrl, callback)
{
$.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
callback();
});
}
let's make a bit modification on your code, use promises - the jqxhr object returned by the ajax method implements the promise interface, therefore you could make use of it.
function getTemplate(templateUrl)
{
return $.get('templates/' + templateUrl, function (content)
{
if (templateUrl === 'leftMenu.html')
{
$('#leftMenu').html(content);
}
else
{
$('#main').html(content);
}
});
}
use it in this way
$(document).on('click', "#subcategoryButtons button", function (e) {
getTemplate('imgMenu.html').then(function () {
var entryIndex = this.id[0];
var subentryIndex;
if (this.id[1] === '0') {
subentryIndex = this.id.slice(-1);
} else {
subentryIndex = this.id.slice(-2);
}
var imgs = data.category[entryIndex].subcategory[subentryIndex].imgs;
$.each(imgs, function (imgIndex) {
var imgName = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgName;
var imgId = data.category[entryIndex].subcategory[subentryIndex].imgs[imgIndex].imgId;
$('#imgButtons span').append('<button id="' + imgId + '">' + imgName + '</button>');
});
});
});
Hey all I have the following code that's a mix of Prototype JS and Jquery:
var oldZindex = 0;
var theClickID = null;
UIProgressButton.prototype._initEvents = function() {
var self = this;
this.button.addEventListener( 'click', function(param) {
$('#overlay').css({'visibility':'visible'});
theClickID = $('button[data-id="' + param.toElement.attributes[1].value + '"]').parent().attr('id');
oldZindex = $('#' + theClickID).css('z-index');
});
}
This code works just fine in a Chrome Browser but seems to throw the error of:
typeerror param.toelement is undefined
In FireFox.... How would I go about fixing this issue since I know for a fact that it does work since its working in Chrome just fine?
Fixed it by adding the following:
var Element = ((window.event)?(event.srcElement):(evt.currentTarget));
Then using that within my code:
UIProgressButton.prototype._initEvents = function() {
var self = this;
this.button.addEventListener( 'click', function(param) {
$('#overlay').css({'visibility':'visible'});
var Element = ((window.event)?(event.srcElement):(param.currentTarget));
theClickID = $('button[data-id="' + Element.attributes[0].value + '"]').parent().attr('id');
oldZindex = $('#' + theClickID).css('z-index');
});
}
Since you already have jQuery you could use it to bind your event listeners so you don't have the IE trouble in getting the event:
var oldZindex = 0;
var theClickID = null;
UIProgressButton.prototype._initEvents = function() {
var self = this;
$(this.button).on( 'click', function(param) {
$('#overlay').css({'visibility':'visible'});
theClickID = $('button[data-id="'
//you may prefer to get a named attribute
//instead of by index:
//param.getAttribute('attributeName')
+ param.target.attributes[1].value
+ '"]').parent().attr('id');
oldZindex = $('#' + theClickID).css('z-index');
});
}