WebSharper JQuery Mobile Page Hidden - jquery-mobile

I'm trying to put together a JQuery Mobile site using the F# WebSharper Framework. In WebSharper parlance I have created a Pagelet using JQueryMObile controls which is being served by a Sitelet. Everything compiles and runs, the problem is in the html that is generated.
The page I have declared (simplePage) clearly exists in the markup and is marked with the JQueryMobile css class ui-active to make it visible. However, it is surrounded by a div that is also a page but is not marked with the active css class, making it invisible. Therefore my page inside this div is hidden. I am not creating this containing page div. It seems to be a side-affect of loading the JQueryMobile script in the html head. How can I get rid of it?
I have taken the example from http://websharper.com/samples/JQueryMobile. I am using WebSharper version 2.5.125.62 and WebSharper.JQueryMobile version 2.5.4.198. I have one relevant code file shown below followed by the generated html.
Main:
open IntelliFactory.Html
open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.Html
open IntelliFactory.WebSharper.JQuery
open IntelliFactory.WebSharper.Sitelets
type Action = | Home
[<JavaScript>]
module MyJQueryContent =
let Main() =
JQuery.Mobile.Mobile.Use()
let page = Div [
Id "simplePage"
HTML5.Attr.Data "role" "page"
HTML5.Attr.Data "url" "#simplePage"
] -< [
Div[Text "content"]
]
Div [page]
|>! OnAfterRender (fun _ -> JQuery.Of(page.Body)
|> JQuery.Mobile.JQuery.Page
|> ignore
JQuery.Mobile.Mobile.Instance.ChangePage(JQuery.Of(page.Body)))
[<Sealed>]
type MyJQueryMobileEntryPoint() =
inherit Web.Control()
[<JavaScript>]
override this.Body = MyJQueryContent.Main() :> _
module Pages =
let HomePage =
PageContent <| fun context ->
{ Page.Default with
Title = Some "Index"
Body = [IntelliFactory.Html.Tags.Div[new MyJQueryMobileEntryPoint()]] }
[<Sealed>]
type Website() =
interface IWebsite<Action> with
member this.Sitelet = Sitelet.Content "/" Home Pages.HomePage
member this.Actions = [Home]
type Global() =
inherit System.Web.HttpApplication()
member g.Application_Start(sender: obj, args: System.EventArgs) =
()
[<assembly: Website(typeof<Website>)>]
do ()
Output:

As a side note, you are creating one of the containing DIVs when you write Div [page] instead of page before adding OnAfterRender, but indeed this is not your problem.
As Omar describes, with jQuery Mobile you need to carefully control when and how the initialization of the page structure takes place, especially when working with dynamic pages. I recall seeing your exact problem before but I can't find that conversation in my email box, however, here is an earlier article that has some useful bits for tapping into JQM page initialization:
http://fpish.net/blog/JankoA/id/3362/201367-jquery-mobile-page-reuse-with-websharper

When jQuery Mobile framework is loaded, it checks if there is a page div in DOM. If not, it wraps body's content in page div. To control this behavior, you need to prevent jQM's autoInitializePage in order to initialize manually $.mobile.initializePage() whenever you want.
You need to listen to mobileinit event to override autoInitializePage. The code should be placed after jQuery (core) and before jQuery Mobile libraries in head.
/* jQuery.js */
$(document).on("mobileinit", function () {
$.mobile.autoInitializePage = false;
});
/* jQuery-Mobile.js */
Now you can initialize jQuery Mobile whenever you want by calling $.mobile.initializePage().

I am a developer of WebSharper. Omar is right, this is a JQM thing, another workaround for it is having a dummy page node in the sitelet. Like this:
module Pages =
open IntelliFactory.Html
let HomePage =
PageContent <| fun context ->
{ Page.Default with
Title = Some "Index"
Body =
[
Div [HTML5.Data "role" "page"; Id "dummy"]
Div [new MyJQueryMobileEntryPoint()]
] }

Related

Is it possible to drive Elm app with existing navigation constructed outside of the Elm app?

I'm working with an existing Rails app where the navigation must continue to be constructed on the backend (due to complexity and time limitations). The intended result is to have some of the pages generated with Elm, and some with Rails, using no hashes, and no full page reloads (at least for the Elm pages). The simplified version of the navigation looks like this:
<nav>
<a href="rails-page-1">...
<a href="rails-page-2">...
<a href="elm-page-1">...
<a href="elm-page-2">...
</nav>
<div id="elm-container"></div>
I've experimented with the Elm navigation package, and the elm-route-url, possibly coming close with the latter unless I'm fundamentally misunderstanding the package's capability.
Is there a way to accomplish this? I've gotten it working using hash tags, but no luck without them.
using hash tags
Well you got a chuckle out of me.
I have this guy in my Helpers.elm file that I can use in lieu of Html.Events.click.
{-| Useful for overriding the default `<a>` behavior which
causes a refresh, but can be used anywhere
-}
overrideClick : a -> Attribute a
overrideClick =
Decode.succeed
>> onWithOptions "click"
{ stopPropagation = False
, preventDefault = True
}
So on an a [ overrideClick (NavigateTo "/route"), href "/route" ] [ text "link" ] which would allow middle-clicking the element as well as using push state to update the navigation.
What you're needing is something similar on the JavaScript that works with pushState, and you don't want to ruin the middle-click experience. You can hijack all <a>s,preventDefault on its event to stop the browser from navigating, and push in the new state via the target's href. You can delegate the navigation's <a>s in the event handler. Since the Navigation runtime doesn't support listening to history changes externally (rather it appears to be using an effect manager), you'll have to push the value through a port -- luckily if you're using the Navigation package, you should already have the pieces.
On the Elm end, use UrlParser.parsePath in conjuction with one of the Navigation programs. Create a port to subscribe to using the same message that is used for it's internal url changes.
import Navigation exposing (Location)
port externalPush : (Location -> msg) -> Sub msg
type Msg
= UrlChange Location
| ...
main =
Navigation.program UrlChange
{ ...
, subscriptions : \_ -> externalPush UrlChange
}
After the page load, use this:
const hijackNavClick = (event) => {
// polyfill `matches` if needed
if (event.target.matches("a[href]")) {
// prevent the browser navigation
event.preventDefault()
// push the new url
window.history.pushState({}, "", event.target.href)
// send the new location into the Elm runtime via port
// assuming `app` is the name of `Elm.Main.embed` or
// whatever
app.ports.externalPush.send(window.location)
}
}
// your nav selector here
const nav = document.querySelector("nav")
nav.addEventListener("click", hijackNavClick, false)

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

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.

Scripty2 : how to close dialog

I am looking for a way to close a scripty2 dialog like this :
http://mir.aculo.us/stuff/scripty2-ui/test/functional/controls_dialog.html
From outside of the dialog (i.e. with firebug command line) but my javascript mojo is a bit limited and after 30 min of going around the DOM I cannot find a way. Any hints ?
NB : scripty2 is a rewrite of script.aculo.us which uses bits of Jquery UI.
Scripty2 UI bits are really based on Prototype classes, not extensions to DOM elements, so you can't use $$() to fetch an existing dialog and close it like you might think. It must be stored in a javascript variable.
var dialog = new S2.UI.Dialog({ // The class must be saved in a
variable
content: "Consulting the server. Please wait."
});
dialog.open(); // We open
new Ajax.Request('/answers', {
onComplete: function(){
alert("Done!");
dialog.close(); // And close.
}
});
Try pasting these into Firebug:
var dialog = new S2.UI.Dialog({content: "Hello World"});
dialog.open();
dialog.close();
This is the documentation for the dialog box :
http://scripty2.com/doc/scripty2%20ui/s2/ui/dialog.html
To close all the dialogs (elements with class div.ui-dialog) on the page with no ids code would be something like this (untested):
$$('div.ui-dialog').each(function() {this.close();});

Resources