I'm trying to use KnockoutJS with jQuery UI. I have an input element with a datepicker attached. I'm currently running knockout.debug.1.2.1.js and it seems that the change event is never being caught by Knockout. The element looks like this:
<input type="text" class="date" data-bind="value: RedemptionExpiration"/>
I've even tried changing the valueUpdate event type but to no avail. It seems like Chrome causes a focus event just before it changes the value, but IE doesn't.
Is there some Knockout method that "rebinds all the bindings"? I technically only need the value changed before I send it back to the server. So I could live with that kind of workaround.
I think the problem's the datepicker's fault, but I can't figure out how to fix this.
Any ideas?
I think that for the jQuery UI datepicker it is preferable to use a custom binding that will read/write with Date objects using the APIs provided by the datepicker.
The binding might look like (from my answer here):
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
$el = $(element);
$el.datepicker(options);
//handle the field changing by registering datepicker's changeDate event
ko.utils.registerEventHandler(element, "changeDate", function () {
var observable = valueAccessor();
observable($el.datepicker("getDate"));
});
//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);
}
}
};
You would use it like:
<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />
Sample in jsFiddle here: http://jsfiddle.net/rniemeyer/NAgNV/
Here is a version of RP Niemeyer's answer that will work with the knockout validation scripts found here: http://github.com/ericmbarnard/Knockout-Validation
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
observable($(element).val());
if (observable.isValid()) {
observable($(element).datepicker("getDate"));
$(element).blur();
}
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datepicker("destroy");
});
ko.bindingHandlers.validationCore.init(element, valueAccessor, allBindingsAccessor);
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') == 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
current = $(element).datepicker("getDate");
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
Changes are to the change event handler to pass the entered value and not the date to the validation scripts first, then only setting the date to the observable if it is valid. I also added the validationCore.init that is needed for custom bindings discussed here:
http://github.com/ericmbarnard/Knockout-Validation/issues/69
I also added rpenrose's suggestion for a blur on change to eliminate some pesky datepicker scenarios getting in the way of things.
I've used a different approach. Since knockout.js doesn't seem to fire the event on change, I've forced the datepicker to call change() for its input once closed.
$(".date").datepicker({
onClose: function() {
$(this).change(); // Forces re-validation
}
});
Although all of these answers saved me a lot of work, none of them fully worked for me. After selecting a date, the binded value would not update. I could only get it to update when changing the date value using the keyboard then clicking out of the input box. I fixed this by augmenting RP Niemeyer's code with syb's code to get:
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
var funcOnSelectdate = function () {
var observable = valueAccessor();
observable($(element).datepicker("getDate"));
}
options.onSelect = funcOnSelectdate;
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", funcOnSelectdate);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datepicker("destroy");
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (typeof(value) === "string") { // JSON string from server
value = value.split("T")[0]; // Removes time
}
var current = $(element).datepicker("getDate");
if (value - current !== 0) {
var parsedDate = $.datepicker.parseDate('yy-mm-dd', value);
$(element).datepicker("setDate", parsedDate);
}
}
};
I suspect putting the observable($(element).datepicker("getDate")); statement in its own function and registering that with options.onSelect did the trick?
Thanks for this article I found it very useful.
If you want the DatePicker to behave exactly like the JQuery UI default behaviour I recommend adding a blur on the element in the change event handler:
i.e.
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
observable($(element).datepicker("getDate"));
$(element).blur();
});
I solved this problem by changing the order of my included script files:
<script src="#Url.Content("~/Scripts/jquery-ui-1.10.2.custom.js")"></script>
<script src="#Url.Content("~/Scripts/knockout-2.2.1.js")"></script>
Same as RP Niemeyer, but better support of WCF DateTime, Timezones and Using the DatePicker onSelect JQuery property.
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
var funcOnSelectdate = function () {
var observable = valueAccessor();
var d = $(element).datepicker("getDate");
var timeInTicks = d.getTime() + (-1 * (d.getTimezoneOffset() * 60 * 1000));
observable("/Date(" + timeInTicks + ")/");
}
options.onSelect = funcOnSelectdate;
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", funcOnSelectdate);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datepicker("destroy");
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') == 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
current = $(element).datepicker("getDate");
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
Enjoy :)
http://jsfiddle.net/yechezkelbr/nUdYH/
I think it can be done much easier: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />
So you do not need manual change handling in init function.
But in this case, your 'myDate' variable will get only visible value, not Date object.
Alternatively, you can specify this in binding:
Update:
function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
current = $(element).datepicker("getDate");
if (typeof value === "string") {
var dateValue = new Date(value);
if (dateValue - current !== 0)
$(element).datepicker("setDate", dateValue);
}
}
Based on Ryan's solution, myDate returns the standard date string, which is not the ideal one in my case. I used date.js to parse the value so it will always return the date format you want. Take a look at this example fiddle Example.
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
current = $(element).datepicker("getDate");
var d = Date.parse(value);
if (value - current !== 0) {
$(element).datepicker("setDate", d.toString("MM/dd/yyyy"));
}
}
I needed to repeatedly update my data from the server came across this but didn't quite finish the job for my needs sharing below(my date format /Date(1224043200000)/):
//Object Model
function Document(data) {
if (String(data.RedemptionExpiration).indexOf('/Date(') == 0) {
var newDate = new Date(parseInt(data.BDate.replace(/\/Date\((.*?)\)\//gi, "$1")));
data.RedemptionExpiration = (newDate.getMonth()+1) +
"/" + newDate.getDate() +
"/" + newDate.getFullYear();
}
this.RedemptionExpiration = ko.observable(data.RedemptionExpiration);
}
//View Model
function DocumentViewModel(){
///additional code removed
self.afterRenderLogic = function (elements) {
$("#documentsContainer .datepicker").each(function () {
$(this).datepicker();
});
};
}
After the model is formatted correctly for the output I added a template with the documentation knockoutjs:
<div id="documentsContainer">
<div data-bind="template: { name: 'document-template', foreach: documents, afterRender: afterRenderLogic }, visible: documents().length > 0"></div>
</div>
//Inline template
<script type="text/html" id="document-template">
<input data-bind="value: RedemptionExpiration" class="datepicker" />
</script>
Few people asked for dynamic datepicker options. In my case I needed a dynamic date range - so the first input defines the min value of the second and the second sets the max value for the first. I solved it by extending the RP Niemeyer's handler. So to his original:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
$el = $(element);
$el.datepicker(options);
//handle the field changing by registering datepicker's changeDate event
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($el.datepicker("getDate"));
});
//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);
}
}
};
I've added two more handlers corresponding to the options I wanted to modify:
ko.bindingHandlers.minDate = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
current = $(element).datepicker("option", "minDate", value);
}
};
ko.bindingHandlers.maxDate = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
current = $(element).datepicker("option", "maxDate", value);
}
};
And used them like that in my template:
<input data-bind="datepicker: selectedLowerValue, datepickerOptions: { minDate: minValue()}, maxDate: selectedUpperValue" />
<input data-bind="datepicker: selectedUpperValue, datepickerOptions: { maxDate: maxValue()}, minDate: selectedLowerValue" />
Using custom bindings provided in previous answers is not always possible. Calling $(element).datepicker(...) takes some considerable time, and if you have, for example, a few dozens, or even hundreds of elements to call this method with, you have to do it "lazy", i.e. on demand.
For example, the view model may be initialized, the inputs being inserted into the DOM, but the corresponding datepickers will only be initialized when a user clicks them.
So, here is my solution:
Add a custom binding that allows to attach arbitrary data to a node:
KO.bindingHandlers.boundData = {
init: function(element, __, allBindings) {
element.boundData = allBindings.get('boundData');
}
};
Use the binding to attcah the observable used for the input's value:
<input type='text' class='my-date-input'
data-bind='textInput: myObservable, boundData: myObservable' />
And finally, when initializing the datepicker, use it's onSelect option:
$('.my-date-input').datepicker({
onSelect: function(dateText) {
this.myObservable(dateText);
}
//Other options
});
This way, every time a user changes the date with the datepicker, the corresponding Knockout observable is also updated.
Related
The radio option in my application return three values allday, morning and halfday. How can I tie my jquery datepicker as such that it is enabled only when allday is selected.
var viewModel = function () {
this.holidayType = ko.observable();
this.allday = ko.computed(
{
read: function () {
return this.holidayType() == "allday";
},
write: function (value) {
if (value)
this.holidayType("allday");
}
}, this);
this.morning = ko.computed(
{
read: function () {
return this.holidayType() == "morning";
},
write: function (value) {
if (value)
this.holidayType("morning");
}
}, this);
this.afternoon = ko.computed(
{
read: function () {
return this.holidayType() == "afternoon";
},
write: function (value) {
if (value)
this.holidayType("afternoon");
}
}, this);
};
ko.applyBindings(new viewModel());
$(function () {
$("#e1").daterangepicker({
datepickerOptions: {
minDate: 0
}
});
});
First, I added an observable named dateRangeButton to your viewmodel. This observable will hold a jQuery selector to the dynamically generated <button> that is created by the date range picker plug-in:
this.dateRangeButton = ko.observable();
Then I added a function to the viewmodel called enableDisableDateRange. When called, this function will add or remove the disabled attribute to the date range picker button based on whether or not the holidayType equals "allday":
this.enableDisableDateRange = function(context) {
context.dateRangeButton().prop('disabled', context.holidayType() != 'allday');
}
How do we get the selector for the date range picker button? I created a custom binding which will apply the date range picker to the element and populate the dateRangeButton observable in the viewmodel:
ko.bindingHandlers.datePicker = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// apply the date range picker
$(element).daterangepicker({
datepickerOptions: {
minDate: 0
}
});
// tell the viewmodel where the date range picker button is
bindingContext.$data.dateRangeButton($(element).next('button'));
// enable or disable the button
bindingContext.$data.enableDisableDateRange(bindingContext.$data);
}
};
I added this binding to the <input> element that will contain the date range picker:
<input id="e1" name="e1" data-bind="datePicker: ''">
Finally, I added a subscribe to the holidayType observable which will call the enableDisableDateRange function any time holidayType changes:
this.holidayType.subscribe(function() {
this.enableDisableDateRange(this);
}, this);
Here is a fiddle: http://jsfiddle.net/6cfb1dLg/
NOTE: This all relies on how the date range picker arranges it's UI. Currently, it creates a <button> element directly after the <input> element it is applied to. If this changes, then the datePicker custom binding I wrote would have to be modified as well. The custom binding also assumes the presence of an observable named dateRangeButton and a function named enableDisableDateRange exist within the viewmodel.
In this fiddle inside the view appointments tab,there is add timing button.When I click on the button then a new row gets added.Now when I click on the calender icon then a pop comes up which shows the current hour and minutes.But I want it to show 00:00. Can any body please tell me how to do?
I have tried to modify the setDate method inside the bootstrap date time picker source code
setLocalDate: function (localDate) {
if (!localDate)
this.setValue(null);
else
this.setValue(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate(), localDate.getHours(), localDate.getMinutes(), localDate.getSeconds(), localDate.getMilliseconds()))
},
to
setLocalDate: function (localDate) {
if (!localDate)
this.setValue(null);
else
this.setValue(00, 00, 00, 00,00, 00, 00))
},
but it did not work.Please anybody tell me what else do I have to change
As #RobG already said, you can simply set the value in the initialization routine
ko.bindingHandlers.datepicker1 = {
init: function (element, valueAccessor, allBindingsAccessor) {
$(function () {
$(element).parent().datetimepicker({
pickDate: false,
pickSeconds: false,
minuteStep: 10
});
$(element).parent().datetimepicker('setValue', '00:00');
});
// ...
See updated JSFiddle
Update:
If you want 00:00 as default value only, you must pick the value from the valueAccessor first and set 00:00, if the value is undefined
init: function (element, valueAccessor, allBindingsAccessor) {
$(function () {
$(element).parent().datetimepicker({
pickDate: false,
pickSeconds: false,
minuteStep: 10
});
var accessor = valueAccessor();
var val = ko.unwrap(accessor) || '00:00';
$(element).parent().datetimepicker('setValue', val);
});
// ...
See JSFiddle
Update:
Unfortunately, I cannot find any documentation about version 2.2.0.
Testing, if the accessor is a function, seems to work
var val = valueAccessor();
if (typeof(val) == 'function')
val = val();
$(element).parent().datetimepicker('setValue', val || '00:00');
Updated JSFiddle
I have the TinyMCE WYSiWYG Editor presenting text depending on a selected object, but experience problem with the binding.
The first "instanciation" seems to work, but when choosing a new text from the drop down list of available text the editor goes blank and Firebug console tells me:
TypeError: D.hasChildNodes is not a function
...ute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r...
and
NS_ERROR_UNEXPECTED: Unexpected error
.../,"$1"));return false}});if(!u.getParam("accessibility_focus")){g.add(i.add(k,"a...
I have tried to recreate my code here: http://jsfiddle.net/xc4sz/1/
It´s not 100% but at least it does´t work. ;)
If I instead of clicking directly from text 1 to text 2 go via the "Choose option" the text is presented properly.
I guess it has to do with the "init" section below:
ko.bindingHandlers.tinymce = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().tinymceOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
var el = $(element)
//handle edits made in the editor. Updates after an undo point is reached.
options.setup = function (ed) {
console.log(ed)
ed.onChange.add(function (ed, l) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(l.content);
}
});
};
//handle destroying an editor
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
//$(element).tinymce(options);
setTimeout(function () { $(element).tinymce(options); }, 0);
el.html(value);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var $element = $(element),
value = ko.utils.unwrapObservable(valueAccessor()),
id = $element.attr('id');
//handle programmatic updates to the observable
// also makes sure it doesn't update it if it's the same.
// otherwise, it will reload the instance, causing the cursor to jump.
if (id !== undefined) {
var tinymceInstance = tinyMCE.get(id);
if (!tinymceInstance)
return;
var content = tinymceInstance.getContent({ format: 'raw' });
if (content !== value) {
$element.val(value);
//this should be more proper but ctr+c, ctr+v is broken, above need fixing
//tinymceInstance.setContent(value,{ format: 'raw' })
}
}
}
};
Depending on the versions of TinyMCE and jQuery that you are dependent on, you might like to try the custom binding I've recently rolled myself.
It's available on GitHub and NuGet
I found the issue. What happened was this:
you select Textbatch #1 and make some changes
you switch to Textbatch #2
the binding changes correctly from Textbatch #1 to #2
THEN the ed.onChange.add event handler kicks in and overwrites the content of the previous Textbatch #1 with that of the new Textbatch #2
Take a look at this updated fiddle (remove /show/light from the URL to get back to the editor). I had to inline select2.js, because Github does not allow files it hosts to be included remotely, causing your fiddle to fail.
The important part is in ko.utils.domNodeDisposal.addDisposeCallback:
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).tinymce().onChange.remove(changeHandler);
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
I do not know why the removal of the editor is minimally delayed with a 0-second timeout, but there is probably a good reason for that. So all we do is remove the 'change' handler, so that the old editor cannot update the bound valueAccessor in your viewmodel anymore.
EDIT: I just noticed that I fixed your fiddle, but not necessarily your original exception… here is to hoping that the two were related.
I've set up a jsFiddle to demonstrate my problem.
What I'm doing:
I'm using Knockout with a jQuery Datepicker as shown in this question.
My model contains an observableArray which contains objects with date properties. Knockout renders an <input type="text"> for every object, and binds the value to the date using RP Niemeyer's datepicker binding.
What's going wrong:
If the user has a datepicker open when knockout's model changes (e.g., adding a new item), the datepicker stops working correctly. From what I can tell, the last <input type="text"> created while the datepicker is open becomes the new datepicker target. I need to fix the binding so that this does not happen.
Sample HTML:
<ul data-bind="foreach: dateBoxes">
<li>
<span data-bind="text: index"></span>
<input type="text" data-bind="datepicker: date"/>
</li>
</ul>
Sample Javascript:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($(element).datepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//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 = $(element).datepicker("getDate");
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
var model;
var id = 0;
function DateBox(d) {
var self = this;
self.date = ko.observable(d);
self.index = ko.observable(id++);
}
function Model() {
var self = this;
self.dateBoxes = ko.observableArray([]);
self.changeCount = function() {
if (self.dateBoxes().length < 2) {
self.dateBoxes.push(new DateBox(new Date()));
} else if (self.dateBoxes().length > 1) {
self.dateBoxes.splice(0,1);
}
}
self.tick = function() {
self.changeCount();
setTimeout(function() {
self.tick();
}, 5000);
}
self.tick();
}
model = new Model();
ko.applyBindings(model);
Note: This answer isn't complete. However, as it's too large for a comment and probably helpful (towards a solution) anyhow I've taken the liberty and posted it as an answer. Anyone that can complete this answer is invited to do so through an edit or by spinning off into another better answer.
The thing that seems to be spoiling the party (taken from the documentation, emphasis mine):
This will be called once when the binding is first applied to an element, and again whenever the associated observable changes value.
If I hackisly prevent the update from doing its datepicker calls in the first update the datepicker won't break anymore (other issues do arise though). For the init I've added this line at the end:
element.isFirstRun = true;
Then the update method will do the following just before the datepicker calls:
if (element.isFirstRun) {
$(element).val(value);
element.IsFirstRun = false;
return;
}
See this updated fiddle for the results, which are:
the mentioned scenario now updates the correct textbox (a good thing);
the initial value is now a more verbose DateTime string and stays that way after updates (kinda not nice);
Hopefully this will help towards a more complete solution.
I am using Knockout to display some tags for an array of images. each tag will have a popup that gives more information about the tag. The elements is registered with popup class the following way:
function RegisterCharacterPopups() {
$('[data-characterid]').each(function() {
var cId = $(this).data('characterid');
var placement = $(this).data('position');
if (placement == null || placement == undefined) {
placement = "top-center";
}
$(this).PopUp({
url: "/Ajax/CharacterPop/" + cId,
position: placement,
});
});
}
And i have added this to my constructor of the view model that contains the tags:
// Hook on to update of Tags
ko.computed(() => {
var test = this.Tags();
RegisterCharacterPopups();
console.log("Tags updated");
});
I can see the methods is executed, but the tags do not register with the popup. if it force the Tags to update again, will it work though!
I think the problem is that this method in executed the first time, before the elements are in the html.
How can I fix this, so it will wait for the elements to be inserted before it executes it?
Solution: a custom binding
ko.bindingHandlers['tagpop'] = {
init: function (element, valueAccessor, allBindings, vm, context) {
var data = valueAccessor();
$(element).PopUp({
url: "/Ajax/CharacterPop/" + data.id,
position: data.placement,
});
},
update: function (element, valueAccessor) {
}
};