jQueryMobile - conditionally allow hash if "page" exists? - jquery-mobile

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?

Related

jQuery Mobile displays all Tab containers on linked page

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();
}
}
});

Using HTML in a Dialog's title in jQuery UI 1.10

http://jqueryui.com/upgrade-guide/1.10/#changed-title-option-from-html-to-text
jQuery UI 1.10 made it so that the dialog title can only be text (no html) to prevent scripting vulnerabilities. I'm not allowing user input to generate this title, so I would still like to use HTML, mainly to display an icon to the left of the title.
I'm going to post my solution to this problem because I haven't seen anyone else ask or answer this yet. Hopefully it will help someone else, or someone else may have a better approach.
More info as to why they did it: http://bugs.jqueryui.com/ticket/6016
This will override the function used when setting jQuery UI dialog titles, allowing it to contain HTML.
$.widget("ui.dialog", $.extend({}, $.ui.dialog.prototype, {
_title: function(title) {
if (!this.options.title ) {
title.html(" ");
} else {
title.html(this.options.title);
}
}
}));
If you hesitate to override jQuery's _title method, you can use the html, append, or similar methods on the title element at the jQuery dialog's open event, like so:
$("#element").dialog({
open: function() {
$(this).find("span.ui-dialog-title").append("<span class='title'>" + subtitle + "</span>");
}
});
The above parses the HTML correctly while bypassing jQuery's title method. And since it happens at the open event, the user experience remains seamless. Just did this on a project, and it worked beautifully.
This will modify the title after init the dialog
$('#element').dialog(options);
var dialogTitle = $('#element').closest('.ui-dialog').find('.ui-dialog-title');
dialogTitle.html('<strong>hello world</strong>');

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();

Rails 3, bootstrap modal multiple layers of remote calling

I've been racking my head against this for 2 days now. I'm massively frustrated, and I can't seem to find any information on this with searching.
The issue. I'm using a :remote => true link to load some html from a different controller.
$('.managed_locations').bind('ajax:complete', function(evt, xhr, status){
$('#locations_modal').modal('show')
$('#locations_modal').html(xhr.responseText);
});
So it gets the html, dumps it into the bootstrap modal and displays the modal. This is working fine.
But inside of the modal I ALSO have a form which also uses :remote => true. Now to make life harder, when a button is pressed I clone the form and display it. So the user could have many forms.
Now the issue. Whenever the form is submitted it just loads it like a normal page. It's as if the :remote => true is being ignored. But this only in the modal. If I just load the modal controller by itself it works just fine. I also had this developed before using another jquery lightbox where it was working fine. I'm just switching in bootstrap for consistency.
So my initial thoughts are that the jquery_ujs.js isn't finding the new forms. So I added some code to output the form elements.
$("#log_events").click(function () {
$(document).find(".new_stored_physical_location").each(function() {
console.log( $(this).data() );
console.log( $(this).data('events') );
});
return false;
});
Which outputs in the console:
Object { type="html", remote=true}
Object { ajax:complete=[1]}
So I see that the events are being set in jQuery. Each of these forms has :remote => true and has the ajax event for when the request is complete. But it's just not doing an ajax request when I hit submit.
Is there something I'm missing that is required to make sure an ajax request will happen from the form???? The data() looks fine, the data('events') look fine. But is there some other event/binding that I need to look at?
The html that is loaded in from the modal right now is loading a layout. But i've done it both with a layout, without a layout. It's driving me nuts. Thanks for the help guys.
Edit: Some extra weirdness. The modal also loads some additional remote links, all of which are working correctly. It's only the form links which don't seem to work.
I got a solution. The big issue was within jquery_ujs.js Especially this line:
$(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
FYI, rails.formSubmitSelector = 'form'. So this code found all of the forms in the document, overwrote the submit with this function. But the issue was that once you loaded in some ajax, and that ajax contained a it wouldn't add this fancy event to it. You need to re-add it.
So this is what I did.
Inside of jquery_ujs there is a bunch of functions that are accessible outside of it using $.rails. So things like: $.rails.enableElement, $.rails.nonBlankInputs. And the code for the submit event was sitting around all willy nilly. It only executes once when the page is loaded. So I put that in a function addSubmitEvent():
// Add the form submit event
addSubmitEvent: function(element) {
//$(element) was before $(document) but I changed it
$(element).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
var form = $(this),
remote = form.data('remote') !== undefined,
blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
if (!rails.allowAction(form)) return rails.stopEverything(e);
// skip other logic when required values are missing or file upload is present
if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
return rails.stopEverything(e);
}
if (remote) {
if (nonBlankFileInputs) {
return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
}
// If browser does not support submit bubbling, then this live-binding will be called before direct
// bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);
rails.handleRemote(form);
return false;
} else {
// slight timeout so that the submit button gets properly serialized
setTimeout(function(){ rails.disableFormElements(form); }, 13);
}
});
}
This is basically the exact same code. But now it's $(element) instead of $(document). This was changed because now I can sniff for when the modal has loaded in the html. Then I can call:
$.rails.addSubmitEvent('#my_modal');
I then had an issue of it adding the event too many times from when I opened/closed the modal multiple times. So I just put a simple true/false if around it to call it once only.

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.

Resources