Knockout is dynamically adding a select menu to a jQuery Mobile page. When it appears it has some select menu styling even though it hasn't been initialized as one. This causes a problem when I do initialize it because then it is wrapped in an extra ui-select. What is causing this and how can I fix it?
Here is an example. Check 'show options' to display the select. Then click one of the buttons to see the problem.
http://jsfiddle.net/5udqV/1/
Looking at your fiddle, the select is not dynamic, only the options within the select are. So one thing you could do is in the markup add data-role="none" to the select so that jQM does not touch it during page initialization. Then when you call .selectmenu() it will look right.
Your updated FIDDLE
UPDATE:
Use proper jQM structure and events:
DEMO
<div data-role="page" id="page1">
<div data-role="header">
<h1>My page</h1>
</div>
<div role="main" class="ui-content">show options
<input type="checkbox" data-bind="checked: showOptions" />
<br />
<div data-bind="if: showOptions">
<select data-bind="options: options, value: selectedOption"></select>
</div>
<button id="a">create select</button>
<button id="b">refresh select</button>
<button id="c">create page</button>
<div data-bind="text: ko.toJSON($root)"></div>
</div>
</div>
var vm = {
options: ["A", "B", "C"],
showOptions: ko.observable(),
selectedOption: ko.observable("B")
};
ko.applyBindings(vm);
$(document).on("pagecreate", "#page1", function () {
$('button').on("click", function () {
var id = $(this).prop("id");
if (id == "a") {
$("select").selectmenu();
} else if (id == "b") {
$("select").selectmenu("referesh");
} else if (id == "c") {
$(".ui-page").trigger("create");
}
});
});
Related
I'm trying to dynamically create and filter a jquery mobile control group containing checkboxes using knockout binding. The basic idea is that the user selects an option which filters the list of checkboxes in the control group. I've seen similar questions on here but they all seem to be a one-time binding where once bound by ko and enhanced by jqm they remain unchanged. I have that behavior working, the issue occurs when the underlying viewModel changes and ko updates the list of checkboxes in the control group. A full demo of the behavior can be found on jsfiddle here: http://jsfiddle.net/hkrauss2/JAvLk/15/
I can see that the issue is due to jqm creating a wrapper div when enhancing the control group. Ko then puts new elements above the wrapper div when updating the DOM. Basically I'm asking if anyone has solved this issue and also if anyone thinks I'm asking for trouble by integrating these two libraries? Thanks to everyone in advance.
Here is the Html:
<div id="home" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
</div>
<div data-role="content">
<ul id="parent-view" data-role="listview" data-inset="true" data-bind="foreach: parentCategories">
<li></li>
</ul>
<p>
To reproduce the issue select Restaurants, come back and select Nightlife or Bars
</p>
</div>
</div>
<div id="list" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
<a data-rel="back" data-icon="carat-l" data-iconpos="notext">Back</a>
</div>
<div data-role="content">
<form>
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</form>
</div>
</div>
And the basic javascript. Note there are two external js files not listed here. One sets $.mobile.autoInitializePage = false; on the mobileinit event. The other brings in data in the form of a JSON array which is used to initialize the Categories property in the AppViewModel.
// Custom binding to handle jqm refresh
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(element).controlgroup("refresh");
} catch (ex) { }
}
}
function GetView(name) {
return $(name).get(0);
}
// Define the AppViewModel
var AppViewModel = function () {
var self = this;
self.currentParentId = ko.observable(0);
self.Categories = ko.observableArray(Categories); // Categories comes from sampledata.js
self.parentCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == 0;
});
});
self.childCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == self.currentParentId();
});
});
self.OnClick = function (viewModel, $event) {
self.currentParentId(viewModel.id);
return true;
};
};
// Create the AppViewModel
var viewModel = new AppViewModel();
// Apply bindings and initialize jqm
$(function () {
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
Update
My old solution wraps each element in a ui-controlgroup-controls div, which adds unnecessary markup. However, the enhancement part is essential.
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
The new solution is more dynamic to maintain clean markup with no additional wrappers:
First step: Once controlgroup is created controlgroupcreate (event), add data-bind to its' container .controlgroup("container")
Second step: Add checkbox consisted of input and label. At the same time, for each element, add data-bind
Third step: Apply bindings ko.applyBindings().
The static structure of the controlgroup should be basic, it shouldn't contain any elements statically. If a checkbox is added statically, each dynamically created checkbox will be wrapped in an additional .ui-checkbox div.
<div id="child-view" data-role="controlgroup">
<!-- nothing here -->
</div>
JS
$(document).on("controlgroupcreate", "#child-view", function (e) {
$(this)
.controlgroup("container")
.attr("data-bind", "foreach: childCategories, jqmRefreshControlGroup: childCategories")
.append($('<input type="checkbox" name="checkbox" />')
.attr("data-bind", "attr: {id: 'categoryId' + id}"))
.append($('<label />')
.attr("data-bind", "text: description, attr: {for: 'categoryId' + id}"));
ko.applyBindings(viewModel, GetView('#child-view'));
});
Demo
Old solution
As of of jQuery Mobile 1.4, items should be appended to .controlgroup("container") not directly to $("[data-role=controlgroup]").
First, you need to wrap inner elements of controlgroup in div with class ui-controlgroup-controls which acts as controlgroup container.
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<div class="ui-controlgroup-controls">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</div>
Second step, you need to enhance elements inserted into controlgroup container, using .enhanceWithin().
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
Demo
Omar's answer above works very well. As he mentions in the comments however it does wrap each input/label combination in their own div. This doesn't seem to affect anything visually or functionally but there is another way as outlined below. Basically it uses the containerless control flow syntax to bind the list.
New Html
<div id="child-view" data-role="controlgroup">
<!-- ko foreach: childCategories, jqmRefreshControlGroup: childCategories, forElement: '#child-view' -->
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}"></input>
<label data-bind="text: description, attr: {for: 'categoryId' + id}"></label>
<!-- /ko -->
</div>
Using the containerless syntax means that we lose the reference to the controlgroup div in the custom binding handler. To help get that back I added the id as '#child-view' in a custom binding named forElement. The magic still all happens in the custom binding handler and Omar's enhanceWithin suggestion remains the secret ingredient. Note: I needed to change the argument list to include all arguments passed by ko.
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(allBindings.get('forElement')).enhanceWithin().controlgroup("refresh");
} catch (ex) { }
}
}
Final note: To use a custom handler on a virtual element ko needs to be notified that it is ok. The following is the updated start up statements:
// Apply bindings and initialize jqm
$(function () {
ko.virtualElements.allowedBindings.jqmRefreshControlGroup = true; // This line added
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
I have two tabs with a submit button on each tab. When the button is clicked, I need to reload the content of that specific tab to get updated data from the server.
if (validStatus()) {
$.ajax({
//...
success: reloadTab
});
}
function reloadTab() {
var currentTab = $("#tabs").tabs("option", "active");
alert(currentTab);
$('#tabs').tabs('select', currentTab);
alert(currentTab);
}
When the button is clicked, the tab doesn't refresh. I see the first alert but not the second.
HTML is as follows:
Head:
<link rel="stylesheet" href="#this.Url.Content("//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css")" />
<script>
$(function () {
$("#tabs").tabs();
});
</script>
Body:
<div id="tabs">
<ul>
<li>The first tab</li>
<li>the second tab</li>
<li>Success</li>
</ul>
<div id="Success">
testing
</div>
<div id="Tab1">
<fieldset >
<legend>Overview</legend>
<input type="button" id="submit1" value="submit" />
<br />
</fieldset>
<fieldset style="width: 700px;">
<legend>Overview</legend>
<div>
<table >
//updated with ajax
</table>
</div>
</fieldset>
<script>
//reloadTab is in here
</script>
</div>
<div id="Tab2">
<fieldset style="float:left; width:300px;">
<input id="submit2" type="button" value="submit"/>
</fieldset>
<fieldset style="float:left;">
<legend>Overview</legend>
<table>
//updated with ajax
</table>
</fieldset>
<script>.....</script>
</div>
Turns out tabs.('select', ...) is deprecated, using tabs.('option', 'active', index) fixed my issue. Solution found in this comment: https://stackoverflow.com/a/16033969/1463649
Do you see anything in the console of your browser? What browser are you using?
Try this to help you with the debugging.
function reloadTab() {
console.log($('#tabs')); // if this is an empty object, the element doesn't exist when you call this function
console.log($('#tabs').tabs()); // if this doesn't return 'function', you haven't included a library properly, maybe jquery ui, or jquery, or you're using an old version or something
console.log(currentTab); // if this is undefined then something went wrong and no tab is active
var currentTab = $("#tabs").tabs("option", "active");
alert(currentTab);
$('#tabs').tabs('select', currentTab);
alert(currentTab);
}
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...
Im using the jQuery Mobile Filter List:
http://jquerymobile.com/test/docs/lists/lists-search-with-dividers.html
Is it possible to move the position of the input field so I can put it into a div already on my page?
Using jQuery's appendTo seems to work fine but its kind of a hacky solution. Thanks
this is what I found while searching for solution to a similar problem.
HTML code example:
<div data-role="page" id="page-id">
<div data-role="content">
<div data-role="fieldcontain">
<input type="search" name="password" id="search" value="" />
</div>
<div class="filter-div" data-filter="one">1</div>
<div class="filter-div" data-filter="two">2</div>
<div class="filter-div" data-filter="three">3</div>
<div class="filter-div" data-filter="four">4</div>
<div class="filter-div" data-filter="five">5</div>
</div>
</div>
JS code example:
$(document).delegate('#page-id', 'pageinit', function () {
var $filterDivs = $('.filter-div');
$('#search').bind('keyup change', function () {
if (this.value == '') {
$filterDivs.slideDown(500);
} else {
var regxp = new RegExp(this.value),
$show = $filterDivs.filter(function () {
return ($(this).attr('data-filter').search(regxp) > -1);
});
$filterDivs.not($show).slideUp(500);
$show.slideDown(500);
}
});
});
This way you can place your input text box anywhere on your page and it should properly filter a list of items you want.
JSFiddle DEMO: http://jsfiddle.net/cZW5r/30/
How do you bind a function for each index of the jquery UI tabs?
For example, I am creating a 3 part slide sign up, step 1 is a form and has validation, I want to place the code for that inside the load of step1, while also adding classes to the tabs to disable #2 and #3 when on 1, disable #1 and # 3 when on #2
There is no need to bind a function to the tabs, it's built into the plugin:
From the plugin page:
$('#tabs').tabs({
select: function(event, ui) { ... }
});
Inside the function, you can determine the current tab you are in and do what you need to do from there:
Get the current tab index
currentTabIndex = $('#tabs').tabs('option', 'selected')
Get the current tab content ID (from href) - there might be an easier way, but I haven't found it yet.
currentTabContent = $( $('.ui-tabs-selected').find('a').attr('href') );
but from seeing the other questions you posted about this tab/form system you are trying to use, I threw together a demo here.
HTML
<div id="tabs">
<ul class="nav"> <!-- this part is used to create the tabs for each div using jquery -->
<li class="ui-tabs-selected"><span>One</span></li>
<li><span>Two</span></li>
<li><span>Three</span></li>
</ul>
<div id="part-1">
<form name="myForm" method="post" action="" id="myForm">
<div class="error"></div>
Part 1
<br /><input type="checkbox" /> #1 Check me!
<br />
<br /><input id="submitForm" type="button" disabled="disabled" value="next >>" />
</form>
</div>
<div id="part-2">
<div class="error"></div>
Part 2
<br />Search <input type="text" />
<br />
<br /><input id="donePart2" type="button" value="next >>" />
</div>
<div id="part-3">
<div class="error"></div>
Part 3:
<br />Some other info here
</div>
</div>
Script
$(document).ready(function(){
// enable Next button when form validates
$('#myForm').change(function(){
if (validate()) {
$('#submitForm').attr('disabled','')
} else {
$('#submitForm').attr('disabled','disabled')
}
})
// enable form next button
$('#submitForm').click(function(){
// enable the disabled tab before you can switch to it, switch, then disable the others.
if (validate()) nxtTab(1,2);
})
// enable part2 next button
$('#donePart2').click(function(){
var okForNext = true; // do whatever checks, return true
if (okForNext) nxtTab(2,1);
})
// Enable tabs
$('#tabs').tabs({ disabled: [1,2], 'selected' : 0 });
})
function validate(){
if ($('#myForm').find(':checkbox').is(':checked')) return true;
return false;
}
function nxtTab(n,o){
// n = next tab, o = other disabled tab (this only works for 3 total tabs)
$('#tabs').data('disabled.tabs',[]).tabs( 'select',n ).data('disabled.tabs',[0,o]);
}