Events running multiple times after adding new content to the DOM - javascript

I have issue with my plugin, because I need a way to update if new elements are added to the DOM I added a update methods,
if I start the plugin all goes well, everything works perfect, not issues, no error, but once I add a new elelemnt(div with class box)
to the DOM things goes wrong, the update works, but the click events seem to fire multiple times now, so if I add a new element the event runs
twice, if I add 2 elements to the DOM, the events runs 3 times....and so on. I am not that good at Js, so I am stuck at this, I have tried a lot but nothing seems to work.
Elements that are newly added work fine, but if I add some more new elements they will have the same issues.
I added below a small preview, as my plugin is custom an big I only posted the parts that have issues(made them easy to understand).
The update method is needed, new elements(.box) need to be updated(add new code to the .box)
the HTML code
<div id="container">
<div class="box">
link 1
link 2
<div>content goes here...</div>
</div>
<div class="box">
link 1
link 2
<div>content goes here...</div>
</div>
<div class="box">
link 1
link 2
<div>content goes here...</div>
</div>
</div>
inline script
$('#container').myplugin01();
$('#somelink').click(function(e){
$('#container').append('<div class="box">link 1link 2<div>content goes here...</div></div>');
$('#container').myplugin01('update');
});
the plugin
;(function($, window, document, undefined){
//"use strict"; // jshint ;_;
var pluginName = 'myplugin01';
var Plugin = function(element, options){
this.init(element, options);
};
Plugin.prototype = {
init: function(element, options){
this.elm = $(element);
this.options = $.extend({}, $.fn[pluginName].options, options);
// example 1: animation
$('#container').children('.box').on("click", ".link1", function(e){
$(this).parent().children('div').animate({height: 'toggle'},400)
});
// example 2: wrapping
$('#container').children('.box').on("click", ".link2", function(e){
$(this).parent().wrap('<div class="wrapped"></div>')
});
this.update();
},
update: function(){
$('#container').children('.box').addClass('someclass');
// more code here...
}
};
$.fn[pluginName] = function(option) {
var options = typeof option == "object" && option;
return this.each(function() {
var $this = $(this);
var data = new Plugin($this, options);
if(!$.data($this, pluginName)){
$.data($this, pluginName, data);
}
if(typeof option == 'string'){
data[option]();
}
});
};
/**
* Default settings(dont change).
* You can globally override these options
* by using $.fn.pluginName.key = 'value';
**/
$.fn[pluginName].options = {
name: 'world'
};
})(jQuery, window, document);

If you bind events more than once this problem will occur.
// inline script
$('#container').myplugin01();// binding first time
$('#somelink').click(function(e){
$('#container').append('<div class="box">link 1link 2<div>content goes here...</div></div>');
$('#container').myplugin01('update');// binding second time
// We suggest you to unbind here and rebind it.
});

Related

get id of dropped div using dragula javascript

I am trying to update a c3.js chart using drag and drops with dragula.js, but I don't know how to get the id of the div that is dragged into a new container. My html is something like this:
<div id="collapse1" class="panel-collapse collapse">
<div id="color1" class="form-inline">1</div>
<div id="color2" class="form-inline">2</div>
<div id="color3" class="form-inline">3</div>
</div>
<div id="collapse2" class="panel-collapse collapse">
</div>
and I'm using dragula.js to drag and drop:
dragula([collapse1,collapse2]);
I am really new to jquery, but following this question, to access the id of the <div> dropped into collapse2 in I was trying to do something like this:
alert($("#collapse1.collapse2 div:first").attr("id"));
But no results. Any help would be really appreciated
Dragula has three Elements One is Source Div, Target Div and Its associated Element. Following Method Works For Me as Charm except i am Not using get() method which has version issue.
You Can Try Both.
Dragula gives you the id of dropped div, Source Div, Target Div.
const dragula = Dragula(['', '']);
dragula.on('drop', (el, target, source, sibling) => {
const elementId = $(el).attr("id");
const targetID = $(target).attr("id");
const sourceId = $(source).attr("id");
}
Can't answer the question directly because I am not familiar with dragula. However, I have used jqueryUI drag drop extensively and its a really good tool. You might want to give that framework a try.
Since you asked for an example, I dug into some of my old code. You might want to go look through the jqueryUI draggable and droppable tutorials to give you some background before looking at this. I have included parts of a function. I put little dots to show you where code has been left out. I have put <<< next the key lines for you. Notice how I use closure to make references available across different parts. Closure is soooo awesome. I abuse the death out of it, so learn how to use it if you can.
Note that once I got my drag object, that is what you are asking for. Notice how I reference the variable to my function later when I register the draggable.
Btw, notice there is also a stop drag function referenced which I don't show the definition of. If you move the declaration of the dragObject outside of startDrag then you can also see it from stopDrag since the definition of the function is "enclosed" in the outside register function.
function tapeChart_registerDraggables(parentObject,scope) {
if ((parentObject==null)||(parentObject==undefined)) {
parentObject=$jq(document.body);
}
var availablesShow = false;
var savingToServer = false;
var dragClone = null;
var startDrag = function(event, ui) {
tapeChartDraggingReservation = true;
var dragObject = event.target; <<<<<<
if (dragObject.getAttribute("unassigned")=="true") {
var is_chrome = window.chrome;
var is_safari = navigator.userAgent.toLowerCase().indexOf('safari/') > -1;
if (!is_chrome && !is_safari) {
$(ui.helper).css("margin-left", event.clientX - $(dragObject).offset().left);
$(ui.helper).css("margin-top", event.clientY - $(dragObject).offset().top);
}
}
...
// assigned rooms
if (scope!="UNBLOCKED") {
// register items in the grid
$(parentObject).find( ".NODRAGHELPER" ).draggable(
{
snap : "true",
revert : "invalid",
start: startDrag, <<<<
stop: stopDrag
}
)
.click(function(){
if ( $(this).is('.NODRAGHELPER-dragging') ) {
return;
}
// seems that the user can drop and click fast
// prevent this
if (!savingToServer) {
tapeChart_getReservation(this);
}
return false;
});
}
...

Creating a way to navigate back when using jQuery for AJAX

I am dynamically loading content into part of a page using jQuery .load().
It is working well, but I am having trouble building a way for the user to navigate back to the original content after the new content has been loaded.
I have created a 'close' icon with css which exists on the new page which is loaded, but I am not sure how to set up the jQuery / JavaScript in order for it to navigate the user back to the original state of that part of the page.
This is the relevant js:
// pages to load
var loadLudwig = "lw.html";
$("#work a:first-child").click(function() {
$("#work").fadeTo('slow', 0, function() {
$("#work").load(loadLudwig, function(){
$("#work").fadeTo('slow', 1);
});
});
});
// (& this part is working fine)
The relevant HTML (on the original page) is like this (its a grid of images embedded within anchor tags):
<section id="work">
...img and svg stuff
</section>
I tried many variations of:
$("#close-button").click(function() {
$("#work").fadeTo('slow', 0, function () {
$("#work").load('home.html #work', function() {
$("#work").fadeTo('slow', 1);
});
});
});
but this loads the content very strangely / some of the original functionality of #work is lost.
How do I get my close button to navigate back to the original state of #work?
In the jquery documentation for .load() is stated that:
Script Execution
When calling .load() using a URL without a suffixed selector
expression, the content is passed to .html() prior to scripts being
removed. This executes the script blocks before they are discarded. If
.load() is called with a selector expression appended to the URL,
however, the scripts are stripped out prior to the DOM being updated,
and thus are not executed. An example of both cases can be seen below:
Here, any JavaScript loaded into #a as a part of the document will
successfully execute.
1. $( "#a" ).load( "article.html" );
However, in the following case, script blocks in the document being
loaded into #b are stripped out and not executed:
1. $( "#b" ).load( "article.html #target" );
This is a probable cause for lack of functionality.
I'd also look into event binding. In your code examples you're using .click but if you are loading content or you are creating elements on-the-fly you should be favoring .on(). This method delegates events instead of just binding them to a DOM node.
I'd recommend you reading the whole article.
EDIT:
Here is a quick n'dirty way of achieving the effect
// pages to load
var loadLudwig = "lw.html",
$ludwig,
$work = $('#work'),
$workContent = $work.children(),
$closeButton = $("#close-button");
$work.find('a:first-child').click(function() {
$work.fadeTo('slow', 0, function() {
//Here is the tricky part
//Detaching keeps all the jQuery data on the elements
$workContent.detach();
//The first time, load the content,
//if the content is already loaded
//append it to the container
if(!$ludwig){
$work.load(loadLudwig, function(){
//Save the content in a var
//so you can reuse it later
$ludwig = $work.children();
$work.fadeTo('slow', 1);
});
} else {
$ludwig.appendTo($work);
$work.fadeTo('slow', 1);
}
});
});
$closeButton.click(function() {
$work.fadeTo('slow', 0, function () {
//Remove the old content, don't worry
//because is stored in $ludwig
$work.children().detach();
//Instead of reloading the content, just
//attach the fragment again
$workContent.appentTo($work);
$work.fadeTo('slow', 1);
});
});
You probably need to save the html somewhere. For example:
// Top of file
var oldHTML = "";
// Lots of stuff...
$("#work a:first-child").click(function() {
$("#work").fadeTo('slow', 0, function() {
// Store the old html
oldHTML = $("#work").html();
$("#work").load(loadLudwig, function(){
$("#work").fadeTo('slow', 1);
});
});
});
// Code for the close button
$("#close-button").click(function() {
$("#work").fadeTo('slow', 0, function () {
$("#work").html(oldHTML).fadeIn("slow");
});
});
Alternatively, instead of replacing the html, you could create another child. Of course, you might have to slightly change your markup.
<section id="work">
<div id="oldHTML">
...img and svg stuff
</div>
<div id="newSection" style="display:none;">
</div>
</section>
Then replace $("#work") with $("#oldHTML") in your first piece of code like so:
$("#oldHTML a:first-child").click(function() {
$("#oldHTML").fadeTo('slow', 0, function() {
$("#oldHTML").hide();
$("#newSection").load(loadLudwig, function(){
$("#newSection").show().fadeTo('slow', 1);
});
});
});
// Code for the close button
$("#close-button").click(function() {
$("#newSection").fadeTo('slow', 0, function () {
$("#newSection").hide();
$("#work").fadeIn("slow");
});
});

Filtering Sigma.Js graph from external events

I have an instance of Sigma.Js 1.0.0 rendering a graph in my Canvas element. (The code is below, but you can simply scroll to Step 2 of Tutorial on the main sigmajs.org page.
As you can see from that code, when the node is clicked, clickNode event occurs, which then applies filtering to the graph, showing only the clicked node and its neighborhood and dimming the others. That's quite clear.
However, how would I make exactly the same thing happen from the outside? Suppose I have the graph rendered already and I have a Tag Cloud next to it. And I want that when I click on a #hashtag, only that node is shown in the graph and the rest are dimmed. How would I do that?
Thanks!
<div id="sigma-container"></div>
<script src="path/to/sigma.js"></script>
<script src="path/to/sigma.parsers.min.gexf.js"></script>
<script>
// Add a method to the graph model that returns an
// object with every neighbors of a node inside:
sigma.classes.graph.addMethod('neighbors', function(nodeId) {
var k,
neighbors = {},
index = this.allNeighborsIndex[nodeId] || {};
for (k in index)
neighbors[k] = this.nodesIndex[k];
return neighbors;
});
sigma.parsers.gexf(
'path/to/les-miserables.gexf',
{
container: 'sigma-container'
},
function(s) {
// We first need to save the original colors of our
// nodes and edges, like this:
s.graph.nodes().forEach(function(n) {
n.originalColor = n.color;
});
s.graph.edges().forEach(function(e) {
e.originalColor = e.color;
});
// When a node is clicked, we check for each node
// if it is a neighbor of the clicked one. If not,
// we set its color as grey, and else, it takes its
// original color.
// We do the same for the edges, and we only keep
// edges that have both extremities colored.
s.bind('clickNode', function(e) {
var nodeId = e.data.node.id,
toKeep = s.graph.neighbors(nodeId);
toKeep[nodeId] = e.data.node;
s.graph.nodes().forEach(function(n) {
if (toKeep[n.id])
n.color = n.originalColor;
else
n.color = '#eee';
});
s.graph.edges().forEach(function(e) {
if (toKeep[e.source] && toKeep[e.target])
e.color = e.originalColor;
else
e.color = '#eee';
});
// Since the data has been modified, we need to
// call the refresh method to make the colors
// update effective.
s.refresh();
});
// When the stage is clicked, we just color each
// node and edge with its original color.
s.bind('clickStage', function(e) {
s.graph.nodes().forEach(function(n) {
n.color = n.originalColor;
});
s.graph.edges().forEach(function(e) {
e.color = e.originalColor;
});
// Same as in the previous event:
s.refresh();
});
}
);
</script>
<!-- [...] -->
I hope this goes some way to answering your question.
You have a tagcloud full of words, and when a word is clicked, you want to trigger the neighbors method on your sigma instance, for which you need the node id.
Simply put, you need the function which is called when the #hashtag is clicked, to be in the same scope as the sigma instantiation.
s= new sigma({
settings: {...}
})
//more code instantiating methods etc
//let's assume your tags are in elements with class='tagword' and have the hashtag stored in a 'name' attribute
$('.tagword').on('click', function(){
var name = this.attr('name');
s.graph.nodes().forEach(function(n){
if (n.label == name){
//use the node to trigger an event in sigma
//i.e. s.graph.neighbors(n.id);
};
};
};

Javascript - ZeroClipboard copied content doesn't get updated

I have 2 pre blocks, each of them is wrapped with a div and has a copy button.
<div class="code">
<a class="copy">copy</a>
<pre>content of 1st pre</pre>
</div>
<div class="code">
<a class="copy">copy</a>
<pre>content of 2nd pre</pre>
</div>
$('.code').on('mouseenter', function() {
var copy_button = $(this).find('.copy');
var clip = new ZeroClipboard(copy_button, {moviePath: 'ZeroClipboard.swf'});
var content = $(this).find('pre').text();
// at this point, content is always right
// alert(content);
clip.on('mousedown', function(client, args) {
// the content doesn't get updated here
alert(content);
clip.setText(content);
});
});
The problem is, it seems that it always copys the conent of the first-mouseentered-div.
Say I first mouseentered div2, and clicked copy, the content (content of 2nd pre) is copied fine. But then when I try to copy the first pre, the content doesn't get updated, it's still content of 2nd pre.
What am I doing wrong here? How can I fix this?
OK, I found another jQuery plugin - zClip, which is built using the Zero Clipboard library. It's much easier to use and configure.
$('.copy').zclip({
path: 'ZeroClipboard.swf',
copy: function() {
var tocopy = $(this).parent().find('pre').text();
// formatting content
// ...
return tocopy;
},
beforeCopy:function(){
// do something before copy
},
afterCopy:function(){
// do something after copy
}
});
You keep on adding more and more events on mouse enter.
This should unbind events so you don't add each time:
.on('mouseout', function(){
$(this).unbind();
});

Stuck on structure of this jquery plugin

I'm practicing jquery plugins and need help finishing this one, especially the outside skeleton.
Let's say I have this markup
<div class="tochange"></div>
<div class="tochange"></div>
<div class="tochange"></div>
and I want the plugin to add to div.tochange this markup
<div class="root">Root</div>
so that it's
<div class="tochange">
<div class="root">Root</div>
</div>
Then if the root is clicked, replace it with 2 divs so the markup looks like this
<div class="tochange">
<div class="child">Child</div>
<div class="child">Child</div>
</div>
If the child is clicked, it goes back to parent
<div class="tochange">
<div class="root">Root</div>
</div>
I'm following documentation but I don't know if I need methods for this. My guess is that I do but I can't finalize the structure of this plugin in my head. This is my first plugin and I thought a practical idea of my own is the best way to learn, but I'm stuck. Can someone who's done this before set me on the right track. I'm a little lost on this.
(function($){
$.fn.sample = function(options) {
var settings = {
'possibleparam1' : 'value1',
};
var methods = {
init: function( options ) { },
tochildren : function( ) { },
toparent : function( ) { },
};
return this.each(function() { /*(i) {*/
// If options exist, merge them with our default settings
if (options) {
$.extend(settings, options);
}
// plugin code goes here
});
};
})( jQuery );
You can use several methods:
Try $("field_name").update("New text"); or $(this).replaceWith.
I have something like this in my site and I'm using: (don't mind the func name - its my ajax callback)
function setOutput()
{
if(httpObject.readyState == 4)
{
document.getElementById('photos').innerHTML = "<div id=\"gallery\" clas.....</ul></div>";
}
}

Categories

Resources