event binding with .on() for JQueryUI dialogs inside JQueryUI tabs - performance

I have a page with links of class 'dialog' that generate in JQueryUI dialog when clicked. Those dialogs are created from other elements present on the page, and can contain links of class 'add_tab', that should create a new JQueryUI [tab] (http://jqueryui.com/demos/dialog/) when clicked. Those tabs load their content via Ajax and consist identical structures. This means that an 'add_tab' link in a dialog creates a new tab, which contains 'dialog' links that generate dialogs containing further 'add_tab' links, and so on.
This is the basic HTML structure:
<div id="tabs">
<ul>
<li>tab 1</li>
</ul>
<div id="tabs-1">
<p>This tab contains a popup and a direct link to a new tab.</p>
<div id="popup1" style="display:nonee;">This popup contains a link to a new tab.</div>
</div>
Using JQuery 1.7's .on() method, I have troubles with properly registering the click handler for 'add_tab' links that appear in dialogs on added tabs. I manage to register the click handlers for the 'dialog' links in newly generated tabs (so that they generate a dialog), but fail to register click handlers for 'add_tab' links that appear inside those dialogs. I've put a simplified test version online at http://www.kantl.be/ctb/temp/jquerytest/tabs1.htm. Take, for example following scenario:
on http://www.kantl.be/ctb/temp/jquerytest/tabs1.htm , click 'popup': this will generate a JQueryUI dialog
in the dialog, click 'new tab': this will generate a new JQueryUI tab
in the the newly added tab labeled 'tabs2.htm', click 'popup': this will generate a JQueryUI dialog
in the dialog, click 'new tab': this will NOT generate a new JQueryUI tab, but instead open the target in a new window
==> this illustrates how this event handler is apparently NOT registered correctly for 'add_tab' links that occur inside dialogs that are generated in newly added tabs
in the tab labeled 'tabs2.htm', click 'new tab': this will generate a new JQueryUI tab
==> this illustrates how this event handler is registered correctly for 'add_tab' links that occur directly inside newly added tabs
This is my javascript code:
// these event registrations register clicks on $('a.dialog') and $('a.add_tab') to open new JQueryUI dialogs / tabs
// note: this event registration works for all such links on the original page
$('a.dialog').on('click', function(){
$($(this).attr('href')).dialog();
return false;
});
$('a.add_tab').on('click', function(){
$tabs.tabs( "add", $(this).attr('href'), 'added tab');
$('.ui-dialog-content').each(function(){$(this).dialog('close')})
return false;
});
// tabs: upon creation, register clicks on nested $('a.dialog') and $('a.add_tab') to open new JQueryUI dialogs / tabs
var $tabs = $( "#tabs" ).tabs({
add: function(event, ui) {
$tabs.tabs('select', '#' + ui.panel.id);
$tabs.tabs($tabs.tabs('option', 'selected'))
.on('click', 'a.dialog', function(){
$($(this).attr('href')).dialog();
return false;
})
// this registration doesn't seem to work for <a class="add_tab"> links occurring inside generated JQueryUI dialogs inside added JQueryUI tabs
.on('click', 'a.add_tab', function(){
$tabs.tabs( "add", $(this).attr('href'), 'added tab');
return false;
});
}
});
I'm nearly there! Could anyone help me out with the last event handler in the code above? Any help is much appreciated!

The idea with event delegation is to bind the event on a parent that is fixed on the page (not created dynamically). Using the selector parameter of the .on() method allows telling for which elements the event handler should be fired for.
In your code you are using event binding in two ways, event if you use .on():
first you do direct binding on the existing elements a.dialog and a.add_tab - this will only work for the links on tab1 as they are the only the ones existing at the time the code is executed, no event delegation here.
when adding a tab, you are doing event delegation on the tab container $tabs:
for the links to open a dialog with $tabs.on('click', 'a.dialog', function(){ ... }) - this works as expected because the link a.dialog are indeed within the tab container.
for the links in the dialog with $tabs.on('click', 'a.add_tab', function(){ ... }) - this won't work because when the dialog is created, the plugin moves the <div id="popup2"> at the end of the body (before </body>). So when you click the a.add_tab inside the dialog, it is not a descendant of the tab container anymore and event delegation does not happen.
Here's what I would do:
to avoid repeating the same code, declare your event handlers as variables
use event delegation on the tab container for links a.dialog and a.add_tab
when creating a new dialog, use event delegation on the dialog for the a.add_tab links it will contain
Here's the code:
var addTabClickHandler = function(e) {
$tabs.tabs("add", $(this).attr('href'), 'added tab');
$('.ui-dialog-content').each(function() {
$(this).dialog('close')
})
return false;
};
var dialogOpenClickHandler = function(e) {
$($(this).attr('href'))
.dialog()
.on('click', 'a.add_tab', addTabClickHandler);
return false;
}
var $tabs = $("#tabs").tabs({
add: function(event, ui) {
$tabs
.tabs('select', '#' + ui.panel.id)
.tabs($tabs.tabs('option', 'selected'));
}
});
$tabs
.on('click', 'a.dialog', dialogOpenClickHandler)
.on('click', 'a.add_tab', addTabClickHandler);

Meanwhile I've come up with a workaround that just registers the event handlers once on the 'tabs' container and appends the dialog to the 'tabs' container upon creation. My initial code could thus be trimmed down to:
// tabs: upon creation, register clicks on nested $('a.dialog') and $('a.add_tab') to open new JQueryUI dialogs / tabs
var $tabs = $( "#tabs" ).tabs({
create: function(event, ui) {
$(this)
.on('click', 'a.dialog', function(){
// dialog: upon creation, move the dialog to the tab to ensure delegated event registration
$($(this).attr('href')).dialog().parent('.ui-dialog').appendTo($tabs);
return false;
})
.on('click', 'a.add_tab', function(){
$tabs.tabs( "add", $(this).attr('href'), $(this).attr('href'));
$('.ui-dialog-content').dialog('close');
return false;
})
}
});

Related

Ajaxinate Endless scolling has stopped product Quick View from working

I am using Shopify "Streamline Theme" with quick product view and I recently added infinite scroll to products on each collection using Ajaxinate.js.
When I open a collection page it loads with some products which is supposed to do, The products already there work fine with quick view and quick add to cart and also.
The Infinite scroll works fine and it loads new product fine but the problem is raised when the new products loaded through AJAX call doesn't have work with the quick view function.
I have tried to create a callback function to activate the quick view with no success, using the theme initialisation code with no success.
function callBack(){
theme.init();
theme.initQuickShop();
};
document.addEventListener("DOMContentLoaded", function() {
var endlessClick = new Ajaxinate({
method: "scroll",
loadingText: 'Loading...',
callback: callBack
});
});
Edit -------
My problem, is that when the page is loaded only the initial loaded products quickview elements are loaded in the DOM. When the scroll more button is clicked, the newly loaded products are loaded without their respective quickview elements. Hence why the quickview does't work for them. The theme.js file comes with this initialisation code:
theme.reinitProductGridItem = function($scope) {
if (AOS) {
AOS.refreshHard();
}
if (theme.settings.currenciesEnabled) {
theme.currencySwitcher.ajaxrefresh();
}
// Reload quick shop buttons
theme.initQuickShop(true);
// Refresh reviews app
if (window.SPR) {
SPR.initDomEls();SPR.loadBadges();
}
// Re-register product templates in quick view modals.
// Will not double-register.
sections.register('product-template', theme.Product, $scope);
// Re-hook up collapsible box triggers
theme.collapsibles.init();
};
I have tried to integrate this into a callback but no success, the quickview modal doesn't seem to load for the newly loaded products:
function callBack(){
ReloadSmartWishlist();
var $container = $('#CollectionSection');
theme.reinitProductGridItem($container);
// I have tried the following init qith no success:
// theme.init();
// theme.initQuickShop(true);
// theme.initQuickShop();
// sections.register('product-template', theme.Product, $container);
// AOS.refreshHard();
};
document.addEventListener("DOMContentLoaded", function() {
var endlessClick = new Ajaxinate({
method: "click",
loadingText: 'Loading...',
offset: 0,
callback: callBack
});
});
I am missing something but what? :/
Note for other things like loading products images with the callback and the wishlist app, it works as intended...
When you load elements via AJAX and if the events are not attached to a parent element that is not removed from the DOM, those elements will not have an attached event to them.
The term used here is event delegation.
Here is an example of non-delegated event:
document.querySelectorAll('a').addEventListener('click', function(){
// Do something
})
Since you are attaching the event to the existing "a" elements if you add new 'a' via AJAX those elements will not have the event since Javascript already attached all the events and it will not reattach them if you don't specifically recall them again.
Here is an example of a delegated event:
document.querySelector('body').addEventListener('click', function(target){
let target = event.target;
if (target.tagName === 'A'){
// Do something here
}
})
Where we attach the event to the body tag ( where it's a better idea to attach it to a closer none-modified parent element of the ajax items ) and once we click we check if our target tag is an "a" and do something then.
So long story short, you will need to delegate the quick cart link so that it works after you load the items via AJAX.
Drip is correct you need to delegate your event, but for people like me it's hard to completely understand how to do that.
I'm not sure how your quickview is structured, but if you open it with a .click function and can use jquery use the [.on() function][1].
For example: I use a quickview that opens on a button click. My button is attached to my product-grid-item.liquid with this bit of code:
<div class="quick-view-button">
<a class="quick-view" data-handle="{{ product.handle }}" href="javascript:void(0);">Quick View</a>
</div>
My quickview function originally looked like this:
function quickView() {
$(".quick-view").click(function () {
//all of the quickview code
What happens is exactly like you described. The event listeners only loaded on the first product load but nothing after an AJAX load.
Using jquery's .on() binds the event listener to the element meaning when it's loaded in later it'll still have the event. Here's an example of what my code looks like after using .on()
function quickView() {
$('body').on('click','.quick-view',function(){
I really hope this helps you or someone else with this problem.
[1]: http://api.jquery.com/on/

Eventlisteners for jqm side panel on multiple pages

I am new to JQM and I'm using Phongeap with JQM for a new project.
My javascript is in one single js file and I'm loading the views from multiple html files.
As the transitions of the siede panel are poor, when I change pages via <a href="page2.html"> I tried to to use event listeners for the Menuitems.
function setPanelListeners(){
$('#menu_search').click(function() {
switchPageTo('search.html');
});
$('#menu_schedule').click(function() {
switchPageTo('program.html');
});
$('#menu_news').click(function() {
switchPageTo('news.html');
});
}
I call this function on the pagebeforeshoe event of each page. To fix the transitions to the way I need it, I use this function
// Close Panel then change page
function switchPageTo(url){
$('#menupanel').panel('close');
setTimeout(function() {
$.mobile.changePage( url, { transition: 'fade'} );
},200);
}
So here is the Problem. It actually works fine on the first page. But on the second page the Menuitems won't work, I guess the event listers are not listening for the new panel, because in the html the panel is loaded twice! And the Event listeners only listen for the first panel (from the first page) which is not displayed on the second page.
Any help is appreciated!
Are you adding panel dynamically on the second page? In JQM 1.3 you have to add panel separately to each page, although this restriction will be removed in future versions.
Use event delegation for dynamic panels:
$(document).on('click' ,'#menu_search' function() {
switchPageTo('search.html')
});

How Do I Reselect A KendoUI TabStrip After AJAX Postback in UpdatePanel

Setup
I've got a Telerik Kendo UI TabStrip with multiple tabs inside of an UpdatePanel...
<asp:UpdatePanel ID="DataDetails_Panel" UpdateMode="Conditional" runat="server">
<div id="ABIOptions_TabContainer">
<ul>
<li>Attendance</li>
<li>Grades</li>
<li>Gradebook</li>
<li>PFT</li>
<li>Scheduling</li>
<li>Miscellaneous</li>
<li>Parent Data Changing</li>
</ul>
</div>
</asp:UpdatePanel>
...which I then wire up in javascript later...
var optionTabContainer = $("#ABIOptions_TabContainer").kendoTabStrip({
animation: {
open: {
effects: "fadeIn"
}
},
select: onMainTabSelect
}).data("kendoTabStrip");
Scenario
The users will click on the various tabs and inside of each tab are settings for our portal. When they are in a tab and they make a change to a setting, the expectation is that they'll click on the 'Save' button, which will perform a postback to the server via ajax, because it is in the update panel.
Current Behavior
After the post back happens and the ul content comes back, I reapply the kendoTabStrip setup function call, which makes none of the tabs selected. This appears to the user like the page is now empty, when it just had content.
Desired Result
What I want to do, is after the partial postback happens and the UpdatePanel sends back the ul, I want to reselect the tab that the user previously selected.
What Already Works
I already have a way to preserve the tab that the user clicked on:
var onMainTabSelect = function (e) {
tabToSelect = e.item;
console.log("onTabSelect --> ", e.item.textContent);
}
and a function to reset the selected tab whenever it is called:
function setMainTab() {
if (!jQuery.isEmptyObject(tabToSelect)) {
var tabStrip = $('#ABIOptions_TabContainer').data("kendoTabStrip");
console.log("Attempt to set tab to ", tabToSelect.textContent);
tabStrip.select(tabToSelect);
} else {
console.log("tabToSelect was empty");
}
}
What Doesn't Work
My hypothesis is that the Kendo TabStrip says, "Hey, that tab is already selected" when I call the setMainTab after my postback:
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_endRequest(function () {
BindControlEvents();
setMainTab();
});
...and therefore, doesn't set my tab back. If I click on the tab, then Poof, all my content is there just like I expect.
Any ideas what I may be doing wrong?
I ended up changing the onMainTabSelect method to:
var onMainTabSelect = function (e) {
tabToSelect = $(e.item).data("tabindex");
}
which gets me the data-tabindex value for each li in my ul. I couldn't get the tab index from kendo, so I had to role my own. Once I got that value, then I was able to set the selected tab via an index rather than the tab object reference itself.

Jquery code not executing on elements that didn't exist on page load

I have a web application that loads javascript on page load:
$(function() {
$('.modal-delete').click(function() {
alert();
});
});
I have a html page with a series of buttons which alert a blank message box when they're clicked:
<button class="modal-delete btn danger"></button>
which works fine.
However, a have some AJAX calls that generate more buttons just like the ones above but NOT on page load! They can be created at any time. These buttons do not do anything but they're meant to load the alerts. They're identical but because they never existed on page load, the Jquery code doesn't work on these buttons. How do I attach the same code to these buttons too?
Many thanks :).
I think you'll want jQuery's 'live()' function:
$('.modal-delete').live('click', function() {
alert();
});
This binds to the elements which match but also rebinds when new elements are added in the future:
"Attach an event handler for all elements which match the current selector, now and in the future"
Change the ready code to this...
$(function() {
$("document").on("click", ".modal-delete", function() {
alert("click");
});
});

Ajax loader and events runs more than once

I have a website that pages' contents loaded by ajax. All of my pages are seperate files actually and when I need to call a page, I just passed the link to my "pageLoader" function.
pageLoader handle with content loading and re-ignite/re-define the necessary functions like close button.
Since the actual function have ~250 lines, I did re-write a short version;
var pageLoader = function(link){
var page = $(link).attr("href").replace('#','');
if(page != lastCalledURL){
// Load the page.
$.ajax({
beforeSend: function(){ /* remove previously loaded content; */ },
success: function(data){
// remove the loaded content if user clicked to close button.
$("a.close-button").live("click", function(){
$(this).parent().fadeOut().remove();
return false;
});
// load another page if user click another page's link.
$("a.content-loader-link").live("click",function(){
pageLoader(this);
});
// handle with tabs
$("a.tabs").live("click", function(){
var index = $("a.tabs").index(this);
console.log(index);
return false;
});
}
});
lastCalledURL = page;
}
return false;
OK. If I click a link in the page, It calls pageLoader. If I click one of the links just once, pageLoader called once. If I click another link, pageLoader called twice. If I click another link again, pageLoader called third times and so on.
Same things happen for the links that bind with "live" function in the code. If I click a.tabs, it write to console twite. If I clicked another .tabs link, it write to console four times and increasing double for every click.
I don't why it happens. Please let me know if you have any idea.
You can solve it by using the bind and unbind. Like this:
$("a.tabs").unbind('click').bind("click", function(){
var index = $("a.tabs").index(this);
console.log(index);
return false;
});
But i would prefer that you attach this events in your $(document).ready function instead of everytime you make an AJAX call.
$(document).ready(function() {
// Attach your events here.
});
Those live event handlers "Attach a handler to the event for all elements which match the current selector, now and in the future." so you shouldn't need them in your ajax call.

Resources