1

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).

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • Here is an example of one. http://jsfiddle.net/3uhNP/1/ – Moonhead Oct 22 '14 at 05:50
  • 1
    @KRUKUSA thank you. I know know how to achieve this with listening for events, but it breaks MVVM paradigm. – Salvador Dali Oct 22 '14 at 05:52
  • 1
    You're better off not trying to restrict how many characters are typed but instead enforce the length on submission. – Jeff Mercado Oct 22 '14 at 05:56
  • @JeffMercado I also think so, but sometimes these things are not in my power. – Salvador Dali Oct 22 '14 at 06:04
  • May be knockout extenders will help you - http://knockoutjs.com/documentation/extenders.html – TSV Oct 22 '14 at 06:11
  • Check the answer on this thread: http://stackoverflow.com/questions/12982587/knockout-need-help-to-build-a-textarea-character-counter – JunM Oct 22 '14 at 06:48
  • And I think this is what you are looking for (credits to the answer on that thread): http://jsfiddle.net/wpbWY/5/ – JunM Oct 22 '14 at 06:49
  • @JunM Good find. After some cleanup, that question turned out a duplicate IMO. – Jeroen Oct 22 '14 at 07:16
  • @JunM thanks, the problem is that this completely disables the ability to change the message inside the textarea. I would like only to disable adding new characters. – Salvador Dali Oct 22 '14 at 07:57

2 Answers2

5

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

Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
1

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>

enter image description here

enter image description here

enter image description here

Anders
  • 17,306
  • 10
  • 76
  • 144