I'm developing a JQM theme using single pages. I also have a side bar / panel that is built as a seperate html file. This panel is imported into the JQM page using the following JS;
/* Creates the functionality to open the left side panel with a swipe */
$(document).one("pagebeforecreate", function () {
$.get('left-panel.html', function(data){
$.mobile.pageContainer.prepend(data);
$("[data-role=panel]").panel().enhanceWithin(); // initialize panel
}, "html");
});
Ive got this script in a js file that is loaded at the foot of every page, since users of the 'mobile site' could enter via any page.
Ive noticed via Firebug that an instance of the panel seems to be added with every page I navigate to. So if I visit 3 pages, the panel will be loaded 3 times, 4pages = 4 panels, etc.
It's fair to say I'm fairly novice at JQ & JQM, but I was under the impression that the use of
$(document).one
meant the event only occurred once per page, and would therefore prevent the issue I have.
IF you can help me work out how I can prevent this issue, I'd really appreciate it.
The pagebeforecreate event will emit on each and every page, but only ONCE. If you have 5 pages in one HTML file (Multi-Page Model), that event will fire 5 times before creating/showing the target page.
This event can't be delegated to a specific page, e.g. the below code won't work.
$(document).on("pagebeforecreate", "#pageX", function (event) {
/* do something to pageX */
});
unlike pagecreate which can be delegated to a specific page.
$(document).on("pagecreate", "#pageX", function (event) {
/* use it to add listeners */
});
However, you can obtain an object of that page which is being created.
$(document).on("pagebeforecreate", function (event) {
var page = event.target.id;
if ( page == "pageX") {
/* do something to pageX */
}
});
Why .one()?
Since pagebeforecreate can't be delegated and it fires on each page, using .one() will run code once only. However, if you repeat the same code using .one() that code will be executed it again.
Altenative approaches:
Check whether panel is added before adding it.
$(document).one('pagebeforecreate', function () {
var panelDOM = $("[data-role=panel]").length;
if (panelDOM === 0) {
/* add panel */
} else {
/* nothing */
}
});
Demo
Use mobileinit as it fires once per document/framework. This event fires before loading jQM, so you will need to enhance/_initialize_ panel after loading jQM.
<script src="jquery-1.9.1.min.js"></script>
<script>
/* inject panel */
$(document).on("mobileinit", function() {
var panel = '<div>panel</div>';
$("body").prepend(panel);
});
</script>
<script src="jquery.mobile-1.4.2.min.js"></script>
<script>
/* initialize it */
$(function() {
$("[data-role=panel]").panel().enhanceWithin();
});
</script>
Demo
Related
I load a part of a page inside a Div on my frontpage using .load. My problem is that the loaded content have a set if buttons with javascript inside them, and they wont execute and it goes to the systemä Im usings errorpage.
The buttons have the same script but different ids, name and classes and looks like this:
Cancel
I have tried multiple ways to load the content without any luck. If I load the whole page it works aside from all the other problems that gives.
Example of scripts I have tried:
Script 1:
$(".dcjqg-accordion ul.sub-menu").load("/m4n?seid=etailer-basket div#centerbox.itembox.centerbox, script”);
Script 2:
$('.dcjqg-accordion ul.sub-menu').load('/m4n?seid=etailer-basket div#centerbox.itembox.centerbox, script', function () {
$.getScript('urltomyscript'); //the script that handles the buttons, as far as I can see
});
Script 3:
$(function(){
$.get('/m4n?seid=etailer-basket', function(result){
$result = $(result);
$result.find('#centerbox.itembox.centerbox').appendTo('.dcjqg-accordion ul.sub-menu');
$result.find('scripts').appendTo('.dcjqg-accordion ul.sub-menu');
}, 'html');
});
Script 4:
$('.dcjqg-accordion ul.sub-menu').load('/m4n?seid=etailer-basket div#centerbox.itembox.centerbox', function() {
$("#_ec_oie2").on("click", function() {
if (UI.pb_boolean(this, 'click')) { }
return false;
});
});
Nothing works. They all load the content but the buttons won't work.
Any ideas?
I am guessing that the problem occurs due to load being executed async, and the HTML therefore is updated AFTER the script that makes the buttons work have executed.
The simple way to solve it, is firing the button script in a callback:
$(".dcjqg-accordion ul.sub-menu").load("/m4n?seid=etailer-basket div#centerbox.itembox.centerbox, script”, function(){//this get executed, when content has loaded});
try to change the script 4 to:
$('.dcjqg-accordion ul.sub-menu').load('/m4n?seid=etailer-basket div#centerbox.itembox.centerbox,script'); //Load the content
i'm not 100% sure that this will load the script also.
and for dynamic content you have to use event delegation
Event delegation allows us to attach a single event listener, to a parent element, that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future.
$(document).on("click","#_ec_oie2",function(){
if (UI.pb_boolean(this, 'click')){
}
return false;
});
I have a site that I made every page fade nicely when you click any a tag in my pages. The site runs very smoothely with just the local domain html pages. Once I added the social sharing widgets for facebook, google+, linkedin, and twitter, my pages now have much more latency as the window bind events are waiting for the entire page to load.
Is it possible for me to make the bind / load events wait for everything to load just from the local domain or create a list of domains to ignore for my div fade effect methods below called fadeColors(). (the sharing widget i made is collapsed closed and shows right away which is fine because its just a button. I want the sharing widget's contents which are the four remote social urls to load in their own time, but not impede the fade effects I am doing on each page with the div called 'overlay'.
<!-- HTML INDEX PAGE -->
<body>
<div id=overlay> </div>
<script type="text/javascript" src="js/custom.js"></script>
</body>
<!-- This Script is remotely included called custom.js -->
$(document).ready(function() {
var $colorsHTML =
'<div class="styleSwitcher">' +
'<i class="icon-export"></i>' +
'<div id="switcherContent">' +
'<div style="margin-top:25px;height:115px;">';
if(!$('#onePage').length){
$colorsHTML +=
'<div class="layoutStyle">' +
'<div style="padding-left:25px;"><script src="//platform.linkedin.com/in.js" type="text/javascript"> lang: en_US</script><script type="IN/Share" data-url="https://example.com"></script></div>' +
'<div style="padding-left:25px;padding-bottom:12px" class="fb-share-button" data-href="https://example.com" data-layout="button"></div>' +
'<div style="padding-left:25px;padding-bottom:2px"><g:plus action="share" annotation="none"></g:plus></div>' +
'<div style="padding-left:25px;padding-bottom:12px">Tweet<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?\'http\':\'https\';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+\'://platform.twitter.com/widgets.js\';fjs.parentNode.insertBefore(js,fjs);}}(document, \'script\', \'twitter-wjs\');</script></div>' +
'</div>';
}
$("body").append($colorsHTML);
});
/* ONLOAD and UNLOAD FADED EFFECTS */
// goto local or remote url
function fadeExit(url) {
window.location = url;
}
// trap any clicked a tag in the page to exit to the next page
$('a').click(function(event) {
event.preventDefault();
var url = $(this).attr('href');
fadeExit(url);
});
// fade the div overlay that is set above all content
function fadeColors(target) {
$("#overlay").fadeTo(250, 0, function() {
$("#overlay").css("display", "none");
});
}
// Once the page is done loading call our function
$(window).bind("load", function() {
fadeColors();
})
You have a couple of options:
1) Restructure your code similar to #restlessbit's suggestion in the comments. Include your fade setup code and function definitions in custom.js, but invoke fadeColors() at the end of your ready handler:
$("body").append($colorsHTML);
fadeColors();
and then remove your bind call
// Once the page is done loading call our function
$(window).bind("load", function() {
fadeColors();
})
2) If that doesn't work as expected, refactor your share link setup code into a separate script, and load it asynchronously. Keep the fadeColors() invocation, and set up the share widget container, in the ready handler. (You mention that the widget container is collapsed to start, so you shouldn't have any issues with content reflows while the guts of the container loads.
Also, a couple of notes:
Your script contains unbalanced div tags in the initial assignment of $colorsHTML.
Consider using on rather than the deprecated bind.
You may need to move your a click handler assignment into the document.ready handler. As it stands, it's being invoked immediately, which may prevent some of your links from being instrumented as you expect. Alternately, use delegated event handling by registering a click listener on a higher level DOM, so no matter when a new a element is created, clicks will be intercepted and properly handled. There are tradeoffs to each approach, so your best option will depend on your use case and the structure of your pages.
In my Rails View template, I'm using some jQuery for tabbed panels functionality:
<section>
... content ommitted
</section>
<script>
$(document).ready(function () {
$('.accordion-tabs-minimal').each(function(index) {
$(this).children('li').first().children('a').addClass('is-active').next().addClass('is-open').show();
});
$('.accordion-tabs-minimal').on('click', 'li > a', function(event) {
if (!$(this).hasClass('is-active')) {
event.preventDefault();
var accordionTabs = $(this).closest('.accordion-tabs-minimal')
accordionTabs.find('.is-open').removeClass('is-open').hide();
$(this).next().toggleClass('is-open').toggle();
accordionTabs.find('.is-active').removeClass('is-active');
$(this).addClass('is-active');
} else {
event.preventDefault();
}
});
});
</script>
Because I'm also using this script in other View templates, and I want to organize my Javascript a bit better, I created a Javascript file (tabbed_panels.js) under app/assets/javascripts and moved the above script to the tabbed_panels.js file.
However now the panels on my page have no content the first time the page is loaded. Only when the page is refreshed, the panels get loaded with content.
Does someone have an idea what's going on and how this can be solved, so my tabbed panels have content at the first page load?
thanks for your help,
Anthony
Turbolinks
The likely issue you have will be that you're trying to load the $(document).ready function with Turbolinks running.
This simply won't work (if you're using Turbolinks), as since Turbolinks refreshes only the <body> tag of your page, it will typically prevent your JS from binding to the various elements in the DOM, as the JS has not been reloaded
The way to fix this is to develop your JS around Turbolinks (using Turbolinks' event handlers):
#app/assets/javascripts/tabbed_panels.js
var new_items = function() {
$('.accordion-tabs-minimal').each(function(index) {
$(this).children('li').first().children('a').addClass('is-active').next().addClass('is-open').show();
});
$(document).on('click', '.accordion-tabs-minimal li > a', function(event) {
if (!$(this).hasClass('is-active')) {
event.preventDefault();
var accordionTabs = $(this).closest('.accordion-tabs-minimal')
accordionTabs.find('.is-open').removeClass('is-open').hide();
$(this).next().toggleClass('is-open').toggle();
accordionTabs.find('.is-active').removeClass('is-active');
$(this).addClass('is-active');
} else {
event.preventDefault();
}
});
});
$(document).on("page:load ready", new_items);
This should allow your tabs to be populated on page load, regardless of whether Turbolinks is operating or not.
--
Unobtrusive JS
Something else to consider (you've already done this), is that you really need to use unobtrusive javascript in your application.
Unobtrusive JS basically means that you're able to abstract your "bindings" from your page to your Javascript files in the asset pipeline. There are several important reasons for this:
Your JS can be loaded on any page you want (it's DRY)
Your JS will reside in the "backend" of your app (won't pollute views)
You'll be able to use the JS to populate the various elements / objects you want on screen
It's always recommended you put your JS into separate files - including in the views sets you up for a big mess down the line
In my jquery mobile application I have one page that I want to load external content.
Trying to follow the docs, and my code doesn't produce any script errors, but my external content does not load.
$(document).ready(function () {
$.mobile.ajaxEnabled = false;
//Initialize page container per docs
$("#staff-directory-container").pagecontainer({ defaults: true });
//Get external content into DOM
$.mobile.loadPage("http://another.domain.com/myContent.html", {
pageContainer: $('#staff-directory-container')
});
});
Thanks in advance for any help offered....
Chris
To bind events in jQuery Mobile, use pagecreate which is equivalent to .ready(). To load an external page, use .pagecontainer("load", "target", { options }) as .loadPage() is deprecated and will be removed in jQM 1.5.
In your case, $.mobile.pageContainer is $("#staff-directory-container"). And note that Ajax should be enabled.
$(document).on("pagecreate", "#pageID", function () {
$("#loadBtn").on("click", function () {
/* define new pagecontainer then load */
$.mobile.pageContainer = $("#staff-directory-container").pagecontainer();
$.mobile.pageContainer.pagecontainer("load", "myContent.html");
});
});
i am having trouble getting ajax loaded links to load other ajax content.
Basically this is my ajax code:
$(function () {
var api = $("#content").jScrollPane().data('jsp');
var reinitialiseScrollPane = function()
{
api.reinitialise();
}
// attaching click handler to links
$("#contentcontainer a[href]").click(function (e) {
// cancel the default behaviour
e.preventDefault();
// get the address of the link
var href = $(this).attr('href');
// getting the desired element for working with it later
var $wrap = $('#content');
$wrap
// removing old data
api.getContentPane()
// load the remote page
.load(href, reinitialiseScrollPane , function (){
}
);
});
});
Basically the links inside the navigation work fine since they are loaded when page is loaded, but links inside the ajax content (wich are supposed to load pages in the same place the navigation links load content) dont work, my understanding is that there needs some sort of ".live" function called as the js does not rescan the code once ajax loads content.
I found some solutions but none i could relate to the code im using.
The first part of the code is not ajax but for a scrollbar plugin, i did not remove it because id like to avoid it getting voided by a solution that dosent keep it into count.
Thanks.
Try using the .on() method (see jQuery documentation) when attaching the click handler:
$(document).on('click', '#contentcontainer a[href]', function (e) {
// Rest of your code here
});