Custom binding and trouble with placeholder text being cleared.

77 views
Skip to first unread message

lrovertrevorl

unread,
Jun 14, 2016, 5:22:36 PM6/14/16
to KnockoutJS
A fellow developer wrote this update handler for a custom binding handler. 

The trouble we were having was that the placeholder text was not being preserved after the update on the binding handler was called. The underlying call to ko.bindingHandlers.text.update is wiping out the element.innerText which is used to store the placeholder value.

This binding handler is being used on a textarea element. The init method (not shown) is calling the ko.bindingHandlers.textInput.init. The update calls ko.bindingHandlers.text.update. textInput doesn't have an update method which can be called directly and I think the decision was made to call ko.bindingHandlers.text.update because it is a synchronous call and the code could then be certain that the element has the observable's value before calling the "afterRender" callback which was the primary intent of this custom handler. The element is also using an autosize control to grow the text area as needed based on the length of the text and the autosize has to be manually triggered to grow the very first time, which is the purpose of the callback. However, the autosize control is not to blame, the issue persists even when that is removed.

The code below WORKS. However, we would like to avoid having to execute the code near the middle of the update which is resetting the placeholder text. Does anyone have any recommendations on how this can/should be done differently to have minimal customization in the binding handler?

Code:
 update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
  {
    var settings;
    var newValueAccessor;
    var elementHasFocus;

    settings = ko.unwrap(valueAccessor()) || {};
    afterRender = settings.afterRender || null;

    // create a new value accessor to include the afterRender callback
    newValueAccessor = function ()
    {
      return settings.data;
    };

    // call the underlying textInput update to have KO do its normal processing but using 
    // the new value accessor with the added event handlers
    ko.bindingHandlers.text.update(element, newValueAccessor, allBindingsAccessor, viewModel, bindingContext);
      
    // Flag whether the curent element has focus
    elementHasFocus = $(element).is(":focus");

    // text.update wipes out placeholder text if it is present, even when the new value is an empty string. So, we want to replace it here, but only
    //  if the current element does not have focus. If the element DOES have focus, it will cause the user to be required to delete the placeholder text before
    // tabbing away in order to prevent the placeholder text from updating the underlying observable. There was also some inconsistent behavior which at times prevented
    //  the placeholder text from being deleted at all.
    if ((element.innerText.length == 0) &&
        (elementHasFocus != true))
    {
      ko.utils.setTextContent(element,
                              element.placeholder);
    }

    if (afterRender !== null)
    {
      afterRender(element,
                  settings.data);
    }
  }


lrovertrevorl

unread,
Jun 14, 2016, 5:25:38 PM6/14/16
to KnockoutJS
By the way, this was only happening with IE (confirmed in 11, didn't test with 9, 10 or Edge). 

Michael Best

unread,
Jun 15, 2016, 4:57:48 AM6/15/16
to KnockoutJS
What happens if you remove the "ko.bindingHandlers.text.update" call?

-- Michael

lrovertrevorl

unread,
Jun 15, 2016, 7:45:22 AM6/15/16
to KnockoutJS
If that call is removed, the "afterRender" callback will often fire too soon. So, the need for the call to ko.bindingHandlers.text.update is the result of not having a way to hook into an event or provide a callback for once Knockout has updated the element. 

Michael Best

unread,
Jun 15, 2016, 6:16:09 PM6/15/16
to KnockoutJS
Try replacing that call with 

ko.unwrap(settings.data);


-- Michael

Trevor Langrehr

unread,
Jun 15, 2016, 8:55:09 PM6/15/16
to knock...@googlegroups.com

Thanks but I'm not sure how that would ensure that the text area has been updated with the observable value before my afterRender callback is called?

--
You received this message because you are subscribed to a topic in the Google Groups "KnockoutJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/knockoutjs/KWTAZBNdY1E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to knockoutjs+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Michael Best

unread,
Jun 17, 2016, 9:19:12 PM6/17/16
to KnockoutJS
You've said that you're using the `textInput` binding as well, which will update the textarea's value. So it seems that all you need in the update code is to have the correct dependencies.

-- Michael

Trevor Langrehr

unread,
Jun 20, 2016, 8:27:27 AM6/20/16
to knock...@googlegroups.com
textInput will update the value, but there is no way to know WHEN it has done so (that I'm aware of). So, because the text update call is synchronous, we can then be sure that the element has been updated with the content before calling the afterRender callback.  The afterRender callback depends on the element containing all of the text that it will eventually have in order to function properly. The whole purpose of this custom binding is to be able to call afterRender only after we are certain the element has been updated with the observable's value. Otherwise, we wouldn't use this custom binding at all.

Thanks :)

--
You received this message because you are subscribed to a topic in the Google Groups "KnockoutJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/knockoutjs/KWTAZBNdY1E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to knockoutjs+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Trevor Langrehr
Co-construct -- Simplified selections, scheduling, & client communication
for builders of custom projects. Guaranteed.
800-213-3392 ext. 111
tre...@co-construct.com
www.co-construct.com

Michael Best

unread,
Jun 21, 2016, 5:24:14 PM6/21/16
to KnockoutJS
I understand what you're saying, and perhaps I simply don't have the whole picture. Can you show me how you are calling the textInput binding?

Thanks,
Michael

lrovertrevorl

unread,
Jun 21, 2016, 5:33:27 PM6/21/16
to KnockoutJS
Sure. Here is the init side of the custom binding. I've already pasted the update side of it and this is the rest of it:

ko.bindingHandlers.textInputWithAfterRenderCallback =
{
  init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
  {
    var settings;
    var newValueAccessor;

    settings = ko.unwrap(valueAccessor()) || {};

    newValueAccessor = function ()
    {
      return settings.data;
    };

    return ko.bindingHandlers.textInput.init(element, newValueAccessor, allBindingsAccessor, viewModel, bindingContext);
  },

Thanks

Michael Best

unread,
Jun 24, 2016, 5:52:21 AM6/24/16
to KnockoutJS
Thanks. This looks like what I was expecting. Can you just try my suggestion and see if it works?

-- Michael
Reply all
Reply to author
Forward
0 new messages