Meteor only trigger template after selecting item from list - jquery-mobile

I have a typical scenario where there is a list view and then there's a details view. You get to the details view by selecting a list item. There's data in the record that of course will inform the layout view of the details. What I see is that the subtemplate's helper function is being called too soon (during the list view rendering) to have the data for the list details . Furthermore, it's not being called when I click an item in the list. What am I doing wrong? I'm using Meteor 0.8.2 with jQM 1.4.3.
The HTML looks as follows:
<!-- details page -->
<div data-role="page" data-theme="q" id="listdetail">
<div data-role="header" data-position="fixed">
<a data-rel="back" href="#" data-transition="slideleft" class="baack fa fa-arrow-left"></a>
<div id="detailTitle" class="headertitle"></div>
</div>
<!-- /header -->
<div data-role="content" class="ma-req-detail" id="details">
{{> qDetails}}
</div>
</div>
<!-- /details page -->
<template name="qList">
{{#each items}}
{{>qListItems}}
{{/each}}
</template>
<template name="qListItems">
<li>
<div id="msg-container-{{requestId}}" class="processing-msg">
<i class="fa fa-2x fa-times-circle"></i>
<p class="msg-text-{{requestId}}">Action pending...</p>
</div>
<a id="requestId" href="#listdetail" data-name="{{additionalProperties}}" data-transition="slide" class="{{#if isProcessing}}ui-disabled{{/if}}">
<p class="requestor">{{additionalProperties.requestedForUid}}</p>
<p class="description">week of {{additionalProperties.workWeekOf}}</p>
<p class="reqdate">total hours: </p>
</a>
</li>
</template>
<template name="qDetails" >
<div role="main" class="q-details">
<div data-role="navbar" class="week-nav">
<ul>
{{#each days}}
{{>navElements}}
{{/each}}
</ul>
</div>
</div>
<div class="ma-buttons ui-fieldcontain ui-footer-fixed">
<div data-role="controlgroup" data-type="horizontal" class="" data-position-fixed="true" data-position="bottom">
<a data-mini="true" href="#" id="approve-request"
class="ui-btn ui-btn-c ui-corner-r ui-icon-check ui-btn-icon-left ma-btn approve">Approve</a>
</div>
</div>
</template>
<template name="navElements">
<li><a id="{{day}}Nav" href="#{{day}}">{{day}}<br><span class="digital-date">{{date}}</span></a></li>
</template>
The JS bits are:
Template.qDetails.rendered = function() {
$('#details').trigger('create');
};
Template.qDetails.helpers({
days: function() {
//TODO need a way to delay this function to call when details template is shown
var dt = Session.get("record").additionalProperties.workWeekOf;
var days = [];
var weekday = new Array(7);
weekday[0] = "SAT";
weekday[1] = "SUN";
weekday[2] = "MON";
weekday[3] = "TUE";
weekday[4] = "WED";
weekday[5] = "THU";
weekday[6] = "FRI";
var dtVal = dt.getDate();
for (var i = 0; i < 7; i++) {
var dayObj = {};
dayObj.date = dtVal + i;
dayObj.day = weekday[i];
days.push(dayObj);
}
return days;
}
});
Template.qDetails.record = function () {
return Session.get("record");
};
In the code above the "days" helper function is being called when the list page is shown which results in an error because it is trying to pull the "workWeekOf" date from a record that hasn't been selected yet. How can I get to this only call once a record has been selected?

This is slightly confusing, since there's nothing in the above that shows how yourqDetails template gets rendered in the first place. However, assuming it does, you can just use:
var dt = Session.get("record") ? Session.get("record").additionalProperties.workWeekOf : [default date]
[default date] could be anything sensible (like new Date()), but you need to return something to avoid the error being thrown. This is a pretty common but very easily solved problem in Meteor - you just need a suitable default for when your Session variable or Collection isn't yet ready.

Related

Delay knockout binding evaluation

Knockout newbie here. I have a page to display the customer info.
1st div should be displayed when customer info is present.
2nd div should be displayed when no customers are displayed
//1st Div
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
//2nd Div
<div id="empty" data-role="content"
data-bind="visible: customers().length === 0 ">
<p>No Customer Information</p>
</div>
My model is like this:
var myModel = {
customers : ko.observableArray();
}
..and on page load I am adding this logic:
//On Page Load, call AJAX to populate the customers
//customers = {jsonData}
My page is using jQuery Mobile. My only problem is when the page is first displayed, the second div is displayed. When the Ajax json data returns, that's where it is hidden.
Is it possible to hide the second div while the ajax is still on loading and data has not yet returned?
UPDATE 2
On a related note, I tried the KO HTML template which I just read from the net
<!-- ko if: customers().length -->
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
<!-- /ko -->
<div id="empty" data-role="content"
data-bind="if: customers().length === 0">
<p>No Customer Information</p>
</div>
but still unsuccessful. Any thoughts what is missing?
UPDATE 3
I tried updating what #shriek demonstrated in his fiddle http://jsfiddle.net/t0wgLt79/17/
<!-- ko if: customers() -->
<div id="custInfoList" data-role="content" data-bind="foreach: customers">
<p data-bind="text: $data"></p>
</div>
<!-- /ko -->
<div id="empty" data-role="content" data-bind="if: customers().length === 0" style="display: none;">
<p>No Customer Information</p>
</div>
<button data-bind="click:popCustomers">Populate</button>
My JS:
$.support.cors = true;
var test = {
customers: ko.observableArray(),
popCustomers: function () {
for (var i = 0; i < 3; i++) {
this.customers.push(i);
}
},
popByAjax: function () {
console.log("Calling JSON...");
$.getJSON("http://api.openweathermap.org/data/2.5/weather?id=2172797", function (data) {
console.log(data);
if (data.sys) {
this.customers.push(data.sys.country);
console.log("Loaded");
}
}.bind(this));
}
};
test.popByAjax();
ko.applyBindings(Object.create(test));
On initial load, the "AU" is displayed. Now change the weather?id=2172797 into weather?id=21727971 to make it invalid. I notice that the no customer information is not displayed.
As mentioned in the comment above, for Update 3 display:none is extraneous as it's already being taken care by if on the data-bind.
Second thing is the observableArray had to be emptied after receiving bad response because hiding/displaying is based on the comparison of that observableArray's length.
Code to fiddle with:-
http://jsfiddle.net/4hmqdsup/
you see the second div as well as the first div because the knockout applyBinding to your DOM elements, has not yet been occurred, which means the visible binding has not yet been evaluated, and therefore no element will be hidden accordingly, leaving it in its default state ( which is to be shown )
to overcome this behaviour, you only need to add a style="display: none;" to those elements you want them to be hidden by default, and then the visible binding will remove the display: none if it is evaluated to true.
so your code should be like this
//1st Div
<div id="custInfoList" data-role="content"
data-bind="foreach: customers, visible : customers().length > 0">
<p data-bind="text: $data.info"></p>
</div>
//2nd Div
<div id="empty" data-role="content"
data-bind="visible: customers().length === 0" style="display: none;">
<p>No Customer Information</p>
</div>
and btw, it does not matter whether you use visible or if binding, as the problem is not with the binding itself.
I guess you did wrongly in //customers = {jsonData}.
To update a ko.observable, you need to use customers(jsonData), not customers = jsonData.
ko.observable() returns a function, the setter is customers(newValue) and the getter is customers(), you need to use function call explicitly in both setter and getter.

jQuery Mobile AJAX navigation to a page having a dialog

JQM AJAX navigation doesn't work well when navigating to a page with an in-page dialog?
page1.aspx:
<div data-role="page">
<div data-role="header">Page 1</div>
<div data-role="content">
<a href='page2.aspx'>Click here</a>
</div>
</div>​
page2.aspx:
<div data-role="page">
<div data-role="header">Page 2</div>
<div data-role="content">
Popup
</div>
</div>​
<div data-role="dialog" id='popup'> <!-- This is not loaded on AJAX navigation -->
<div data-role="header">Page 2 Popup</div>
<div data-role="content">
Popup content
</div>
</div>​
When you click the link from Page 1, it doesn't load the <div data-role='dialog'> of Page 2.
How do you get around this issue?
There is a workaround here. I modified it a little:
$(document).bind('pagecontainerload', function (event, ui) {
// Find all dialogs in the DOM
var response = ui.xhr.responseText;
var data = $(response).find("div[data-role='dialog'],div[data-role='popup']");
for (var i = 0; i < data.length; i++) {
var node = data.eq(i);
if (node.attr('id') && document.getElementById(node.attr('id')))
$('#' + node.attr('id')).remove(); // Delete existing one
node.addClass('cache').appendTo('#form1'); // or appendTo('body')
}
});
I added the class cache for cleanup purpose.

Nested ListView In Jquery Mobile with mvc

I create nested list view in Asp.net mvc with jQuery mobile, it has 2 sub view, when i navigate to first subview1 i can return back to main view, but when i navigate from subview1 to subview2 i can not back to subview1, since subview1 is somehow null.
in my layout page i put this script
<script>
$(document).on('mobileinit', function () {
$.mobile.page.prototype.options.addBackBtn = true;
$.mobile.page.prototype.options.backBtnText = "Back";
$.mobile.page.prototype.options.backBtnTheme = "a";
$.mobile.defaultPageTransition = "slide";
});
</script>
the main page is :
<script>
$(document).on('click', '[data-rel=back]', function (e) {
e.preventDefault();
var activepage = $.mobile.activePage.attr("id");
if (activepage == "schedule")
$.mobile.changePage($('#flight'), { transition: "slide", reverse: false });
if (activepage == "dayFlight") {
$.mobile.changePage($('#schedule'), { transition: "slide", reverse: false });
}
});
<div data-role="page" id="flight" data-add-back-btn="true">
<div data-role="content">
<ul id="flightListView" data-role="listview" data-inset="false" data-filter="true">
#foreach (var item in Model)
{
<li>
<a href="#Url.Action("Schedules", new { from = item.DepartureFrom, to = item.Destination })" data-direction="reverse" data-transition="slide">
<span class="ui-li-count ui-btn-up-c ui-btn-corner-all">#item.NumberOfFlights</span>
</a>
</li>
}
</ul>
</div>
the subview1 is:
<div data-role="page" data-add-back-btn="true" id="schedule">
<div data-role="content">
<ul id="scheduleListView" data-role="listview" data-inset="false" data-filter="false">
#foreach (var item in Model)
{
<li>
<a href="#Url.Action("DayFlights", new { from = from, to = to, date = item.Date })" data-direction="reverse" data-transition="slide">
<span class="lg">#Html.DisplayFor(modelItem => item.Day)</span>
<span class="lg number laligned">#Html.DisplayFor(modelItem => item.Date)</span>
<span class="ui-li-count ui-btn-up-c ui-btn-corner-all">#item.Nof</span>
</a>
</li>
}
</ul>
</div>
the subview2 is :
<div data-role="page" data-add-back-btn="true" id="dayFlight">
<div data-role="content">
<ul id="dayListView" data-role="listview" data-inset="true" data-filter="false">
#foreach (var item in Model)
{
<li>
<a href="#item.AgencyUrl" rel="external" data-ajax="false">
<span class="ui-li-count ui-btn-up-c ui-btn-corner-all">#item.Cap</span>
</a>
</li>
}
</ul>
</div>
something that i figure out, it is when i navigate to subview1, the mainview and the subview1 both of them are in DOM but then when i navigate to subview2, subview1 will replaced by subview2, so it means when i am at subview2 the subview1 does not exist in DOM.
in all cases $.mobile.activePage.prev('.ui-page'); return null, that is the reason why i decided to use if clause to determine to which page i should navigate back to, however when i want to navigate back from subview2 to subview1 it does not work.
how can i implement this senario?
thanks in advance

jquerymobile - navigation in multi-page template

I have a multi-page template set up. The first page has a set of links to the inner pages - however all but one link to the same #template page which loads data dynamically. The other link is to a form which I have built within the page.
Problem I'm having is when you navigate to one of the links, continue through the pages and then use the back button to get back to the first page, if you then click the link to the form it doesn't navigate to the #page, it goes to the last page you were on in the other links.
Hope that makes sense - maybe a diagram will make it clearer
home > product list > product detail
product detail > product list > home (back button)
home > contact form
This last step shows the product detail page again, instead of the contact form. It's as if it's not clearing the page out:
My code (truncated for clarity):
<script type="text/javascript">
$(document).bind("mobileinit", function () {
$.mobile.allowCrossDomainPages = true;
$.support.cors = true;
});
$(document).delegate("#pageDetail", "pagecreate", function () {
$(this).css('background', '#ECF2FE');//`this` refers to `#pageDetail`
});
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
$.mobile.pageData = (data && data.options && data.options.pageData) ? data.options.pageData : null;
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var page;
var type;
var cat;
if ($.mobile.pageData && $.mobile.pageData.type) {
type = $.mobile.pageData.type;
}
if ($.mobile.pageData && $.mobile.pageData.cat) {
cat = $.mobile.pageData.cat;
}
if ($.mobile.pageData && $.mobile.pageData.page) {
page = $.mobile.pageData.page;
}
var url = $.url(data.toPage); // page url
var hash = url.fsegment(1).replace(/&.*/, ""); // nav hash for page holder
switch (hash) {
case "pageList":
$.ajax({
url: "http://localhost/myapp/" + type + ".aspx?type=" + type,
datatype: "html",
success: function (data) {
$('.submenu').html(data);
$('.title').html(type);
$('.submenu').trigger('create');
}
});
break;
case "pageDetail":
$('.detail').load('http://localhost/myapp/' + type + '.aspx?page=' + page + '&type=' + type + ' #contentdiv', function () {
$(this).trigger('create');
});
break;
default:
break;
}
}
});
</script>
</head>
HTML bits
<body>
<div data-role="page" id="home">
<div data-role="content">
<img src="images/Icon-Courses.png" alt="courses" /><br />
<img src="images/Icon-Contact.png" alt="contact" />
</div>
</div>
<div data-role="page" data-theme="a" id="pageList" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="pageDetail" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="contactForm" data-add-back-btn="true" class="detailpage" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Contact Us</h1>
</div>
<div data-role="content" class="detail">
<div id="contentdiv" style="margin:10px auto;width:90%;">
<label for="name">Your Name</label>
<input type="text" id="name" style="width:90%;" data-mini="true" />
<label for="email">Email Address</label>
<input type="text" id="email" style="width:90%;" data-mini="true" />
<label for="phone">Phone Number</label>
<input type="text" id="phone" style="width:90%;" data-mini="true" />
<label for="comments">Enquiry / Comments</label>
<textarea id="comments" rows="80" style="width:90%;" data-mini="true"></textarea>
<br />
<input type="submit" id="submit" value="Send Enquiry Now" />
</div>
</div>
</div>
</body>
</html>
I'm using the jqm.page.params.js for handling the querystring parameters.
Is there anything obvious there? The links to the #pageDetail page are returned within the content shown on #pageList.
Don't understand why I can't seem to clear the cache and it doesn't navigate correctly to the contact form if you've been anywhere else first. if you go straight there it works fine.
Anyone shed any light? BTW this needs to work using PhoneGap as a standalone
Thanks
Actually feel a bit dim about this, but it's because I had the same class names on multiple divs and it was using those to import data - so when it was loading data remotely it was putting it in more than one place, overwriting the form at the same time...

content of dynamically-added jQuery-ui tabs only showing once

I'm trying to create a tab set using jQuery UI that has some permanent tabs as well as some special purpose tabs. The special tabs are added temporarily: when the form they contain is submitted, the tabs are removed.
I've got this working except for one thing: after a tab is removed, if it is re-added later its content isn't shown, and I can't figure out why. I've distilled it down to this jsFiddle example, code also reposted below.
HTML:
<div id="tabs">
<ul>
<li>Foo</li>
</ul>
<div id="foo">
<h2>Foo Tab</h2>
</div>
<div id="bar" class="transient" style="display: none">
<h2><button type="button" class="close" style="float: right"><span class="ui-icon ui-icon-closethick">close</span></button>Bar Tab</h2>
</div>
<div id="baz" class="transient" style="display: none">
<h2><button type="button" class="close" style="float: right"><span class="ui-icon ui-icon-closethick">close</span></button>Baz Tab</h2>
</div>
</div>
<hr>
<button onClick="openTransientTab('bar', 'Bar')">Add Bar Tab</button>
<button onClick="openTransientTab('baz', 'Baz')">Add Baz Tab</button>
JavaScript:
$('#tabs').find('div.transient').find(".close").live('click', function() {
var footer_tabs = $('#tabs');
var tab_id = $(this).closest("div.transient").attr("id");
var index = footer_tabs.tabs("option", "selected");
footer_tabs.tabs("select", -1);
footer_tabs.tabs("remove", index);
});
function openTransientTab(id, title) {
var footer_tabs = $("#tabs");
footer_tabs.tabs("select", -1);
footer_tabs.tabs("select", "#" + id);
var selected = footer_tabs.tabs("option", "selected");
if (selected < 0) {
footer_tabs.tabs("add", "#" + id, title);
footer_tabs.tabs("select", "#" + id);
}
$("#" + id).css("display", "block");
}
$(function() {
var footer_tabs = $("#tabs");
footer_tabs.tabs({
collapsible: true,
selected: -1
});
});
When you load the page, the bar and baz tabs are created, but in their style, display is set to none which is why they are not visible originally. Inside the tab, when you hit the X, it actually removes the div for bar and baz completely. When you re-click to add the bar or baz tab after it closes, it recreates the div, but you are not putting anything within it. Add something like the following to once you create the tab.
document.getElementById("bar").innerHtml = whatever you want within it here
Before:
<div id="foo" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide">
<h2>Foo Tab</h2>
</div>
<div id="baz" class="transient" style="display: none">
<h2>
<button class="close" style="float: right" type="button">
<span class="ui-icon ui-icon-closethick">close</span>
</button>
Baz Tab
</h2>
</div>
<div id="bar" class="transient ui-tabs-panel ui-widget-content ui-corner-bottom" style="display: block;">
<h2>
<button class="close" style="float: right" type="button">
<span class="ui-icon ui-icon-closethick">close</span>
</button>
Bar Tab
</h2>
</div>
After opening and closing both
<div id="foo" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide">
<h2>Foo Tab</h2>
</div>
<div id="bar" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" style="display: block;"></div>
<div id="baz" class="ui-tabs-panel ui-widget-content ui-corner-bottom" style="display: block;"></div>
After Collecter provided the key insight about what wasn't working, I found a nicer way to preserve the tab content for reuse. I changed my close function to the following:
$('#tabs').find('div.transient').find(".close").live('click', function() {
var footer_tabs = $('#tabs');
var tab = $(this).closest("div.transient");
var index = footer_tabs.tabs("option", "selected");
footer_tabs.tabs("select", -1);
footer_tabs.tabs("remove", index);
footer_tabs.append(tab);
});

Resources