How to ignore changes not done by user? - javascript

I've got a ko.observable in my viewmodel, which is attached to an input. When user changes value of that input (per-character) I run AJAX call, which downloads suggestions from backend.
When user chooses one of suggestions, I'd like to fill the input with chosen value, but without sending the AJAX call. But setting observable's value will still trigger event and call function attached to textInput binding.
How can I set observable's value without triggering textInput?
Sample code:
var name = ko.observable();
name.subscribe(nameChanged);
var nameChanged = function() {
someService.post(name(), success, failure);
}
var someAction = function() {
name("Test"); // I don't want to trigger AJAX call here
}
<input type="text" data-bind="textInput: name" />

Another option is to use a computed observable as an intermediary. The ajax function can trigger in the write event of the computed so that direct writes to the observable bypass it.
function viewModel(){
var self = this;
self.realName = ko.observable('test');
self.Name = ko.computed({
read: function(){
return self.realName();
},
write: function(value){
self.realName(value);
nameChanged(value);
}
});
function nameChanged(newName) {
console.log("name changed:", newName);
}
self.modifyName = function(){
self.realName(self.realName() + 'z');
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<input type="text" data-bind="textInput: Name" />
<br />
<button data-bind="click: modifyName">Modify</button>

Maybe set a particular CSS class on the field after the selection is made and put some logic around the ajax call to check if the field has the CSS class.

Instead of subscribing to the change event of the observable you could tie your ajax call to the change event of the ui element instead. You would get rid of name.subscribe(nameChanged); and add an event binding:
data-bind="textInput: name, event: { changed: nameChanged }"

An "old fashioned" way that works, but is not really elegant, is to temporarily dispose the subscription and then reattach it:
var myName = ko.observable();
var nameSub = myName.subscribe(nameChanged);
function nameChanged(newName) {
console.log("name changed:", newName);
}
function changeWithoutLog() {
nameSub.dispose();
myName("Test");
nameSub = myName.subscribe(nameChanged);
}
ko.applyBindings({ name: myName });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<input type="text" data-bind="textInput: name" />
<button data-bind="click: changeWithoutLog">change without post</button>

Related

Trigger a function when value change using knockoutjs

I want to trigger a function when user select other value from a combobox <select>
HTML
<div class="wrapper wrapper-content" id="appGradeHorariaCompleta">
<select class="form-control m-b" data-bind="options: Curriculos,optionsText:'Text',optionsValue:'Value',optionsCaption:'Selecione', value: CurriculoSelected, event:{ change: $parent.CurriculosChanged}"></select>
</div>
JS
function PainelViewModel() {
var self = this;
self.Curriculos = ko.observableArray([]);
self.CurriculosChanged = (function (curriculo) {
console.debug(curriculo);
});
}
function CurriculoViewModel() {
var self = this;
self.Id = ko.observable(0);
self.Value = ko.observable('');
self.Selected = ko.observable(0);
}
...GET DATA...
...
..
.
$(data.CurriculoComboBox).each(function (index, item) {
var model = new CurriculoViewModel();
model.myvalues = item.myValues;
painelVM.Curriculos().push(model);
});
ko.applyBindings(painelVM, document.getElementById("appGradeHorariaCompleta"));
What I want is when value change in the combobox I want to get this parameter to bind another function.
But in the way I've done, I got an error:
Unable to process binding "event: function (){return { change:$parent.HabilitacoesCursosChanged} }
First thing first your view model is real messed up. You may need to make changes with variable names used in data-bind to match those in viewmodel. I hope I am getting you right. What you want is to trigger a method when value of combobox changes. For that there is an easier way of using subscribe on an observable(your combobox value). So, whenever value of your combobox changes, subscribe function is triggered. The functionality that you want is passed inside the subscribe function as a function body.
HTML:
<div class="wrapper wrapper-content" id="appGradeHorariaCompleta">
<select class="form-control m-b" data-bind="options:Curriculos, optionsText:'Text',optionsValue:'Value',optionsCaption:'Selectone', value: CurriculoSelected"></select>
</div>
Javascript:
function CurriculoViewModel() {
var self = this;
self.Curriculos = ko.observableArray([]);
self.CurriculoSelected = ko.observable('');
self.CurriculoSelected.subscribe(function(value) {
// Your change functionality goes here combobox.
});
}
References check out the subscribe:
Knockout observables subscribe
Knockout options binding
Subscribe not work, so I still use event: {change: $parent.CurriculosChanged}
So, using ideia from siddhearth to get I get the value selected from my var, I can get the new value changing the $parent for $root
HTML
<select class="form-control m-b" data-bind="options: Curriculos,optionsText:'Text',optionsValue:'Value',optionsCaption:'Selecione', value: CurriculoSelected, event:{ change: $root.CurriculosChanged}"></select>
JS
function PainelViewModel() {
var self = this;
self.Curriculos = ko.observableArray([]);
self.CurriculoSelected = ko.observable(0);
self.CurriculosChanged = function (c) {
console.debug(c.CurriculoSelected);
};
}
Check binding context

How to bind current object to text input?

MessageModel
function MessageModel(content) {
var self = this;
self.content = content;
}
RoomViewModel
self.currentMessage = ko.observable(new MessageModel(""));
self.addMessage = function () {
self.messages.push(self.currentMessage());
self.currentMessage(new MessageModel(""));
};
View
<form data-bind="submit: addMessage">
<input data-bind='value: currentMessage.content, valueUpdate: "afterkeydown"' />
<button id="ButtonSendMessage" type="submit">Send</button>
</form>
When user types in input box want the current message content property to update and when I click add I want the currentMessage to be added with content. But the content is always blank.
Maybe because the content isn't an observable and the value bind is wrong beacuse currentMessage is an observable so to bind any property of you must to do like currentMessage().prop or use the with: currentMessage binding in an element context, try something like this:
function MessageModel(content) {
var self = this;
self.content = ko.observable(content);
}
And also i suggest you to use the textInput bind:
<form data-bind="submit: addMessage">
<input type="text" data-bind='textInput: currentMessage().content' />
<button id="ButtonSendMessage" type="submit">Send</button>
</form>
textInput Binding:
The textInput binding links a text box () or text area () with a viewmodel property, providing two-way updates between the viewmodel property and the element’s value. Unlike the value binding, textInput provides instant updates from the DOM for all types of user input, including autocomplete, drag-and-drop, and clipboard events.
Ref:TextInput Bind

DataPicker not getting binded to textbox ? fiddle provided

Well in other cases i will get datepicker binded to my textbox which will be straight forward but not in this case .
Fiddle link : http://jsfiddle.net/JL26Z/1/ .. while to setup perfect seanrio i tried but unable to bind datepicker to textboxes . except that everything is in place
My code :
**<script id="Customisation" type="text/html">** // here i need to have text/html
<table style="width:1100px;height:40px;" align="center" >
<tr>
<input style="width:125px;height:auto;" class="txtBoxEffectiveDate" type="text" id="txtEffective" data-bind="" />
</tr>
</script>
The above code is used for my dynamic generation of same thing n no of time when i click each time on a button . So above thing is a TEMPLATE sort of thing .
My knockout code :
<div data-bind="template:{name:'Customisation', foreach:CustomisationList},visible:isVisible"></div>
<button data-bind="click:$root.CustomisatioAdd" >add </button>
I tried same old way to bind it with datepicker
$('#txtEffective').datepicker(); // in document.ready i placed
Actually to test this i created a textbox with some id outside script with text/html and binded datepicker to it and It is working fine sadly its not working for the textbox inside text/html and i want to work at any cost.
PS: well i haven't posted my view model as it is not required in this issue based senario
View model added with Js
var paymentsModel = function ()
{
function Customisation()
{
var self = this;
}
var self = this;
self.isVisible = ko.observable(false);
self.CustomisationList = ko.observableArray([new Customisation()]);
self.CustomisationRemove = function () {
self.CustomisationList.remove(this);
};
self.CustomisatioAdd = function () {
if (self.isVisible() === false)
{
self.isVisible(true);
}
else
{
self.CustomisationList.push(new Customisation());
}
};
}
$(document).ready(function()
{
$('#txtEffective').datepicker();
ko.applyBindings(new paymentsModel());
});
Any possible work around is appreciated
Regards
The best way I've found to do this is create a simple bindingHandler.
This is adapted from code I have locally, you may need to tweak it...
** code removed, see below **
Then update your template:
** code removed, see below **
By using a bindingHandler you don't need to try to hook this up later, it's done by knockout when it databinds.
Hope this is helpful.
EDIT
I created a fiddle, because I did indeed need to tweak the date picker binding quite a lot. Here's a link to the Fiddle, and here's the code with some notes. First up, the HTML:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" data-bind="value: countryCode" />
</span>
<span><br/>
<label>Date:</label>
<input type="text" data-bind="datepicker: date" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" data-bind="value: phoneNumber" />
</span>
<input type="button" value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
<div>
<input type="button" value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
Note I removed the id=... from in your template; because your template repeats per phone number, and ids must be unique to be meaningful. Also, I removed the datepicker: binding from the country code and phone number elements, and added it only to the date field. Also - the syntax changed to "datepicker: ". If you need to specify date picker options, you would do it like this:
<input type="text" data-bind="datepicker: myObservable, datepickerOptions: { optionName: optionValue }" />
Where optionName and optionValue would come from the jQueryUI documentation for datepicker.
Now for the code and some notes:
// Adapted from this answer:
// https://stackoverflow.com/a/6613255/1634810
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
observable = valueAccessor(),
$el = $(element);
// Adapted from this answer:
// https://stackoverflow.com/a/8147201/1634810
options.onSelect = function () {
if (ko.isObservable(observable)) {
observable($el.datepicker('getDate'));
}
};
$el.datepicker(options);
// set the initial value
var value = ko.unwrap(valueAccessor());
if (value) {
$el.datepicker("setDate", value);
}
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element);
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') === 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
function Phone() {
var self = this;
self.countryCode = ko.observable('');
self.date = ko.observable('');
self.phoneNumber = ko.observable('');
}
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone()]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone());
};
}
var phoneModel = new PhoneViewModel();
ko.applyBindings(phoneModel);
Note the very updated binding handler which was adapted from this answer for the binding, and this answer for handling onSelect.
I also included countryCode, date, and phoneNumber observables inside your Phone() object, and turned your model into a global variable phoneModel. From a debugger window (F12 in Chrome) you can type something like:
phoneModel.PhoneList()[0].date()
This will show you the current value of the date.
I notice that your form is set up to post somewhere. I would recommend instead that you add a click handler to a "Submit" button and post the values from your phoneModel using ajax.
Hope this edit helps.
Dynamic entities need to have datepicker applied after they are created. To do this I'd use an on-click function somewhere along the lines of
HTML
<!-- Note the id added here -->
<button data-bind="click:$root.CustomisatioAdd" id="addForm" >add </button>
<script>
$(document).on('click', '#addForm', function(){
$('[id$="txtEffective"]').datepicker();
});
</script>

React to blur event in knockout.js

I have an input textbox and whenever it loses focus I want to get its value text in a function.
For example, if type "testimonials1", how could I get that text in an event handler for the blur event?
This is what I tried. I get ProjectTestimonial as an object, not user-typed text.
HMTL
<div class="ratingcontents" data-bind="foreach: ProjectTestimonial">
<!--ko if: !Testimonialstext-->
<input type="text" placeholder="Testimonials" class="txttestimonials"
data-bind="
text: Testimonialstext,
event: {
blur: $root.testimonialblurFunction.bind(SourceId, SourceText, Testimonialstext)
}
"
>
<!--/ko-->
</div>
JS
self.testimonialblurFunction = function (data, event, Testimonialstext) {
debugger;
alert(data.soid + Testimonialstext);
}
You can use event, that attaches to any JS event:
<input name="id" data-bind="value: id, event: { blur: blurFunction }">
And in your view model:
self.blurFuncion = function(){
// this attacks when blur event occurs in input
}
Simple as that.
The first mistake you made was using the 'text' binding on the input field, rather than the 'value' binding.
Regarding the event handler, I would not do this. I would use knockout's 'subscribe' functionality to listen for changes to the observable.
Here is a Jsfiddle version of your code. I have changed your markup to demonstrate more clearly.
HTML
<div class="ratingcontents" data-bind="foreach: ProjectTestimonial">
<input type="text" placeholder="Testimonials" class="txttestimonials"
data-bind="value: Testimonialstext" />
</div>
Javascript
function viewModel(jsModel){
var self = this;
self.ProjectTestimonial = ko.utils.arrayMap(jsModel, function(item) {
return new testimonial(item);
});
}
function testimonial(jsTestimonial){
var self = this;
ko.mapping.fromJS(jsTestimonial, {}, self);
self.Testimonialstext.subscribe(function(){
alert(self.SourceId() + self.Testimonialstext());
});
}
var rawModel = [
{
SourceId: '1',
SourceText: 'Foo',
Testimonialstext: 'Ahoy there.'
},
{
SourceId: '2',
SourceText: 'Bar',
Testimonialstext: 'Blah blah blah'
}];
ko.applyBindings(new viewModel(rawModel));
Use the has focus binding instead. From what I understand you want the data in the text box once the user stops editing. This is simple enough. Check out this example from the knockout js documentation page.
<p>
Hello <b data-bind="text:name"></b> </p>
<input data-bind="value: name, hasfocus: editing" />
<p><em>Click the name to edit it; click elsewhere to apply changes.</em></p>
View model
function PersonViewModel(name) {
// Data
this.name = ko.observable(name);
this.editing = ko.observable(false);
// Behaviors
this.edit = function() { this.editing(true) }
}
ko.applyBindings(new PersonViewModel("Bert Bertington"));
jsFiddle

How to create an observable array with undo?

I am trying to add knockout JS to a search page on our website. Currently you open up a jQuery dialog box, which has a number of checkboxes of criteria that you can select.
There are multiple dialogs with multiple types of criteria. When you open the dialog, the checkboxes do not take effect until you hit an "Update" button, if you click cancel or just close the window, the changes you made get reverted and the dialog is set to its former state.
I read this and a few other posts. However this seems to only work with ko.observable, and I cannot seem to get it to work with ko.observableArray.
Has anyone accomplished this or have any ideas?
An example of what I want to do:
Html:
<form>
<div>
<div>
<label><input type="checkbox" data-bind="checked: genders" value="1" />Male</label>
<label><input type="checkbox" data-bind="checked: genders" value="2" />Female</label>
</div>
</div>
<a id="buttonCancel">Cancel</a>
<a id="buttonUpdate">Update</a>
</form>
<div data-bind="text: ko.toJSON(viewModel)"></div>
Javascript:
var viewModel = {
genders: ko.observableArrayWithUndo([])
};
ko.applyBindings(viewModel);
$('#buttonCancel').click(function(){
viewModel.genders.resetChange();
});
$('#buttonUpdate').click(function(){
viewModel.genders.commit();
return false;
});
Here would be one way to approach it:
//wrapper to an observableArray of primitive types that has commit/reset
ko.observableArrayWithUndo = function(initialArray) {
var _tempValue = ko.observableArray(initialArray.slice(0)),
result = ko.observableArray(initialArray);
//expose temp value for binding
result.temp = _tempValue;
//commit temp value
result.commit = function() {
result(_tempValue.slice(0));
};
//reset temp value
result.reset = function() {
_tempValue(result.slice(0));
};
return result;
};
You would bind your checkboxes to yourName.temp and the other part of your UI to just yourName.
Here is a sample: http://jsfiddle.net/rniemeyer/YrfyW/
The slice(0) is one way to get a shallow copy of an array (or even just slice()). Otherwise, you would be performing operations on a reference to the same array.
Given HTML similar to:
<div>
<button data-bind="click: function() { undo(); }">Undo</button>
<input data-bind="value: firstName" />
<input data-bind="value: lastName" />
<textarea data-bind="value: text"></textarea>
</div>
You could use some Knockout code similar to this, basically saving the undo stack as a JSON string representation of the state after every change. Basically you create a fake dependent observable to subscribe to all the properties in the view, alternatively you could manually iterate and subscribe to each property.
//current state would probably come from the server, hard coded here for example
var currentState = JSON.stringify({
firstName: 'Paul',
lastName: 'Tyng',
text: 'Text'
})
, undoStack = [] //this represents all the previous states of the data in JSON format
, performingUndo = false //flag indicating in the middle of an undo, to skip pushing to undoStack when resetting properties
, viewModel = ko.mapping.fromJSON(currentState); //enriching of state with observables
//this creates a dependent observable subscribed to all observables
//in the view (toJS is just a shorthand to traverse all the properties)
//the dependent observable is then subscribed to for pushing state history
ko.dependentObservable(function() {
ko.toJS(viewModel); //subscribe to all properties
}, viewModel).subscribe(function() {
if(!performingUndo) {
undoStack.push(currentState);
currentState = ko.mapping.toJSON(viewModel);
}
});
//pops state history from undoStack, if its the first entry, just retrieve it
window.undo = function() {
performingUndo = true;
if(undoStack.length > 1)
{
currentState = undoStack.pop();
ko.mapping.fromJSON(currentState, {}, viewModel);
}
else {
currentState = undoStack[0];
ko.mapping.fromJSON(undoStack[0], {}, viewModel);
}
performingUndo = false;
};
ko.applyBindings(viewModel);
I have a sample of N-Level undo with knockout here:
http://jsfiddle.net/paultyng/TmvCs/22/
You may be able to adapt for your uses.

Categories