I am using Knockout Js for my view page. I have a requirement where if any editable field changes, I have to enable Save button else not. This is working nicely.
My issue is I have checkboxes too for each row of item. These are observable items in my viewModel. What happens now is when I check or uncheck any checkbox, Knockout considers that as Dirty item and enables the Save button which I don't want.
How can I tackle this?
I am not sure of the exact code that you are using for a dirty flag, but if it involves using ko.toJS in a dependentObservable like this, then there is a trick that you can use to have it skip some observables.
If you create an observable that is a property of a function, then ko.toJS will not find it.
Here are two examples (someFlag and anotherFlag):
function Item(id, name) {
this.id = ko.observable(id);
//create a sub-observable that the dirty flag won't find
this.id.someFlag = ko.observable(false);
this.name = ko.observable(name);
this.dirtyFlag = new ko.dirtyFlag(this);
//or similarly, place an observable on a plain ol' function
this.forgetAboutMe = function() { };
this.forgetAboutMe.anotherFlag = ko.observable(false);
}
Sample here: http://jsfiddle.net/rniemeyer/vGU88/
Related
Is the left over text in input accessible programatically? If so, how?
I only allow tags from autocomplete (to use as search filters), and want to use the left over text as additional keywords, meaning I want to know if it's bound to anything so I can pass it to a search function.
Thanks for the help
That's not directly possible, but you can hack into the directive and make it work by using a helper directive:
app.directive('bindInternalInputTo', function() {
return function(scope, element, attrs) {
var property = attrs.bindInternalInputTo,
input = element.find('input'),
inputScope = input.scope();
inputScope.$watch('newTag.text', function(value) {
scope[property] = value;
});
};
});
Now you can bind some variable in the outer scope to the inner input by doing the following:
<tags-input ng-model="tags" bind-internal-input-to="variable"></tags-input>
Working Plunker
Please note that this solution isn't guaranteed to work with future versions of ngTagsInput since it relies on internal implementation details.
I'm having trouble with a knockout model that is not binding on a subscribed update. I have a C# MVC page that delivers a model to the template which is parsed to Json and delivered raw as part of a ViewModel assignment for ko.applyBindings. I have a subscription to an observable that calls a method to perform an update of the viewModel's data. Irrelevant stuff pulled out and renamed for example usage:
var myViewModel = function (data) {
var self = this;
self.CurrentPage = ko.observable();
self.SomeComplexArray= ko.observableArray([]);
self.Pager().CurrentPage.subscribe(function (newPage) {
self.UpdateMyViewModel(newPage);
});
self.UpdateMyViewModel= function (newPage) {
var postData = { PageNumber: newPage };
$.post('/Article/GetMyModelSearchByPage', postData, function (data) {
ko.mapping.fromJS(data, {}, self);;
});
};
When I perform logging, I can see all of the data, and it all looks correct. The same method is used to produce both the initial model and the updated model. I've used this technique on other pages and it worked flawlessly each time. In this case however, I'm looking for it to bind/update SomeComplexArray, and that's just not happening. If I attempt to do it manually, I don't get a proper bind on the array I get blank. I'm wondering if there is something obvious that I'm doing wrong that I'm just flat out missing.
Edit: I don't know that ko.mapping can be pointed to as the culprit. Standard model changes are also not affecting the interface. Here is something that is not working in a bound sense. I have a p element with visible bound to the length of the array and a div element with a click bound to a function that pops items off of SomeComplexArray. I can see in the console log that it is performing its function (and subsequent clicks result in 'undefined' not having that function). However, the p element never displays. The initial array has only 2 items so a single click empties it:
<p data-bind="visible: SomeComplexArray().length === 0">nothing found</p>
<div data-bind="click: function() { UpdateArray(); }">try it manually</div>
-- in js model
self.UpdateArray = function () {
console.log(self.SomeComplexArray());
console.log(self.SomeComplexArray().pop());
console.log(self.SomeComplexArray());
console.log(self.SomeComplexArray().pop());
console.log(self.SomeComplexArray());
});
Edit 2: from the comment #Matt Burland, I've modified how the pop is called and the manual method now works to modify the elements dynamically. However, the ko.mapping is still not functioning as I would expect. In a test, I did a console.log of a specific row before calling ko.mapping and after. No change was made to the observableArray.
I created a test of your knockout situation in JSFiddle.
You have to call your array function without paranthesis. I tested this part:
self.UpdateArray = function () {
self.SomeComplexArray.pop();
};
It seems to be working on JSFiddle side.
I'm not really sure why, but it would seem that ko.mapping is having difficulty remapping the viewmodel at all. Since none of the fields are being mapped into self my assumption is that there is an exception occurring somewhere that ko.mapping is simply swallowing or it is not being reported for some other reason. Given that I could manually manipulate the array with a helpful tip from #MattBurland, I decided to backtrack a bit and update only the elements that needed to change directly on the data load. I ended up creating an Init function for my viewModel and using ko.mapping to populate the items directly there:
self.Init = function (jsonData) {
self.CurrentPage(0);
self.Items(ko.mapping.fromJS(jsonData.Items)());
self.TotalItems(jsonData.TotalItems);
// More stuff below here not relevant to question
}
The primary difference here is that the ko.mapping.fromJS result needed to be called as a function before the observableArray would recognize it as such. Given that this worked and that my controller would be providing an identical object back during the AJAX request, it was almost copy/past:
self.UpdateMyViewModel= function (newPage) {
var postData = { PageNumber: newPage };
$.post('/Article/GetMyModelSearchByPage', postData, function (data) {
self.Items(ko.mapping.fromJS(JSON.parse(data).Items)());
});
};
This is probably not ideal for most situations, but since there is not a large manipulation of the viewModel occurring during the update this provides a working solution. I would still like to know why ko.mapping would not remap the viewModel at the top level, but in retrospect it probably would have been a disaster anyway since there was "modified" data in the viewModel that the server would have had to replace. This solution is quick and simple enough.
I have taken the codes shared from the Modal example page and instead of an LI I have decided to use a select element. My select element has ng-model="selectedColor" in it, and I can use {{selectedColor}} all over the partial I created, however, I can not use "$scope.selectedColor" from the "Model Instance Controller" or any controller for that matter. I assume this is because something is off with $scope but I cant seem to figure it out. Any help is appreciated.
http://plnkr.co/edit/MsNBglLJN0hWxvGZ1pj1?p=preview
The problem in your code is that $scope.selectedColor and the selectedColor in the modal markup are two different references. For details on this, please read Understanding Scopes, you will probably benefit from it as it is a common task.
Instead of writing $scope.selectedColor, you should make an object in your controller, then store the result in it.
var ModalInstanceCtrl = function ($scope, $modalInstance, colors) {
$scope.colors = colors;
$scope.o = {}
$scope.ok = function () {
console.log($scope.o.selectedColor, "$scope.o.selectedColor");
$modalInstance.close($scope.o.selectedColor);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
and in the markup, refer to o.selectedColor.
Here is a working version of your Plunker
I have a checkbox and a textbox (both are enabled and the checkbox starts unchecked [false]).
What I need is the following:
When I write something in the textbox and leave it (loses focus) the
checkbox is checked automatically.
When I write something in the
textbox, remove it and leave it the checkbox should remain
unchecked.
When I write something in the textbox and click the
checkbox, the checkbox is checked now and the data in the textbox is
not cleared.
When I write something in the textbox and click the
checkbox twice, first happens step 3 and then the checkbox is
unchecked and the data in the textbox is cleared.
When I click in the checkbox the checkbox is checked, then I write in the textbox
and uncheck the checkbox, then the data in the textbox is cleared.
What I tried so far is the following code:
//The checked property in the checkbox is binded to
that.BuildingCriteria.IncludeLoadingDocks
that.BuildingCriteria.IncludeLoadingDocks.subscribe(function (newValue) {
if (!that.updatingTextBox && !newValue) {
that.BuildingCriteria.LoadingDocksMin(null);
}
});
//The textbox value is binded to that.BuildingCriteria.LoadingDocksMin
that.BuildingCriteria.LoadingDocksMin.subscribe(function (newValue) {
that.updatingTextBox = true;
that.BuildingCriteria.IncludeLoadingDocks(true);
that.updatingTextBox = false;
});
This works if you try all the steps above, for all of them but then, when you try some of them again stops working for some... specially if you write something in the textbox with the checkbox unchecked and then leave the textbox, it doesn't check the checkbox automatically anymore.
I tried using flags as you can see but I couldn't make it to work on ALL the cases ALWAYS.
I've been working on this for days so if you can help me out soon I'd appreciate it a lot!
Thanks in advance!!
It's near impossible to gave a straight up answer to your question, but from it I feel the closest thing may be to note a few KO features that you may yet need to consider.
The value binding supports a valueUpdate = 'afterkeydown' version, which would allow you to keep your textbox and checkbox in synch real time. This may well remove the need for requirement 3.
The computed observable supports specializing read and write operations, which at times may be clearer than using subscriptions.
You may need to introduce a "grace" period for the checkbox, if you must stick with requirement 3. Just don't allow updating the checkbox too shortly after leaving the textbox. The throttle extender and hasfocus binding can help you with that.
There's a great blogpost on when to use which feature.
In any case, your requirements are a bit hard to understand without the business case, and it might even be that you're experiencing an XY-problem. From your implementation requirements I'd assume functional (not implementation) requirements like this:
There's a textbox to hold the actual order/criterium/name/whatever.
There's a checkbox to indicate such an order/etc is wanted.
This checkbox should be in synch (checked) with whether the user typed some text.
This checkbox should be in synch (unchecked) if the user empties the textbox.
If the user checks the checkbox then
If there was text for the order/etc it should be cleared.
If there was no text a default order/etc should be suggested.
Here's a jsfiddle with a demo of how you could approach these functional requirements. For completeness, here's the relevant code, starting with the View:
<input type="checkbox" data-bind="checked: isChecked" />
<input type="textbox" data-bind="value: someText, valueUpdate: 'afterkeydown', selectSuggestion: someText" />
The custom binding for selecting the "default suggestion text":
var suggestion = "<enter something>";
ko.bindingHandlers.selectSuggestion = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var currentText = ko.utils.unwrapObservable(valueAccessor());
if (currentText === suggestion) element.select();
}
};
And the ViewModel:
var ViewModel = function() {
var self = this;
var privateIsChecked = ko.observable(false);
var privateText = ko.observable("");
self.isChecked = ko.computed({
read: privateIsChecked,
write: function(value) {
if (!privateIsChecked() && value && privateText() === "") {
privateText(suggestion);
}
if (privateIsChecked() && !value) {
privateText("");
}
privateIsChecked(value);
}
});
self.someText = ko.computed({
read: privateText,
write: function(value) {
privateIsChecked(value !== "");
privateText(value);
}
});
}
I'm aware that this doesn't directly answer your question, but like I said that's pretty hard to do for us on Stack Overflow, without knowledge of your business case.
I'm trying to dynamically populate a select tag at load time (latest jQM version) using a custom template filling function.
If the fn is called in the "pagebeforechange" event, the select tag is properly initialized. Since this event is called on every page transition, I thought of moving the fn to the 'pageinit' event. This does not work, presumably because the DOM is not yet fully available. How can I coerce jQM to inject content in a page only once? Currently, I am using a kludge. There surely must be a smarter way. Thanks for any suggestions.
$(document).bind('pageinit', function () {
InitSelTagTest("#selActTag", "tplTag"); // Does not work.
});
$(document).bind("pagebeforechange", function (e, data) {
if ($("#selActTag").children().size() === 0) {
InitSelTagTest("#selActTag", "tplTag"); // Kludge, but it works
}
});
function InitSelTagTest(el,tpl) { // Append all tags to element el
var lstAllTags = JSON.parse($("#hidTag").val()); // Create tag array
// Retrieve html content from template.
var cbeg = "//<![" + "CDATA[", cend = "//]" + "]>";
var rslt = tmpl(tpl, { ddd: lstAllTags }).replace(cbeg, ").replace(cend,");
$(el).html(rslt).trigger("create"); // Add to DOM.
}
EDIT
In response to Shenaniganz' comment, it seems that the "pagebeforecreate" event could do the trick ie.
$("#pgAct").live("pagebeforecreate", function () {
// Populate tag select. Works. Traversed only once.
InitSelTag("#selActTag", "tplTag");
});
I'm not sure I fully understand your question but I'll throw a few things out there and you let me know if I can extend further.
To make something trigger only once on page load you can try to implement a regular JQuery $(document).ready(function(){}) aka $(function(){}) for the exact reason why JQuery Mobile users are told not to use it. It triggers only once on DOM load. Further pages don't trigger it because they're being switched via Ajax.
Other than that, on regular dynamic content loading you take a look at the following example I put together for someone else earlier:
http://jsbin.com/ozejif/1/edit