If I have an HTML element that is pre-populated from the server (e.g. PHP or ASP.NET) like this:
<input type="text" data-bind="value:TheMeaningOfLife" data-revert-to="42" />
and I declare my model as
var TheMeaningOfLife = ko.observable(69);
how can I get the model to initialise with the value of data-revert-to? I started with the following custom binding:
ko.bindingHandlers.serverValue = {
init: function (element, valueAccesor, allBindingsAccessor, viewModel) {
var el = $(element);
var serverValue = el.data("revert-to");
if (serverValue == undefined) {
return;
}
el.val(serverValue);
}
}
and update my control to
<input type="text" data-bind="serverValue:TheMeaningOfLife" data-revert-to="42" />
but that seems to kill the update event, because when I change the value in the textbox, the viewmodel does not update.
Solution as per segfault's answer
I tweaked segfault's answer a little to follow more closely how the server code is emitted:
ko.bindingHandlers.serverValue = {
init: function (element, valueAccesor, allBindingsAccessor, viewModel) {
var observable = valueAccesor();
if (observable() == undefined) {
var el = $(element);
var serverValue = el.data("revert-to");
observable(serverValue);
}
ko.applyBindingsToNode(element, { value: observable });
}
}
and this works just fine. I just need to make sure that the observable is initialised with a null, rather than an empty string.
It looks like you never check the observable so you are always using the revert to value. If you do a check on the value accesor and if its provided dont overwrite it.
<input type="text" data-bind="serverValue:TheMeaningOfLife, defaultValue='42'" />
ko.bindingHandlers.serverValue = {
init: function (element, valueAccesor, allBindingsAccessor, viewModel) {
var myValue = null;
var observable = valueAccesor();
if (observable() == undefined) {
observable(allBindingsAccessor.defaultValue);
}
ko.applyBindingsToNode(element, { value: observable });
}
}
Related
I have this weird issue where if I update my quantity observable through a callback updateQuantity on my custom binding checkbox it calls my update function on my custom binding. My element with the custom binding doesnt, or shouldnt have any type of subscription to it, but it gets executed again. This calls my callback method twice. Am I missing something there? If my updateQuantity only has a return true, then it doesn't call the update function. Also if I remove values.callback($element.is(':checked') || checked); in my update method of the custom binding it also works. It's like that line causes a subscription or something.
<input type="checkbox" data-bind="checkbox: { text: 'Hello', callback: updateQuantity.bind($data) }" />
Callback method
self.quantity = ko.observable(0);
self.updateQuantity = function (checked) {
var quantity = self.quantity();
if (checked)
self.quantity(quantity + 1);
else if ((quantity - 1) >= 0) {
self.quantity(quantity - 1);
}
return false;
};
Custom Binding
ko.bindingHandlers.checkbox = {
init: function(element, valueAccessor, allBindingsAccessor) {
var values = valueAccessor();
var checked = ko.utils.unwrapObservable(values.checked);
var css = ko.utils.unwrapObservable(values.css);
var $element = $(element);
var button = $('<button type="button" class="btn btn-check ' + (values.buttonClass || '') + '" data-toggle="buttons-checkbox"><i class="' + (values.iconClass || 'icon-ok') + '"></i> ' + (values.text || '') + '</button>');
button.click(function (evt, data) {
if (typeof (values.callback) != 'undefined') {
values.callback($(element).is(':checked') || checked);
}
return true;
});
button.insertBefore(element);
},
update: function(element, valueAccessor, allBindingsAccessor) {
var values = valueAccessor();
var checked = ko.utils.unwrapObservable(values.checked);
if (typeof(values.callback) != 'undefined') {
values.callback($(element).is(':checked') || checked);
}
}
};
I used .peek() in the standalone callback method and it worked. Still not sure why it would re-evaluate the custom binding though on a regular retrieval.
var quantity = self.quantity.peek();
I am trying to bind the value=".."-attribute from an <input>-field to a JsViews observable, so that changes made from a JS datepicker will get detected by the JsView framework.
Example
On initial rendering, the data-linked observedDate parameter is displayed in the <input>-field:
<input class="flatpickr-calendar" type="text" data-link="observedDate">
Then, selecting a new date using the flatpickr javascript tool, the new date will be stored in the value=".."-field:
<input class="flatpickr-calendar" type="text" data-link="observedDate" value="2017-05-09">
The problem
There is now a divergence between the date handled by observedDate and the value-attribute:
JsViews does not detect the change in the value-attribute.
Does anyone have some suggestion as of how to handle this situation? Thanks.
You need an event onChange update value observedDate.
For example, you can do so:
$(".flatpickr").flatpickr({
onChange: function(selectedDates, dateStr, instance) {
$.observable($.view(this.input).data).setProperty("observedDate", dateStr);
},
});
full code.
Update
Or you can create custom tag:
$.views.tags({
flatpickr: {
template: "<input/>",
onUpdate: false,
dataBoundOnly: true,
flatpickr: null,
isChange: false,
changeEvent: function (selectedDates, dateStr, instance) {
this.isChange = true;
this.update(dateStr);
this.isChange = false;
},
onDispose: function () {
if (this.flatpickr) {
this.flatpickr.destroy();
}
},
onAfterLink: function (tagCtx, linkCtx) {
var tag = this;
var props = tagCtx.props;
var options = {
defaultDate: tagCtx.args[0]
};
if (tag._.unlinked) {
if (!tag.linkedElem) {
tag.linkedElem = tag._.inline ? tag.contents("*").first() : $(linkCtx.elem);
}
$.each(props, function (key, prop) {
var option;
if (key.charAt(0) === "_") {
key = key.slice(1);
options[key] = prop;
}
});
options.onChange = $.proxy(tag.changeEvent, tag);
this.flatpickr = tag.linkedElem.flatpickr(options);
} else {
if (!this.isChange) {
this.flatpickr.setDate(options.defaultDate)
}
}
}
}
});
And use:
{^{flatpickr observedDate /}}
full code
Support flatpickr options:
{^{flatpickr observedDate _inline=true _maxDate='2018-05-01' _minDate='2017-05-01'/}}
full code
Almost there, just need the popover to appear only after submit button is clicked and the field has an alert class added to it.
Note: Hover only works for isValid is true in the binding handler, otherwise testing for not true requires a click rather than a hover :S
http://jsfiddle.net/hotdiggity/UUb4M/
HTML:
<input data-bind="value: lastName, popover: isValidField" data-placement="below" data-title="Alert" data-content="We have identified this information is incorrect and must be updated."/>
JS:
self.lastName = ko.observable().extend({ required: true });
self.isValidField = ko.observable();
this.submit = function () {
if (self.errors().length === 0) {
alert('Thank you.');
} else {
self.errors.showAllMessages();
self.isValidField(self.lastName.isValid());
}
};
Binding:
ko.bindingHandlers.popover = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
valueUnwrap = ko.unwrap(value),
allBindings = allBindingsAccessor(),
isValid = allBindings.value;
if (isValid) {
$(element).popover({
trigger: "hover"
});
} else {
$(element).popover("hide");
}
},
Update: //See jsfiddle link above (the same code)
Here is a working version for you. The popover will appear only if the validation for the lastName field is not met and only when the submit button is clicked. It will go away if the user types something into the lastName field. See this updated fiddle
The update function of a binding handler creates dependencies on observables that it accesses, so essentially the update of a binding handler will be triggered when that observable is changed. In your case, I did it with the isValidField observable. See notes in the code
var viewModel = function () {
var self = this;
self.lastName = ko.observable().extend({ required: true });
self.isValidField = ko.observable();
// once the popover is shown, we want it to go away if the user types something
// into the lastName field. We do this by triggering the popover binding handler
// by changing the value of isValidField
self.lastName.subscribe(function(val){
if(val && val.length > 0){ self.isValidField(true); }
});
// need to create your errors object
self.errors = ko.validation.group(self);
this.submit = function () {
if (self.errors().length === 0) {
alert('Thank you.');
} else {
self.errors.showAllMessages();
// change the value of isValidField to trigger the popover binding handler's
// update
self.isValidField(self.lastName.isValid());
}
};
};
ko.bindingHandlers.popover = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
valueUnwrap = ko.unwrap(value);
if (valueUnwrap === false) {
$(element).popover('show');
} else {
$(element).popover("destroy");
}
}
};
JSFiddle: http://jsfiddle.net/92z35/2/
I have a Pagedown editor that is currently working perfectly, except for the few cases when I have to insert some content programatically. If I insert and then type, the preview updates. But if I just insert, the preview remains unchanged.
What am I missing?
Code:
var converter = Markdown.getSanitizingConverter();
converter.hooks.chain("preBlockGamut", function (text, rbg) {
return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {http://jsfiddle.net/BcuLq/#run
return "<blockquote>" + rbg(inner) + "</blockquote>\n";
});
});
var editor = new Markdown.Editor(converter);
ko.bindingHandlers.wysiwyg = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
editor.hooks.chain("onPreviewRefresh", function () {
$(element).change();
});
editor.run();
}
};
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.computed(function() {
return this.firstName() + " " + this.lastName();
}, this);
this.appendText = function(){
$("#wmd-input").val($("#wmd-input").val() + '#')
}
};
ko.applyBindings(new ViewModel("Planet", "Earth"));
First, add an update handler to your custom binding that causes the preview to refresh when the observable is changed. Once you've done that, make all your programmatic changes by altering the observable rather than directly changing the element's val; that way Knockout will detect that the value has changed and call your new update handler which will tell the editor to refresh.
i am working on a webpage that consists a JQuery item and a Knockout Item.
basically the view has a select field and a sevond view that is being updated by the select value change.
also i have a textbox search field with jquery autocomplete.
What i want to do is when i press enter after on the search box, the javascript will update the ko.observable value and trigger the ther updates yet its not working. i've managed to trigger keypress but i cant update and trigger the update..
Heres the code:
function Station(data){
var self = this;
this.userId = ko.observable(data.userid);
this.displayName = ko.observable(data.displayName);
this.email = ko.observable(data.email);
this.redirectURL = ko.computed(function(){
return "/someurl/somerequest?userId="+self.userId();
});
this.selectText = ko.computed(function(){
return self.displayName();
})
}
function currentStation(index)
{
return self.stations()[index];
}
function StationViewModel(){
var self = this;
self.stations = ko.observableArray([]);
$("#stationSelect").attr("disabled,true");
$.getJSON("#{someurl.getStationList()}",function(allData){
var mappedStations = $.map(allData,function(item)
{
return new Station(item);
});
self.stations(mappedStations);
$("#stationSelect").attr("disabled,false");
});
url = "/someurl/somerequest?userId=";
this.selectedStation = ko.observable();
this.redirectToStation = function(){
var linkToSend =
alert(self.selectedStation.redirectURL());
}
<!-- THIS IS THE CODE THAT HAS TO UPDATE THE FIELD BUT IT DOESN'T-->
this.getStation = function(event)
{
for(i = 0; i<this.stations().length;i++)
{
if(this.stations()[i].userId()==$("#search").val())
{
self.selectedStation = ko.observable(this.stations()[i]); //Am i doing it right?
}
}
};
}
<!-- This is the code That handles the click event inside the textbox. its working -->
ko.bindingHandlers.executeOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).keypress(function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13) {
allBindings.executeOnEnter.call(viewModel);
return false;
}
return true;
});
}
};
ko.applyBindings(new StationViewModel());
</script>
Instead of
self.selectedStation = ko.observable(this.stations()[i]);
do
self.selectedStation(this.stations()[i]);
Hope this helps!
What I have done in the past to get the Enter key working is to wrap the <input> in a <form> tag
so from
<input type="text" data-bind="value:myValue"></input>
to
<form>
<input type="text" data-bind="value:myValue"></input>
</form>