Jquery mobile not rendering after injecting precompiled handlebars - jquery-mobile

The Environment
Building an app in PhoneGap and using Handlebars for the templating. Jquery-mobile is in the mix to provide widgets and some out of the box themes. The templates are packed with gulp into a build file and rendered onto the page through separate JS files.
The Route
The file structure
/-
--|templates/
----|login.hbs/
--|www/
----|js/
------|index.js
------|render/
--------|LoginView.js
The template before packing, login.hbs:
<div class="app" data-role="page">
<div data-role="header">
<h1>Index Page</h1>
</div>
<div data-role="main" class="ui-content">
</div>
<div data-role="footer">
<h1>Title on Page</h1>
<button id="login-btn" class="ui-btn ui-icon-plus ui-btn-icon-left">Login Page</button>
</div>
</div>
The file that handles the rendering, LoginView.js
var LoginView = function() {
this.initialize = function() {
this.el = $('<div data-role="page"/>');
this.el.on('click', '#index-btn', this.renderIndex);
};
this.render = function() {
this.el.html(LoginView.template());
return this;
};
this.renderIndex = function() {
console.log('button click');
$('body').html(new IndexView().render().el);
}
this.initialize();
}
LoginView.template = App.templates.login;
The place where this file is called index.js
var app = {
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {
app.receivedEvent();
},
receivedEvent: function() {
console.log('Received Event!');
$('body').html(new LoginView().render().el);
$('body').trigger('create');
},
initialize: function() {
var self = this;
this.bindEvents();
}
};
The Question
Is very much like this one about Jquery not loading on handlebars inject. But maybe I'm not fully understanding where this method needs to be called. Currently the Jquery works when the rendering is disabled and simple html is present on the page... Once the rendering is wired up again: Blank page.
I've tried several different places so far:
In the this.render part of LoginView
In the place where it is now
tried both 'create' and 'pagecreate'
used the id of the rendered page instead of body
I've also moved the <div data-role="page"> around a bit.

Related

Knockout not disposing of dialog when removing template

Solution Here: http://jsfiddle.net/lookitstony/24hups0e/6/
Crimson's comment lead me to a solution.
I am having an issue with KO and the Jquery UI dialog. The dialogs are not being destroyed with the template that loaded them.
I was previously storing an instance of the dialog and reusing it over and over without using the binding handler. After reading a few posts about the included binding handler I felt perhaps that was the best way to handle the dialogs. Am I using knockout wrong? Should I stick with the stored reference or does KO have a better way to handle this? If this was an SPA, how would I manage this if I was swapping between pages that may or may not have these dialogs?
You can witness this behaviour by checking out my example here: http://jsfiddle.net/lookitstony/24hups0e/2/
JAVASCRIPT
(function () {
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
setTimeout(function () {
options.close = function () {
allBindingsAccessor().dialogVisible(false);
};
$(element).dialog(options);
}, 0);
//handle disposal (not strictly necessary in this scenario)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).dialog("destroy");
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
var shouldBeOpen = ko.utils.unwrapObservable(allBindingsAccessor().dialogVisible),
$el = $(element),
dialog = $el.data("uiDialog") || $el.data("dialog");
//don't call open/close before initilization
if (dialog) {
$el.dialog(shouldBeOpen ? "open" : "close");
}
}
}
})();
$(function () {
var vm = {
open: ko.observable(false),
content: ko.observable('Nothing to see here...'),
templateOne: ko.observable(true),
templateTwo: ko.observable(false),
templateOneHasDialog: ko.observable(true),
showOne: function(){
this.templateTwo(false);
this.templateOne(true);
},
showTwo: function(){
this.templateOne(false);
this.templateTwo(true);
},
diagOpt: {
autoOpen: false,
position: "center",
modal: true,
draggable: true,
width: 'auto'
},
openDialog: function () {
if(this.templateOneHasDialog()){
this.content('Dialog opened!');
this.open(open);
} else {
this.content('No Dialog Available');
}
}
}
ko.applyBindings(vm);
});
HTML
<div id='ContentContainer'>
Experience Multiple Dialogs
<ul>
<li>Click "Open Dialog"</li>
<li>Move the dialog out of the center and notice only 1 dialog</li>
<li>Close Dialog</li>
<li>Now click "One" and "Two" buttons back and forth a few times</li>
<li>Now click "Open Dialog"</li>
<li>Move the dialog and observe the multiple dialogs</li>
</ul>
<button data-bind="click:showOne">One</button>
<button data-bind="click:showTwo">Two</button>
<!-- ko if: templateOne -->
<div data-bind="template:{name:'template-one'}"></div>
<!-- /ko -->
<!-- ko if: templateTwo -->
<div data-bind="template:{name:'template-two'}"></div>
<!-- /ko -->
</div>
<script type="text/html" id="template-one">
<h3>Template #1</h3>
<p data-bind="text:content"></p>
<div><input type= "checkbox" data-bind="checked:templateOneHasDialog" /> Has Dialog </div>
<button data-bind="click:openDialog">Open Dialog</button>
<!-- ko if: templateOneHasDialog -->
<div style="display:none" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
<!-- /ko -->
</script>
<script type="text/html" id="template-two">
Template #2
</script>
When using dialog inside template the init method will be called every time when the template is shown and hence multiple dialogs are appeared in your case. To resolve this place the dialog outside the template.
<div style="display:none" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
Place this outside the template and now the issue will be resolved.
Updated fiddle: Fiddle
Edit: I went through your code and found that the ko.utils.domNodeDisposal.addDisposeCallback has not been triggered in your case. And hence the dialog has not been destroyed on template change which in returns shows multiple dialog.
But why ko.utils.domNodeDisposal.addDisposeCallback has not called?
The ko.utils.domNodeDisposal.addDisposeCallback will be triggered when the element(rendered using custom binding) in the template is removed from DOM. But in your case, the dialog element is appended to the body instead of template and so it was not triggered
Solution
The jquery ui 1.10.0+ have option to specify where the dialog element has to be appended using appendTo option we can use that to resolve this.
diagOpt: {
autoOpen: false,
position: "center",
modal: true,
draggable: true,
width: 'auto',
appendTo: "#DesiredDivID"
},
<script type="text/html" id="template-one">
<h3>Template #1</h3>
<p data-bind="text:content"></p>
<div><input type= "checkbox" data-bind="checked:templateOneHasDialog" /> Has Dialog </div>
<button data-bind="click:openDialog">Open Dialog</button>
<!-- ko if: templateOneHasDialog -->
<div id="DesiredDivID"></div>
<div id="dlg" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
<!-- /ko -->
</script>
Now the dialog element will be appended to the #DesiredDivID and destroyed on template change.
See the updated fiddle: Updated one-April-1

LocalStorage Values not updated on Page Show in multipage mobile app using phonegap and jquery(-mobile)

I have a multipage mobile app using phonegap, leaflet and jquery(-mobile) and the following problem:
On page1, when clicking on a map-marker, the name of the poi is written to localstorage and then page2 is called:
var onMarkerClick = function(e) {
akt_poi = e.layer.options.poi;
var globVars = {
"akt_poi": akt_poi,
};
localStorage.setItem('globalVariables', JSON.stringify(globVars));
window.location = '#page2';
};
On page2 i'm doing the following:
<div data-role="content">
<div id=poi></div>
<script type="text/javascript">
var gV = JSON.parse(localStorage.getItem("globalVariables"));
var a_poi = gV.akt_poi;
document.getElementById("poi").innerHTML='<h2>'+a_poi+'</h2>';
</script>
The correct value is shown only at the first call. When doing another click on a map-marker of page1 the old value instead of the locally stored one is shown on page2 until doing a page-refresh.
What can i do display the right value?
I solved the problem by using sessionstorage:
setting on page1:
var onMarkerClick = function(e) {
akt_poi = e.layer.options.poi;
sessionStorage.akt_poi=akt_poi;
window.location = '#page2';
};
reading on page2:
<div data-role="content">
<div id=poi></div>
<script type="text/javascript">
$('#page_audio').live('pageshow', function(event, ui) {
document.getElementById("poi").innerHTML='<h2>'+sessionStorage.akt_poi+'</h2>';
});
</script>

Adding a reload button to an Ajax page with jQuery Mobile

In a jQuery Mobile application, I'm using Ajax to load a page with dynamic content. Now, I want to add a "reload" button to that page.
Consider index.html:
<div data-role="page" id="home">
<div data-role="header"><h1>Home</h1></div>
<div data-role="content">Time</div>
</div>
And time.php:
<div data-role="page" data-add-back-btn="true" id="time">
<div data-role="header"><h1>Time</h1></div>
<div data-role="content">
<p><?= date(DATE_RFC822)?></p>
Reload
</div>
</div>
I can now navigate back and forth between the "home" and "time" pages, and the time will be updated when arriving on the "time" page, but I would like the "Reload" button to reload the "time" page. That is: it should do an Ajax request for time.php, and recreate the "time" page from the response (and meanwhile show the "page loading" indicator).
How can I do that?
After looking at the code of changePage and loadPage I came up with a simpler solution:
$('.reload').live('click', function(e) {
$.mobile.changePage($.mobile.activePage.jqmData('url'), {
reloadPage: true,
changeHash: false,
transition: 'none'
});
e.preventDefault();
});
Note that JQM will still transition between the old version of the page and the new version; it is best not to use a transition that has a stronge sense of direction (like 'slide').
The preventDefault does not seem strictly necessary, but I added it for good measure.
This does not seem to be possible when using the hijax-style links. You can however use a multi-page HTML document and load the dynamic page content yourself in the pagebeforechange event.
So in index.html, I already add the "time" page with empty content:
<div data-role="page" id="home">
<div data-role="header"><h1>Home</h1></div>
<div data-role="content">Time</div>
</div>
<div data-role="page" data-add-back-btn="true" id="time">
<div data-role="header">
<h1>Time</h1>
Reload
</div>
<div data-role="content"></div>
</div>
Next, I intercept the pagebeforechange event: in case of the "time" page, we will load the page content from URL time.php.
$(document).bind('pagebeforechange', function(e, data) {
if(typeof data.toPage == 'string') {
var u = $.mobile.path.parseUrl(data.toPage);
if(u.hash == "#time") {
loadPageContent($('#time'), 'time.php', data.options);
e.preventDefault();
}
}
});
Since I want to wait for the Ajax request to come back before changing the page, I prevent the page change; loadPageContent will have to do this.
I can do a similar loadPageContent for the "Reload" button:
$(document).ready(function() {
$('#time .reload').bind('click', function(e) {
loadPageContent($('#time'), 'time.php', undefined, $(e.target).closest(".ui-btn"));
});
});
This is the code of loadPageContent:
function loadPageContent(page, url, options, button) {
if(typeof button != "undefined")
button.addClass($.mobile.activeBtnClass);
$.mobile.showPageLoadingMsg($.mobile.loadingMessageTheme, $.mobile.loadingMessage, $.mobile.loadingMessageTextVisible);
var content = page.children(':jqmData(role=content)');
content.load(url, function(response, status, xhr) {
if(status == "success") {
page.page();
content.trigger('create');
$.mobile.hidePageLoadingMsg();
if(typeof button != "undefined")
button.removeClass($.mobile.activeBtnClass);
$.mobile.changePage(page, options);
} else {
$.mobile.hidePageLoadingMsg();
$.mobile.showPageLoadingMsg($.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true);
setTimeout(function() {
$.mobile.hidePageLoadingMsg();
if(typeof button != "undefined")
button.removeClass($.mobile.activeBtnClass);
}, 1000);
}
});
}
This is already quite an elaborate version: it will display the page loading message, and it will display an error message for 1s when the request fails. At the end of a successful request, it will enhance the content with the create trigger, and it will do the page change. If the reload button is passed along, it will mark this button as active while the operation is in progress.

submit form after clicking selected tab and page also stay on that tab

I would like to do the following:
1. tab-1 is selected when page load at first time
2. After clicking tab-2, form is submitted and page need to stay on the tab-2
I have tested two code snippets. However, both of them have errors (see at below):
<form id="target">
<ul>
<li>Tab-1</li>
<li>Tab-2</li>
<li>Tab-3</li>
</ul>
<div id="tabs-1">
</div>
<div id="tabs-2">
</div>
<div id="tabs-3">
</div>
</form>
code 1-
It works with point2 and doesn't work with point 1
<script>
$(function() {
var $tabs = $('#tabs').tabs();
$tabs.tabs( "option", "selected", 1 );
$('#tabs-B').click(function() {
$('#target').submit();
});
});
</script>
code 2-
It works with point1, but form doesn't submit after clicking tab-2
var $tabs = $('#tabs').tabs();
$('#tabs-B').click(function() {
$('#target').submit(function() {
$tabs.tabs( "option", "selected", 1 );
});
});
use the [select][1] method
$( "#tabs" ).tabs({
select: function(event, ui) {
var data = $("#from1").serialize();
console.log(data);
// submit the for via ajax here
/*$.post("/path",{data:data},function(){
//clear the form fields or what ever you want to do
});*/
}
});
http://jsfiddle.net/5RMxZ/16/

jquery-ui .tabs ajax load specific content of page?

I'm trying to use jQuery UI's .tabs() to obtain content via AJAX, but the default behavior is to grab the entire page's content. How would I obtain content from a specific #id and/or multiple #id's?
I have a feeling I will need to use the load: event (http://docs.jquery.com/UI/Tabs#event-load), but I need an assist figuring this out.
Example:
The Page with the tabs that is getting and displaying the tabbed content. I have placed #content after the first #the_tabs link to retrieve in an attempt to obtain that specific region of the content, but the entire page is still loaded.
<div id="tabs">
<div id="tabs_display">
</div>
<ul id="the_tabs">
<li><span>1</span></li>
<li><span>2</span></li>
<li><span>3</span></li>
<li><span>4</span></li>
</ul>
</div><!-- /#tabs -->
The page being retrieved by the previous markup:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Remote HTML Page Example</title>
</head>
<body>
<div id="content">
I want this content
</div>
<div id="other_stuff">
Not this content
</div>
</body>
</html>
the JS (for setup purposes):
$(document).ready(function(){
/* Tabs
--------------------*/
$(function() {
var $tabs = $('#tabs').tabs({
});
});
});
In Jquery-UI 1.9, "ajaxOptions" is depreciated; so instead the code below worked for me:
(ref: http://jqueryui.com/upgrade-guide/1.9/#deprecated-ajaxoptions-and-cache-options-added-beforeload-event)
$(function() {
$( "#the_tabs" ).tabs({
beforeLoad: function( event, ui ) {
ui.ajaxSettings.dataType = 'html';
ui.ajaxSettings.dataFilter = function(data) {
return $(data).filter("#content").html();
};
}
});
});
$(document).ready(function(){
/* Tabs
--------------------*/
var $tabs = $('#the_tabs').tabs({
ajaxOptions: {
dataFilter: function(data, type){
return $(data).filter("#content").html();
}
}
});
});
Solution props to Supavisah in #jquery on irc.freenode.net
I have had luck using .find, rather than .filter. Like this:
$(document).ready(function(){
$('#the_tabs').tabs({
ajaxOptions: {
cache : true,
dataFilter: function(data){
return $(data).find('#content');
},
}
});
});

Resources