Binding issues with knockout and jQuery Mobile - jquery-mobile

UPDATE: The third bullet below ("so I tried the following" section) is the closest I have come to a fix. I think I basically need to 1) disable the button, 2) add ui-disable, 3) jqm refresh, all within the data-bind or model.
I am trying to get one of the knockout demos to run with jqm in order to build something similar in my project. It mostly works except that the submit button does not disable goes disabled but does not appear grayed out if items = 0.
If you remove jqm, the example works fine and the button turns gray. I realize that jqm can conflict with knockout due to dom manipulation so I tried the following:
Triggering style refresh in the model methods: $('button').button(); or $('.ui-page-active' ).page( 'destroy' ).page();
Starting the binding after pageinit. This broke the entire thing.
As a test, I tried a data-bind to set ui-disable instead of disabling the button. It applies the class but jqm needs a refresh somehow. Can I put code into the data-bind to do the refresh?
<button data-bind="css: {'ui-disable': gifts().length > 0}" type='submit'>Submit</button>
Here is the fiddle I have been using to troubleshoot this: http://jsfiddle.net/wtjones/wkEgn/
What am I missing?
<form action='/someServerSideHandler'>
<p>You have asked for <span data-bind='text: gifts().length'> </span> gift(s)</p>
<table data-bind='visible: gifts().length > 0'>
<thead>
<tr>
<th>Gift name</th>
<th>Price</th>
<th />
</tr>
</thead>
<tbody data-bind='foreach: gifts'>
<tr>
<td><input class='required' data-bind='value: name, uniqueName: true' /></td>
<td><input class='required number' data-bind='value: price, uniqueName: true' /></td>
<td><a href='#' data-bind='click: $root.removeGift'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: addGift'>Add Gift</button>
<button data-bind='enable: gifts().length > 0' type='submit'>Submit</button>
</form>
The model code:
var GiftModel = function(gifts) {
var self = this;
self.gifts = ko.observableArray(gifts);
self.addGift = function() {
self.gifts.push({
name: "",
price: ""
});
};
self.removeGift = function(gift) {
self.gifts.remove(gift);
};
self.save = function(form) {
alert("Could now transmit to server: " + ko.utils.stringifyJson(self.gifts));
// To actually transmit to server as a regular form post, write this: ko.utils.postJson($("form")[0], self.gifts);
};
};
var viewModel = new GiftModel([
{ name: "Tall Hat", price: "39.95"},
{ name: "Long Cloak", price: "120.00"}
]);
ko.applyBindings(viewModel);
// Activate jQuery Validation
//$("form").validate({ submitHandler: viewModel.save });

Yep. If you change button properties via JS (or using KO to change these props), then you must call the refresh method to update visual styling.
$('button').button('refresh');
So I suggest to create custom binding instead of default enable that updates mobile button styling (if applied):
ko.bindingHandlers.mobileEnable = {
update: function(el) {
ko.bindingHandlers.enable.update.apply(el, arguments);
$.fn.button && $(el).button('refresh');
}
}
and...
<button data-bind='mobileEnable: gifts().length > 0' type='submit'>Submit</button>
Corrected fiddle: http://jsfiddle.net/wkEgn/2/

Related

Close all other jQueryUI tooltips when a new tooltip is opened

How can I close all other jQueryUI tooltips when a user opens a new tooltip. I want to avoid littering the UI with open tooltips.
I did not want to clutter up what I thought was a straightforward question but my tooltips are customized to only show when a user has clicked on a help icon or field name as in the example below. In addition as in the example, the help triggers are not in the [label] tag associated to that input and so the tooltip can't count on the field focus. I suspect that is the issue.
function loadCSS(filename) {
var file = document.createElement("link");
file.setAttribute("rel", "stylesheet");
file.setAttribute("type", "text/css");
file.setAttribute("href", filename);
document.head.appendChild(file);
}
loadCSS("https://code.jquery.com/ui/1.12.1/themes/start/jquery-ui.css");
// Disable HOVER tooltips for input elements since they are just annoying.
$("input[title]").tooltip({
disabled: true,
content: function() {
// Allows the tooltip text to be treated as raw HTML.
return $(this).prop('title');
},
close: function(event, ui) {
// Disable the Tooltip once closed to ensure it can only open via click.
$(this).tooltip('disable');
}
});
/* Manually Open the Tooltips */
$(".ui-field-help").click(function(e) {
var forId = e.target.getAttribute('for');
if (forId) {
var $forEl = $("#" + forId);
if ($forEl.length)
$forEl.tooltip('enable').tooltip('open');
}
});
.ui-field-help {
text-decoration: underline;
}
input {
width: 100%
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
<table width=100%>
<tr>
<td for="A000" class="ui-field-help">First</td>
<td><input type="Text" id="A000" title="title #A000"></td>
</tr>
<tr>
<td for="B000" class="ui-field-help">Second</td>
<td><input type="Text" id="B000" title="title #B000"></td>
</tr>
<tr>
<td for="C000" class="ui-field-help">Third</td>
<td><input type="Text" id="C000" title="title #C000"></td>
</tr>
<tr>
<td for="D000" class="ui-field-help">Fourth</td>
<td><input type="Text" id="D000" title="title #D000"></td>
</tr>
<tr>
<td for="E000" class="ui-field-help">Fifth</td>
<td><input type="Text" id="E000" title="title #E000"></td>
</tr>
</table>
What I was asking for was unnecessary. If used correctly jQueryUI tooltips will automatically close themselves based on the focus/hover events.
If the tooltip is opened without triggering these events then there won't be any way for it to automatically close itself.
I had intentionally opened the tooltips via a click on a separate help icon. That click did not trigger the focus. By fixing my code to move the icon into the LABEL (for=) tied to the INPUT, it resulted in the INPUT receiving the focus which then gave the jQueryUI tooltip widget what it needed to manage the visibility.

How to Pass Data to a vuestrap Modal component

Im trying to pass some table data to its child vuestrap Modal component. The modal will be reused by all the Td's where the checkbox is calling the modal.
<div id="ordertbl" class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
...
</tr>
</thead>
<tbody>
<tr v-repeat="slides">
<td>#{ {NAME}}</td>
<td>#{ {MESSAGE}}</td>
<td> <input type="checkbox" v-on="click:showMod = true" v-model="published" > </td>
</tr>
</tbody>
</table>
<modal title="Modal" show="#{{#showMod}}" effect="fade" width="400">
<div class="modal-body">You will publish NAME, MESSAGE</div>
</modal>
</div>
when the checkbox is clicked. As you can see every row in the table has one checkbox so the data to be passed to the Modal will be unique to its row.
As Im new to Vue, besides Im trying to use Vuestrap to not reinvent things,
I dont know how to give that Data to the Modal when it pops.
new Vue({
el:'#ordertbl',
components: {
'modal':VueStrap.modal
},
data: {
showMod: false,
sortKey: '',
reverse:false,
slides: {
id: '',
name: '',
message: '',
published: ''
}
},
Basically I want to do the following
$('#exampleModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget) // Button that triggered the modal
var recipient = button.data('whatever') // Extract info from data-* attributes
// If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
// Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
var modal = $(this)
modal.find('.modal-title').text('New message to ' + recipient)
modal.find('.modal-body input').val(recipient)
})
Pass the related data to the Modal, but with Vuestrap
You can use v-for="slide in slides". So every tr can get one of the slide object. Then you can pass it as a props to modal.
Extra ajax request is not required.
I manage to solve it with an ajax request depending on the Id clicked.

Reorder items of an angularfire backed list, drag and drop style

I'm building an angularjs / firebase app unsing the angularfire bindings (v0.5.0).
I have a list of items, displayed in a table with ng-repeat on the <tr>, something like this:
<table>
<tbody>
<tr ng-repeat="(index, item) in items">
<td>
<input type="checkbox" ng-model="item.done">
</td>
<td>
<input type="text" ng-model="item.text" ng-focus="onItemFocus(index)" ng-blur="onItemBlur(index)">
</td>
<td>
<button type="button" ng-click="remove(index)">×</button>
</td>
</tr>
</tbody>
</table>
and there's an angularfire 3 way data binding on this list of items, something like:
$scope.ref = new Firebase('...');
$scope.remote = $firebase($scope.ref);
$scope.remote.$child("items").$bind($scope, "items");
This works all fine, but now I'm trying to add the possibility to reorder the items with drag and drop.
I managed to setup the drag and dropping UI with jquery-ui (essentially calling $("tbody").sortable()), but my problem is to bind it to the angular models. There's a number of questions regarding that (with great jsfiddles) but in my case the angularfire 3 way binding seems to be messing it up.
I think I need to use firebase priorities with angularfire's orderByPriority and maybe deal with it in one of the sortable callbacks but I'm having trouble figuring out exactly how I should do that... and can't find any sort of documentation about it.
Has anyone done something similar, and could you share some pointers on how to set this up?
I saw your post a long time ago while I was looking for the same solution. Here is something I put together:
function onDropComplete(dropIndex, item) {
if (!item.isNew){
var dragIndex = $scope.fireData.indexOf(item);
item.priority = dropIndex;
$scope.fireData.$save(dragIndex);
if (dragIndex > dropIndex){
while ($scope.fireData[dropIndex] && dropIndex !== dragIndex ){
$scope.fireData[dropIndex].priority = dropIndex+1;
$scope.fireData.$save(dropIndex);
dropIndex++;
}
} else if(dragIndex < dropIndex){
while ($scope.fireData[dropIndex] && dropIndex !== dragIndex ){
$scope.fireData[dropIndex].priority = dropIndex-1;
$scope.fireData.$save(dropIndex);
dropIndex--;
}
}
} else if (item.isNew){
item = angular.copy(item);
item.isNew = false;
item.priority = dropIndex;
$scope.fireData.$add(item);
while ($scope.fireData[dropIndex]){
$scope.fireData[dropIndex].priority = dropIndex+1;
$scope.fireData.$save(dropIndex);
dropIndex++;
}
}
}
Here, I take items already in the list and have the priority properties of items adjust on drop, depending if the item that was dragged was above or below the drop area. Also, if the drag item in new to the list, it will be added at index where dropped and all items below will be bumped up 1 priority. This is dependent on having your list sorted by var sync = $firebase(ref.orderByChild('priority'));, and you to be using ngDraggable.
Here is some HTML for an example:
<tr ng-repeat="obj in fireData" ng-drop="true" ng-drop-success="onDropComplete($index, $data,$event)">
<td draggable="true" ng-drag="true" ng-drag-data="obj">
<span class="glyphicon glyphicon-move"></span><div class="drag-name">{{obj.name}}</div>
</td>
<td>{{obj.name}}</td>
<td>{{obj.type}}</td>
<td><input type="checkbox" ng-change="saveChanges(obj)" ng-model="obj.completed"></td>
<td>
<div class="" ng-click="deleteItemFromList(obj)">
<span class="glyphicon glyphicon-remove"></span>
</div>
</td>
</tr>

add item to observable array in viewmodel from another partial view (MVC)

I am new to knockoutJS. I am working on an MVC application where I want to implement knockoutJS but the scenario is bit different.
I have a page where I am showing a list. I have 3 links on the page and on click of them I am adding partial views to page accordingly. What I want to do is that whenever I add values/data to partial views, the list which is on page should be updated with knockout. In other words I want to add value to observable array when I save data from partial view.
Please let me know if this is possible or I should keep it in jquery only.
Here is the code:
Main view:
<input type="button" value="Add Partial View" onclick="LoadPartial();" />
<div id="dvContent"></div>
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
Remove
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script>
function LoadPartial() {
$.ajax({
url: "/home/index",
dataType:"html",
type: "GET",
success: function (data) {
$("#dvContent").html(data);
}
});
}
</script>
<script>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function () {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function () {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
</script>
Partial View:
<table>
<tr>
<td>Add new Row</td>
<td><input type="button" value="Add" data-bind="click: addPerson"/></td>
</tr>
</table>
Thanks,
JsHunjan
It is easy to accomplish with Knockout. You need to show some code that you have tried though if you want to get some help. I will post a general answer but it isn't going to fix your use case exactly, just basically -
Create an object to hold your new item, you can do this either in the parent or the child view model, but if you do it in the child you need to pass it back to the parent.
Once you hit a save button or add or whatever in the child view model just do a .push() into the observableArray that you created ex... - myObservableArray.push(newItem());
Knockout will recognize all of the changes taking place and perform the actions you want automatically.
Hope this helps.

Knockout refresh observableArray in the html

I'm working on Android with Jquery Mobile with PhoneGap using Knockout for loading data.
I'm getting the data all right and loading it on the HTML page accordingly to the for each data-bind I have on the tags.
When I want to refresh the data, it just doesn't do it. It returns just an HTML without bound data or throwing dom exception not found.
My applyBinding happens inside pagecreate event of the page.
I've posted a simple example describing the problem on my SkyDrive - http://sdrv.ms/LpUdLt
It's a public example reproducing the problem.
the viewmodel holds an array that holds array.
refreshed with randomal values.
trying to reload the page in jquery mobile with changepage reload with new data by pressing the navbar button fails with the dom object mistake.
I do agree not to that I shouldn't create an instance of VM every page create, just can't find a way to implement it, so that the data will be rerendered on HTML.
//indexPage.js
var wAViewModelInst ;
var viewPageIndexContent;
$('#pageIndex').live('pagecreate', function (event) {
viewPageIndexContent = document.getElementById("pageIndexContent");
wAViewModelInst = new WAViewModel(true);
ko.applyBindings(wAViewModelInst, viewPageIndexContent);
waHeaderVM.refreshContentData = function () {
// wAViewModelInst.updateRowList();
// ko.cleanNode(viewPageIndexContent);
// viewPageIndexContent = document.getElementById("pageIndexContent");
//wAViewModelInst = new WAViewModel(true);
//ko.applyBindings(wAViewModelInst, viewPageIndexContent);
$.mobile.changePage("index.html", { allowSamePageTransition: true, reloadPage: true });
$.mobile.hidePageLoadingMsg();
}
}
//WAViewModel
self.WARowList = ko.observableArray();
self.updateRowList = function () {
self.WARowList(self.GetWA());
}
//tried the exteding
//ko.observableArray.fn.WARowListUpdate = function () {
// //create a sub-observable
// this.hasItems = ko.observable();
// //update it when the observableArray is updated
// this.subscribe(function (newValue) {
// this.hasItems(newValue && newValue.length ? true : false);
// }, this);
// //trigger change to initialize the value
// this.valueHasMutated();
// //support chaining by returning the array
// return this;
//};
is there any way to update the html after the first rendering ?
adding the html code:
<div id="pageIndex" data-role="page" data-transition="flip" data-theme="e" data-dom-cache="true">
<div id="indexHeader" data-role="header" data-theme="e">
<div data-role="navbar" data-iconpos="right">
<ul>
<li><a href="login.html" data-role="tab" data-icon="back" data-bind="click: loadingHandler"
class="brighter-text">חזרה</a></li>
<li><a href="#" data-role="tab" data-icon="refresh" data-bind ="click: refreshContentData" >רענן</a></li>
<li>ביצוע חקר</li>
<li><a href="#" data-role="tab" data-icon="grid" class="ui-btn-active brighter-text">
סידור עבודה</a></li>
</ul>
</div>
</div>
<div id="pageIndexContent" data-role="content" data-theme="e" style="padding-bottom: 52px;
height: 570px;">
<h2 data-bind="text:Title" class="brighter-text" style="font-size:22pt;">
</h2>
<div data-bind="foreach: WARowList" style="width: 99%; text-align: center">
<div>
<table style="float: right; width: 20%; height: 60px;" cellpadding="0" cellspacing="0">
<tr data-bind="visible : FirstRow " style="height: 31px;">
<th class="AlignedHeader">
<label>
שעה / שילוט</label>
</th>
</tr>
<tr data-bind="style: { backgroundColor: Odd() ? '#8CC63F' : '#AFC493' }">
<td style="width: 20%;" data-bind="style: { backgroundColor: IsNew() ? 'yellow' : 'transparent' }">
<input type="button" data-bind="click: ShowSampleDetails, value: ShilutTime , jQueryButtonUIEnableDisable:$data"
data-icon="plus" data-iconpos="right"/>
</td>
</tr>
</table>
<table style="height: 60px; width: 80%; background-color: #8CC63F;" data-bind="style: { backgroundColor: Odd() ? '#8CC63F' : '#AFC493' }"
cellpadding="0" cellspacing="0">
<thead data-bind="if : FirstRow">
<tr data-bind="foreach: CellList">
<th class="AlignedHeader">
<label data-bind="text: Date">
</label>
</th>
</tr>
</thead>
<tbody>
<tr data-bind="foreach: CellList">
<td style="width: 11.5%;">
<div data-bind="visible:IsPopulated ">
<div data-bind="visible: HasDrivers">
<input type="button" data-role="button" data-bind="click: ShowBusDriverList.bind($data , $root) , jQueryButtonUIEnableDisable: $data "
data-icon="search" data-iconpos="right" value="נהגים" class="brighter-text" />
</div>
<div data-bind="visible: !HasDrivers()">
<input type="button" data-role="button" id="btnNoDriver" disabled="disabled" data-icon="info"
data-iconpos="right" value="אין נהג" class="brighter-text" />
</div>
</div>
<div data-bind="visible: !IsPopulated">
</div>
</td>
</tr>
</tbody>
</table>
</div>
and so on ..
the GetWA function returns an observableArray of warow list .
it works the first time the trouble is rerendering the dom object.
the dom element is contaminated with ko and fails ..
I tried the answer of Luffy :
var lVM = new loginViewModel();
var footerViewModelLogin = {
IsOnline: ko.observable(globalContext.Network()),
IsSync: ko.observable(globalContext.Sync())
};
$('#login').live('pagecreate', function (event) {
viewLoginContent = document.getElementById("loginContent");
ko.applyBindingsToNode(viewLoginContent, lVM);
viewLoginFooter= document.getElementById("footerLogin");
ko.applyBindingsToNode(viewLoginFooter, footerViewModelLogin);
});
$('#login').live('pagehide', function (event, ui) {
$.mobile.hidePageLoadingMsg();
});
function loginViewModel() {
var self = this;
try {
self.userName = ko.observable("");
self.password = ko.observable("");
self.message = ko.observable("");
self.CleanGlobalContext = function () {
...
};
self.Validate = function () {
...
};
}
catch (e) {
if (IsDebug) alert("GlobalContext.prototype.SetMapOverlay " + e.message);
if (typeof (console) != 'undefined' && console) console.log("GlobalContext.prototype.SetMapOverlay " + e.message);
}
}
ko.applyBindings(lVM);
ko.applyBindings(footerViewModelLogin);
The knockout fails without the element predefined event to bind .
I have not understood well you difficulty. However, I don't understand why you need to reapply bindings. Notice what follows:
If you change an observable array the needed templates are re-instantiated to updated coherently the UI. However you have to update the observalble array either by using the observable array functions, or by passing to the observable a new array: obs(newArray).
If you need to perform actions after the ko re-rendering you just need to use the afterRender, or afterAdd parameters of the template binding
if you add new jquery mobile code you have to parse it after having created it otherwise jquery mobile will not work...again you can do this in the afterRender function.
it is problematic to load html containing ajax ko bindings via ajax. The right approach is to create that content as a template, that you render immediately (not with ajax). Then you query the server just to get new json content. Then you can instantiate the template by using for instance the with binding.
Yes you can reapply bindings with ko.applyBindingsToNode function to the specific dom element.
Check these examples
http://jsfiddle.net/rniemeyer/gYk6f/ ---> With templating
http://jsfiddle.net/rniemeyer/BnDh6/ ---> With data - value pair
The solution was to add afterRender on the foreach table data-bind. as – Francesco Abbruzzese suggested.
The ajax request has been turned to async:false though.
Each time I update the list I perform some dat-bind = "click:updateRowList" which updates the table.
Thanks a lot to Elad Katz for help with bounty .

Resources