jQuery Mobile displays all Tab containers on linked page - jquery-ui

I'm new to jQuery Mobile. I'm trying to implement a site using a main index.html (containing header, footer and main Page body) and multiple Page partials (one page per file).
One of the partials that gets swapped into that body uses the Tabs widget. When I trigger the link to this page, the tabs load "flat", as one would expect when the jQUI/jQM code doesn't work its magic.
If I put this same markup in index.html, it looks fine. My guess is that something needs to run to initialize the secondary page, but I don't know what. I'm already listening for pagechange, but don't know what to call to initialize the Tabs widget.
I threw the code into this Plunkr, but jQM doesn't seem to work there (only jQUI?).

This seems to be a duplicate of Jquery mobile Tabs not working on external page, which references jQM issue 7169. The fix is targeted for version 1.5.
A workaround is given in this comment, from gabrielschulhof:
$.widget( "ui.tabs", $.ui.tabs, {
_createWidget: function( options, element ) {
var page, delayedCreate,
that = this;
if ( $.mobile.page ) {
page = $( element )
.parents( ":jqmData(role='page'),:mobile-page" )
.first();
if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
delayedCreate = this._super;
page.one( "pagebeforeshow", function() {
delayedCreate.call( that, options, element );
});
}
} else {
return this._super();
}
}
});

Related

JQM 1.4 - What is the preffered way to use pagecontainer event on a specific page/selector?

jQuery Mobile v1.4, we are february 2014.
So, I've read here (gihub) that we are supposed to make an if statement on catched pagecontainer events to assume if the page loaded is the one intended.
Here is a little scenario trying to understand the intended behavior of the new pageContainer widget and it's intended use.
Simple as that, a login page, pre-fetch a welcome page programatically, then switch to welcome page on succesful login. Welcome page have some JS script to animate the page, must be launched only when page is visible. How do we do that ?
Here are the results I got while investigating the pagecontainer events through the console. My goal here is to find a way to detect that my welcome (any page in fact) page is active/visible.
I used this format as query for the sake of understanding:
$( 'body' ).on( 'pagecontainerbeforeload', function( event, ui ) {
console.log("beforeload");
console.log(event);
console.log(ui);
});
So fresh start, when I load a page & JQM for the first time (ie. /Users/login)
Events usable :
pagecontainercreate => empty ui
PCbeforetransition => ui.toPage usable
PCshow => only get a ui.prevPage (which is empty in that case)
PCtransition => ui.toPage usable
Now, these are always launched even if you disabled the transition effects ( See api )
Ok, then I want to programatically load a page (pre-fetch it), I do : (I want /Users/welcome)
$("body").pagecontainer("load", "/Users/welcome");
I get these event launched (the page is not yet visible):
PCbeforeload => I get a url which I could use to identify the page..
PCload => pretty much the same data as PCbeforeload
All right, now I go change my page : (to /Users/welcome)
$("body").pagecontainer("change", "/Users/welcome");
I get these events triggered:
PChide => ui.nextPage is the same as a ui.toPage...
PCbeforetransition => ui.toPage usable
PCshow => only gives ui.prevPage
PCtransition => ui.toPage present as expected
Fine, now I'm pretty sure the only pagecontainer event I want to use to be sure that this page is loaded is pagecontainertransition. Here is what I implemented on every page that needs to launch JS :
Set id of the page container (PHP)
<div data-role="page" data-theme='a' id="<?php echo $this->id_url()?>">
...at the end of the page (JS)
$( 'body' ).on( 'pagecontainertransition', function( event, ui ) {
if(ui.toPage[0] == $('#'+id_url )[0] ) {
functionToLaunchWhenPageShowUp();
}
} );
Now, as you can see, I'm referring to ui.toPage 1st child [0] to compare it to $('.selector') 1st child [0] . Is that the right way to do that ? I mean, the intended way by the new API. Thanks to share your knowledge ;)
I managed to do something that works, is relatively simple, and as close I could to the DRY principle (don't repeat yourself).
In the order they are "loaded" :
JS script in < head > of document
<script type="text/javascript" src="/js/jquery-2.1.0.min.js"></script>
<script type="text/javascript">
(function(){
//Global settings for JQM 1.4.0
$( document ).on( "mobileinit", function() {
//apply overrides here
$.mobile.defaultPageTransition = 'flip';
});
// The event listener in question !
$( document ).ready(function () { //..only launched once the body exist
// The event listener in question :
$( 'body' ).on( 'pagecontainertransition', function( event, ui ) {
//gets the id you programatically give to your page
id_url = $( "body" ).pagecontainer( "getActivePage" )[0].id;
//compare the actual event data (ui.toPage)
if( ui.toPage[0] == $('#'+id_url )[0] ) {
// failsafe
if ( typeof( window[ id_url ].initPage ) === "function" ) {
// call the dynamically created init function
window[ id_url ].initPage();
}
}
} );
});
document.loadPage = function( url ){
$( "body" ).pagecontainer( "load", url , options);
};
document.changePage = function( url ){
$( "body" ).pagecontainer( "change", url , options);
};
})();
</script>
<script type="text/javascript" src="/js/jquery.mobile-1.4.0.min.js"></script>
Start of every returned page
<div data-role="page" data-theme='a' id="<?php echo $this->id_url()?>">
<div data-role="content">
<script type="text/javascript">
// we create dynamically a function named after the id you gave to that page
// this will bind it to window
this[ '<?php echo $this->id_url() ?>' ].initPage = function(){
// put anything you want here, I prefer to use a call
// to another JS function elsewhere in the code. This way I don't touch the
// settings anymore
NameOfThisPage.launchEverythingThatNeedsTo();
};
</script>
...
Here's the description for this code. First, I get one place for all those global query, JQM already forced me to put stuff in-between jquery.js & jquery.mobile.js, so let's use that.
Along with the global JQM settings, I use only one event listener bind to the body/document/(whatever it'll be in the future). It's only launched once the body exist.
Now, here's where the fun begins. I programatically give an id to every pages the server returns (namely the script route with _ instead of / ). You then create a function named after that id, it's attached to the window, (I suppose you could put it elsewhere..).
Then back to the event listener, on transition, you get that id you've set through pagecontainer( "getActivePage" ) method, use that id to grab the page jQuery style, then compare it with the data returned by the event listener.
If success, use that id to launch the init function you've putted in your page. There's a failsafe in case you don't put an init script in page.
Bonus here, are those document.loadPage / changePage . I've putted them there in case the methos to change page changes. One place to modify, and it'll apply to the entire app. That's DRY ^^
All in all, if you have comment on a way to improve this method, please share. There's a big lack of example for v1.4 methods (along with a bit of confusion with v1.3 examples). I've tried to share my discoveries the best I could (ps. I need those rep points :P )

Jquery calls not working in $viewContentLoaded of Angular

Unable to call jquery functions in $viewContentLoaded event of Angular controller, here is the code for the same.
$scope.$on('$viewContentLoaded', function() {
jQuery.growlUI('Growl Notification', 'Saved Succesfully');
jQuery('#category').tree()
});
Is any configuration required here?? I tried even noConflict(); var $jq = jQuery.noConflict();
Does it require any other configuration?
Thanks,
Abdul
First thing first, don't do DOM manipulation from controller. Instead do it from directives.
You can do same thing in directive link method. You can access the element on which directive is applied.
Make sure you load jquery before angularjs scripts, then grawlUI, three, angularJS and finally your application script. Below is directive sample
var app = angular.module("someModule", []);
app.directive("myDirective", function () {
return function (scope, element, attrs) {
$.growlUI('Growl Notification', 'Saved Succesfully');
element.tree();
};
});
angularjs has built in jQuery lite.
if you load full jquery after angular, since jQuery is already defined, the full jquery script will skip execution.
==Update after your comment==
I reviewed again your question after comment and realised that content which is loaded trough ajax is appended to some div in your angular view. Then you want to apply element.tree() jquery plugin to that content. Unfortunately example above will not work since it is fired on linking which happened before your content from ajax response is appended to element with directive I showed to you. But don't worry, there is a way :) tho it is quick and dirty but it is just for demo.
Let's say this is your controller
function ContentCtrl($scope, $http){
$scope.trees=[];
$scope.submitSomethingToServer=function(something){
$http.post("/article/1.html", something)
.success(function(response,status){
// don't forget to set correct order of jquery, angular javascript lib load
$.growlUI('Growl Notification', 'Saved Succesfully');
$scope.trees.push(response); // append response, I hope it is HTML
});
}
}
Now, directive which is in controller scope (it uses same scope as controller)
var app = angular.module("someModule", []);
app.directive("myDirective", function () {
return function (scope, element, attrs) {
scope.$watch("trees", function(){
var newParagraph=$("<p>" + scope.trees[scope.trees.length-1] + "</p>" ); // I hope this is ul>li>ul>li...or what ever you want to make as tree
element.append(newParagraph);
newParagraph.tree(); //it will apply tree plugin after content is appended to DOM in view
});
};
});
The second approach would be to $broadcast or $emit event from controller (depends where directive is, out or in scope of controller) after your ajax completes and you get content from server. Then directive should be subscribed to this event and handle it by receiving passed data (data=content as string) and do the rest as I showed you above.
The thing is, threat that content from ajax as data all the way it comes to directive, then inject it to element in which you want to render it and apply tree plugin to that content.

jQuery UI in Backbone View adds elements, but doesn't respond to events

I'm building an app in which I'm using Django on the backend and jQuery UI/Backbone to build the front. I'm pulling a Django-generated form into a page with jQuery.get() inside of a Backbone View. That part works fine, but now I want to add some jQuery UI stuff to the form (e.g. a datepicker, some buttons that open dialogs, etc). So, here's the relevant code:
var InstructionForm = Backbone.View.extend({
render: function() {
var that = this;
$.get(
'/tlstats/instruction/new/',
function(data) {
var elements = $(data);
$('#id_date', elements).datepicker();
that.$el.html(elements.html());
}
};
return this;
}
});
The path /tlstats/instruction/new/ returns an HTML fragment with the form Django has generated. What's happening is that input#id_date is getting the hasDatePicker class added and the datepicker div is appended to my <body> element (both as expected), but when I click on input#id_date, nothing happens. No datepicker widget appears, no errors in the console. Why might this be happening?
Also, somewhat off-topic, but in trying to figure this problem out on my own, I've come across several code examples where people are doing stuff like:
$(function() {
$('#dialog').dialog(...);
...
});
Then later:
var MyView = Backbone.View.extend({
initialize(): function() {
this.el = $('#dialog');
}
});
Isn't this defeating the purpose of Backbone, having all that jQuery UI code completely outside any Backbone structure? Or do I misunderstand the role of Backbone?
Thanks.
I think your problem is right here:
$('#id_date', elements).datepicker();
that.$el.html(elements.html());
First you bind the datepicker with .datepicker() and then you throw it all away by converting your elements to an HTML string:
that.$el.html(elements.html());
and you put that string into $el. When you say e.html(), you're taking a wrapped DOM object with event bindings and everything else and turning into a simple piece of HTML in a string, that process throws away everything (such as event bindings) that isn't simple HTML.
Either give .html() the jQuery object itself:
$('#id_date', elements).datepicker();
that.$el.html(elements);
or bind the datepicker after adding the HTML:
that.$el.html(elements);
that.$('#id_date').datepicker();

Creating a jQuery UI sortable in an iframe

On a page I have an iframe. In this iframe is a collection of items that I need to be sortable. All of the Javascript is being run on the parent page. I can access the list in the iframe document and create the sortable by using context:
var ifrDoc = $( '#iframe' ).contents();
$( '.sortable', ifrDoc ).sortable( { cursor: 'move' } );
However, when trying to actually sort the items, I'm getting some aberrant behavior. As soon as an item is clicked on, the target of the script changes to the outer document. If you move the mouse off of the iframe, you can move the item around and drop it back by clicking, but you can not interact with it within the iframe.
Example: http://robertadamray.com/sortable-test.html
So, is there a way to achieve what I want to do - preferably without having to go hacking around in jQuery UI code?
Dynamically add jQuery and jQuery UI to the iframe (demo):
$('iframe')
.load(function() {
var win = this.contentWindow,
doc = win.document,
body = doc.body,
jQueryLoaded = false,
jQuery;
function loadJQueryUI() {
body.removeChild(jQuery);
jQuery = null;
win.jQuery.ajax({
url: 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js',
dataType: 'script',
cache: true,
success: function () {
win.jQuery('.sortable').sortable({ cursor: 'move' });
}
});
}
jQuery = doc.createElement('script');
// based on https://gist.github.com/getify/603980
jQuery.onload = jQuery.onreadystatechange = function () {
if ((jQuery.readyState && jQuery.readyState !== 'complete' && jQuery.readyState !== 'loaded') || jQueryLoaded) {
return false;
}
jQuery.onload = jQuery.onreadystatechange = null;
jQueryLoaded = true;
loadJQueryUI();
};
jQuery.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js';
body.appendChild(jQuery);
})
.prop('src', 'iframe-test.html');
​
Update: Andrew Ingram is correct that jQuery UI holds and uses references to window and document for the page to which jQuery UI was loaded. By loading jQuery / jQuery UI into the iframe, it has the correct references (for the iframe, rather than the outer document) and works as expected.
Update 2: The original code snippet had a subtle issue: the execution order of dynamic script tags isn't guaranteed. I've updated it so that jQuery UI is loaded after jQuery is ready.
I also incorporated getify's code to load LABjs dynamically, so that no polling is necessary.
Having played with their javascript a bit, Campaign Monitor solves this by basically having a custom version of jQuery UI. They've modified ui.mouse and ui.sortable to replace references to document and window with code that gets the document and window for the element in question. document becomes this.element[0].ownerDocument
and they have a custom jQuery function called window() which lets them replace window with this.element.window() or similar.
I don't know why your code isn't working. Looks like it should be.
That said, here are two alternative ways to implement this feature:
If you can modify the iframe
Move your JavaScript from the parent document into iframe-test.html. This may be the cleanest way because it couples the JavaScript with the elements its actually executing on.
http://dl.dropbox.com/u/3287783/snippets/rarayiframe/sortable-test.html
If you only control the parent document
Use the jQuery .load() method to fetch the content instead of an HTML iframe.
http://dl.dropbox.com/u/3287783/snippets/rarayiframe2/sortable-test.html
Instead of loading jQuery and jQueryUI inside the iFrame and evaluating jQueryUI interactions both in parent and child - you can simply bubble the mouse events to the parent's document:
var ifrDoc = $( '#iframe' ).contents();
$('.sortable', ifrDoc).on('mousemove mouseup', function (event) {
$(parent.document).trigger(event);
});
This way you can evaluate all your Javascript on the parent's document context.

jQueryMobile - conditionally allow hash if "page" exists?

I saw a lot of related questions, but none that directly have this issue...
I have a very large site for which I am building a jquery mobile theme. I don't have much control over the content though, and when it was built for desktop, a lot of #anchor tags were used to move to positions on the page.
I have fixed this for links on the same page with the following (jqm is a stand-in for the jquery object, I need to clean up the $/jqm references...
This is bound into the mobileinit event, and works if there are anchors on the page:
jqm('div').live('pagebeforecreate', function(e, data)
{
if(location.hash.length>0){
if($(location.hash).length>0){
if ($(location.hash).attr("data-role") != "page")
{
$.scrollTo(location.hash, 800);
};
}
}
//Check to see if there are any internal links on page
$("a[href*='#']").each(function()
{
//make sure they aren't legit jqm pages
if($(this.hash).length){
if ($(this.hash).attr("data-role") != "page")
{
//Disable jqm behavior, instead scroll down the page
$(this).click(function(event)
{
event.preventDefault();
$.scrollTo(this.hash, 800);
return false;
});
};
}
else {
$(this).attr('data-ajax','false');
}
});
});
});
So, if a user is on foo.php, and there's a link to #bar and #baz, it will nicely scroll to them, knowing that they aren't data-role="page"
But if the user is on foo.php and there's a link on that page to qux.php#bar it chokes because when the page loads, it is trying to to a changePage to #bar, but #bar on qux.php is really just an id for a regular old div.
It seems to me that something like the on-page solution above would work for this as well, but maybe I would need to bind to the actual page load instead of the pagebeforecreate?

Resources