I am trying to do the following functionality. Show a number of characters left in the textarea and disallow entering anything else if you already exceeded the maximum number.
I am confused with how to achieve this. Now I have something like this:
<textarea data-bind="textInput: message"></textarea>
<p>Characters left : <span data-bind="text: charLeft"></span></p>
function Vm_app() {
var self = this;
this.message = ko.observable('');
this.charLeft = ko.pureComputed(function(){
return 128 - self.message().length;
});
}
ko.applyBindings(new Vm_app());
Any idea how should I proceed?
P.S. I know how to achieve the task with listening for events, but I do not want to break MVVM paradigm.
P.S.2 the linked answer does not allow to continue adding the text, once you disabled it. I want only to disallow writing new characters (a person will be able to click delete, backspace).
As I stated earlier, you'd be better off not trying to restrict typing beyond a certain length. There are many things to consider when you want to intercept these sorts of things, many corner cases you would have to work around to have it work naturally. And more importantly, it would be a much better experience for the user to be able to type out all that they want to type with no restrictions, at least until they try to submit. Then that's when you can enforce that it needs to be a certain length.
With that said, there are some things you can do to make this work close enough. There are many approaches you can take to enforce this. Probably the simplest approach would be to create a delegating observable which can intercept writes to your observable. You could then check the length if appropriate and set the value, or ignore it. You could keep this all self-contained in an extender.
ko.extenders.maxlength = function (target, maxlength) {
var view = ko.dependentObservable({
read: target,
write: function (value) {
if (value.length <= maxlength) {
target(value);
} else {
view.notifySubscribers(target()); // "refresh" the view
}
}
});
target.view = view;
target.maxlength = maxlength;
return target;
};
Then to use it:
this.message = ko.observable('').extend({ maxlength: 128 });
Then just bind to the view:
<textarea data-bind="textInput: message.view"></textarea>
Just note that when "refreshing", the cursor will always be moved to the end. Just the nature of setting the values. If you want to preserve the cursor position, you'll have to reset that as well.
fiddle
I solved it with a reusable ViewModel
define(["knockout"], function(ko) {
var ctor = function(limit, limitWarning) {
this.text = ko.observable("");
this.isEditing = ko.observable();
this.limitedText = ko.computed({
write: this.setText,
read: this.text
}, this);
this.limit = limit;
this.limitWarning = limitWarning;
this.charactersLeft = ko.computed(this.getCharactersLeft, this);
this.charactersLeftWarning = ko.computed(this.getCharactersLeftWarning, this);
this.limitReached = ko.computed(this.getLimitReached, this);
};
ctor.prototype = {
setText: function(text) {
if (text.length > this.limit) {
text = text.substring(0, this.limit);
}
this.text(text);
},
getCharactersLeft: function() {
return this.limit - this.text().length;
},
getCharactersLeftWarning: function() {
return this.getCharactersLeft() <= (this.limit * this.limitWarning);
},
getLimitReached: function() {
return this.text().length >= this.limit;
},
writing: function(model, e) {
return e.keyCode < 65 || !this.getLimitReached();
}
};
return ctor;
});
View
<textarea data-bind="text: limitedText,valueUpdate: 'afterkeydown', event: { keydown: writing }" class="form-control" rows="5"></textarea>
<div data-bind="css: { 'limit-warning': charactersLeftWarning, 'limit-reached': limitReached }">
Characters left <span data-bind="text: charactersLeft"></span>
</div>