Bind Events Understanding - javascript

Just going through some code on a vanilla JS to do list and I'm a bit confused about how this section is working.
The bit that blags my mind a bit is how the checkbox knows which list item is active. I understand we loop over the list items at the beginning and pass them as arguments but how does the checkbox know which one? Aren't we passing multiple list items to the 'taskListItem' argument here?
So when we click the checkbox I'm confused at how it knows which item to use.
Also is this a common way of binding things together? I'm trying to learn patterns so I can fully understand what's going on and can thus build in certain ways rather than just writing any old code.
If someone could break down what's going on here I'd be grateful!
Thanks
var bindTaskEvent = function (taskListItem, checkBoxEventHandler) {
debugger
console.log("Binding Events to Checkbox, Edit, Delete Buttons")
// Select task's <li> children
var checkBox = taskListItem.querySelector("input[type=checkbox]");
var editButton = taskListItem.querySelector("button.edit");
var deleteButton = taskListItem.querySelector("button.delete");
// Bind checkBoxEventHandler to checkbox
checkBox.onchange = checkBoxEventHandler;;
// Bind editTask to Edit button
editButton.onclick = editTask;
// Bind deleteTask to Delete Button
deleteButton.onclick = deleteTask;
};
// TASK COMPLETED
var taskCompleted = function () {
console.log("Running taskCompleted");
var listItem = this.parentNode;
completedTasks.appendChild(listItem);
bindTaskEvent(listItem, taskIncomplete);
}
// TASK INCOMPLETE
var taskIncomplete = function () {
console.log("Running taskIncomplete");
var listItem = this.parentNode;
incompleteTasks.appendChild(listItem);
bindTaskEvent(listItem, taskCompleted);
}
//// WIRING OF THINGS...
// cycle over To Do List items
for (var i = 0; i < incompleteTasks.children.length; i++) {
// bind events to <li>'s children
bindTaskEvent(incompleteTasks.children[i], taskCompleted);
};
// cycle over Completed Items
for (var i = 0; i < completedTasks.children.length; i++) {
// for each list item
// bind event to <li> children (taskCompleted)
bindTaskEvent(completedTasks.children[i], taskIncomplete);
};

Yes, the 'taskListItem' gets multiple list items but the querySelector() method returns the first element that matches a specified CSS selector(s) in the document. Therefore the first element is stored in the variable checkbox not the whole list.

Related

GoJS delete child nodes without knowing parent node's key

I have a goJS diagram with a custom model. When dropping a node on another node, I link them when the mouseDrop fires and set the from and to in the link data on the diagram.model:
mydiagram.model.addLinkData({ from: oldNodeModel.key, to: dragNodeModel.key });
This all works fine. In my node template I have a custom template which puts a panel around the nodes with a delete button. This delete button is simply an image with a click event.
Now when I click the delete image/button, I want to delete this now and all its child nodes.
My issue is I cannot find the children.
I have user events like findNodesOutOf, which yields no results and findNodesConnected which finds parents and child nodes and deletes the lot - which is not what I want.
Any idea how I can solve this?
You can get the item to delete by using the diagram.selection:
var nodeToDelete = mydiagram.selection.iterator.first();
Next to find all the children of this node I recommend a recursive function which will do the following:
Take in the node you want to delete,
Find all the connected nodes to it using mydiagram.getChildrenNodes(nodeToDelete)
Itterrate through the cconnected nodes
Check if each node is a child, by using the linkNodeModel and checking is the link goes from the currentnode to the child node.
Then call the recursive function again with this child node
The recursive function will return an array with all child nodes
Then you can delete them.
Your code will look something like this:
function deleteNode()
{
// TAKE NOTE - This will get all selections so you need to handel this
// If you have multiple select enabled
var nodeToDelete = mydiagram.selection.iterator.first();
var childNodes = getChildNodes(deletedItem);
//Remove linked children
$.each(childNodes, function()
{
myDiagram.remove(this);
});
// Then also delete the actual node after the children was deleted
// TAKE NOTE - This will delete all selections so you need to handle this
// If you have multiple select enabled
mydiagram.commandHandler.deleteSelection();
}
The recursive function keeps checking each node for its children and adds them to an aray:
function getChildNodes(deleteNode)
{
var children = [];
var allConnected= deleteNode.findNodesConnected();
while (allConnected.next())
{
var child = allConnected.value;
// Check to see if this node is a child:
if (isChildNode(deleteNode, child))
{
// add the current child
children.push(child);
// Now call the recursive function again with the current child
// to get its sub children
var subChildren = getChildrenNodes(child);
// add all the children to the children array
$.each(subChildren, function()
{
children.push(this);
});
}
}
// return the children array
return children;
}
This function will check if the node is a child by looking at the links in the diagram and checking to to and from against the current node and child node:
function isChildNode(currNode, currChild)
{
var links = myDiagram.links.iterator;
while (links.next())
{
// Here simply look at the link to determine the direction by checking the direction against the currNode and the child node. If from is the current node and to the child node
// then you know its a vhild
var currentLinkModel = links.value.data;
if (currentLinkModel.from === currNode.data.key && currentLinkModel.to === currChild.data.key)
{
return true;
}
}
return false;
}

Not able attach click event for a button using Javascript oop

I'm working on attaching a click event on a button which is having class "nextPage", but its not working. Let me show you the code.
function myContent() {
}
myContent.prototype.clickNext = function() {
alert("clicked");
};
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage');
el.onclick=objMyContent.clickNext();
Please take a look into it. Please let me know where I did mistake.
You need to reference the function, not execute it, when assigning the click handler.
Instead of:
el.onclick = objMyContent.clickNext();
Use this:
el.onclick = objMyContent.clickNext;
The first piece of code executes clickNext, and assigns it's return value to el.onclick.
The second line assigns a reference to the clickNext function to el.onclick, instead. (Which is what you want)
Also, getElementsByClassName returns an HTMLCollection (Which is basically an array of HTML elements).
You'll need to assign the click handler to each found element in that collection:
for(var i = 0; i < el.length; i++){
el[i].onclick = objMyContent.clickNext;
}
iterate the array and the assign the onclick event
var objMyContent = new myContent();
var el = document.getElementsByClassName('nextPage'); for(var i=0;i<el.length;i++) el[i].onclick=objMyContent.clickNext;

Can't remove all objects from ContentFlow coverflow carousel with remove function

Before I describe my problem I'd like to outline exactly what I'm attempting
What I'm trying to do here is load different sets of items into a content flow based on buttons a user can press. I've already set up a function triggered by a button that will get the items associated with the name value I pass to it via AJAX (using the jQuery $.get) and put in a div with an ID of itemcontainer. This works great.
function getPictures(Gallery){
$.get( "getimages.php", {name : Gallery}, function( data ) {
for(x in data){
$("#itemcontainer").append(data[x]);
}
addPictures();
}, "json" );
return false;
}
You'll see the function calls another function inside of it ( addPictures() ) that actually adds the pictures to the ContentFlow carousel from the div with an ID of itemcontainer via ContentFlow's addItem method.
function addPictures(){
clearBox();
var it = ajax_cf.items;
// Grabs pictures from 'itemcontainer' and iteratively adds them to 'flow'
var ic = document.getElementById('itemcontainer');
var is = ic.getElementsByTagName('img');
for (var i=0; i< is.length; i++) {
ajax_cf.addItem(is[i],"end");
}
}
You'll notice that the addPictures() function has a function inside it that I previously declare called clearBox(). clearBox() is supposed to loop through the items in my content flow and remove them so I can delete the old gallery of items in the ContentFlow carousel before loading new ones.
It works by looping through the list items (called items) which is a property of the ContentFlow object ajax_cf I create and run a method rmItem on the list element at index 0 for each item. Here's the clearBox function.
var ajax_cf = new ContentFlow('ajax_cf');
function clearBox(){
alert("clear");
var it = ajax_cf.items;
var len = it.length;
for(var i=0; i<len; i++){
ajax_cf.rmItem(0);
}
return false;
}
If you want you can get the source or the docs here.
http://www.jacksasylum.eu/ContentFlow/
Thanks in advance!
I don't know if you've fixed the problem yet, but here's a thought. Since these objects are in the DOM, try making your clearBox() method remove the items in the itemcontainer. So your method could look like:
function clearBox()
{
alert("clear");
var images = $('itemcontainer').children();
for (var i = 0; i < images.length; i++)
{
$('itemcontainer').remove(images[i]);
}
}
This way, you're forcing the ContentFlow object to remove the images and when your addPictures method continues, it will basically reinitialize the ContentFlow carousel.
This is untested but logically seems it would work because all the ContentFlow library's rmItem method would do is remove the object from the DOM, so you are choosing to do that yourself instead.

KnockoutJS custom binding calling click event during bind, not on click

I`m attempting to bind an observable array of people two a two column responsive layout with click events using knockoutjs.
I have created a custom binding called TwoCol that loops through the array, and appends nodes to the DOM to create my suggested layout, but the click events are giving me trouble when I try to apply them in a custom binding nested in a loop.
I have played with it quite a bit, and encountered all types of results, but where I`m at now is calling my ~click~ event during binding, rather than on click.
http://jsfiddle.net/5SPVm/6/
HTML:
<div data-bind="TwoCol: Friends" id="" style="padding: 20px">
JAVASCRIPT:
function FriendsModel() {
var self = this;
this.Friends = ko.observableArray();
this.SelectedFriend = "";
this.SetSelected = function (person) {
alert(person);
self.SelectedFriend = person;
}
}
function isOdd(num) {
return num % 2;
}
ko.bindingHandlers.TwoCol = {
update: function (elem, valueAccessor) {
var i = 0;
var rowDiv;
var vFriends = ko.utils.unwrapObservable(valueAccessor());
$(elem).html('');
while (i < vFriends.length) {
//create row container every other iteration
if (!isOdd(i)) {
rowDiv = document.createElement("div");
$(rowDiv).addClass("row-fluid");
elem.appendChild(rowDiv);
}
//add column for every iteration
var colDiv = document.createElement("div");
$(colDiv).addClass("span6");
rowDiv.appendChild(colDiv);
//actual code has fairly complex button html here
var htmlDiv = document.createElement("div");
var htmlButton = vFriends[i]
htmlDiv.innerHTML = htmlButton;
colDiv.appendChild(htmlDiv);
//i think i need to add the event to the template too?
//$(htmlDiv).attr("data-bind", "click: { alert: $data }")
//it seems that the SetSelected Method is called while looping
ko.applyBindingsToDescendants(htmlDiv, { click: friends.SetSelected(vFriends[i]) });
i++;
}
return { controlsDescendantBindings: true };
}
}
var friends = new FriendsModel();
friends.Friends.push('bob');
friends.Friends.push('rob');
friends.Friends.push('mob');
friends.Friends.push('lob');
ko.applyBindings(friends);
I don't think you're using ko.applyBindingsToDescendants correctly. I admit I'm a little confused as to the meaning of some of the values in your code, so I may have interpreted something incorrectly.
Here's a fiddle where I think it's working the way you intended:
http://jsfiddle.net/5SPVm/7/
http://jsfiddle.net/5SPVm/8/
Notice if manually control descendant bindings (return { controlsDescendantBindings: true };), you need to set that up in the init callback, instead of update. The update callback is too late for that.
Quick rundown of the changes (edited):
Moved the controlsDescendantBindings into the init binding callback
Added the necessary parameter names to the binding param list to access additional values.
I re-enabled the html.attr call. Notice that now, because the binding context is set to the actual item, the SetSelected method doesn't exist at that level anymore, so it is necessary to use $parent.SetSelected.
$(htmlDiv).attr("data-bind", "click: $parent.SetSelected")
Fixed the ko.applyBindingsToDescendants call. This method takes a binding context, which is created from the current binding context, and also takes the element to apply the binding to. You don't want to reapply the binding, which is why this whole thing needs to be in the init handler.
var childBindingContext = bindingContext.createChildContext(vFriends[i]);
ko.applyBindingsToDescendants(childBindingContext, colDiv);

Transfer HTML elements from one list to another with JavaScript

I have a problem with the following JavaScript function. I have two UL and I need that when the user clicks on a LI, this element transfers to the other UL.
I've managed to move them onClick from on list to the other, the problem appears when I try to move again a LI that was previously in the other UL, when that happens it just doesn't work...
function testList() {
usersA = document.getElementById("users-a");
usersB = document.getElementById("users-b");
for (var i=0; i < usersA.getElementsByTagName("li").length; i++) {
usersA.getElementsByTagName("li")[i].onclick = function() {
transfer = this.cloneNode(true);
usersB.appendChild(transfer);
usersA.removeChild(this);
return false;
}
}
for (var i=0; i < usersB.getElementsByTagName("li").length; i++) {
usersB.getElementsByTagName("li")[i].onclick = function() {
transfer = this.cloneNode(true);
usersA.appendChild(transfer);
usersB.removeChild(this);
return false;
}
}
}
I know that my logic sucks but it's all I could come up with. Any ideas why it works the first time I transfer a LI but when I try to move back to its original UL it doesn't work?
You're not "moving" elements, you're creating a copy and deleting the original. Although this seems like a "move" from the user's point of view the new elements that you create do not have click handlers assigned. From MDN: "Cloning a node copies all of its attributes and their values but does not copy event listeners."
According to MDN, .appendChild() will remove the child from its current parent so you shouldn't need the two-step clone/remove that you are currently doing. I haven't tested it, but perhaps using just .appendChild() it will keep the handlers? If so you need to remove that handler and assign a new one to allow for which list it now belongs to.
Or, rewrite your handlers so that they check which list is the current parent (have a look at .parentNode) and move to the other list as appropriate.
Bearing in mind that click events "bubble up" starting from the target/source element and up through the parent heirarchy you're probably better off setting your click handlers on the parent <ul> elements and then testing which <li> was clicked. That way you don't have to worry about setting new click handlers on new child <li> elements.
function testList() {
var usersA = document.getElementById("users-a"),
usersB = document.getElementById("users-b");
usersA.onclick = function(e) {
// allow for the IE and non-IE way of handling the event object
if (!e) e = window.event;
var el = e.target || e.srcElement;
if (el.tagName === "li") {
usersB.appendChild(el);
}
}
usersB.onclick = function(e) {
// allow for the IE and non-IE way of handling the event object
if (!e) e = window.event;
var el = e.target || e.srcElement;
if (el.tagName === "li") {
usersA.appendChild(el);
}
}
}
Also, if you're using .getElementsByTagName() call it once in the loop initialisation and assign the result to a variable, then use the variable - don't keep calling the function again and again to test the length, or to access individual elements inside your loop:
for (var i=0, lis = usersA.getElementsByTagName("li"); i < lis.length; i++) {
lis[i].onclick = function() {
// etc
}
}
The problem is that even after you've moved an element from list A to list B, it still keeps its old onclick handler, which still says "remove me from list A and add me to list B". You need to change its onclick handler to say "remove me from list B and add me to list A". Here's one way to fix that:
var usersA = document.getElementById("users-a");
var usersB = document.getElementById("users-b");
var onclickA;
var onclickB = function() {
usersA.appendChild(this);
this.onclick = onclickA;
return false;
};
onclickA = function() {
usersB.appendChild(this);
this.onclick = onclickB;
return false;
};
for (var i=0; i < usersA.getElementsByTagName("li").length; i++)
usersA.getElementsByTagName("li")[i].onclick = onclickA;
for (var i=0; i < usersB.getElementsByTagName("li").length; i++)
usersB.getElementsByTagName("li")[i].onclick = onclickB;
(I also got rid of the cloneNode stuff, for exactly the reasons that nnnnnn gives.)

Categories

Resources