Question

Using with in KO causes elements to only render when the context of the data-bind is available. If I have script that runs on page-load and it's in charge of styling HTML elements, I get null if I call document.getElementById on an element whose data-bind uses with. Here's some pseudo code:

HTML:

<section data-bind="with: selectedView" id="sec">
    <span data-bind="text: title" id="span"></span>
</section>

JS that runs onload:

var PseudoViewModel = { ... };
ko.applyBindings(PseudoViewModel);

var x = document.getElementById("span"); // this is null

What's the best-practice for dynamically styling my elements? Thanks.

Was it helpful?

Solution

What you're doing should work provided you're executing that code when the element exists (example).

In your example, #sec is the element with the with binding. with will omit descendant elements of #sec if the value of its with binding (selectedView in your example) is null or undefined. So #sec will always be there, just not the span within it. (Example) (Turns out that was an error in the code in the question, you meant to be targeting the span.)

What's the best-practice for dynamically styling my elements?

Best practice would be to use CSS, not JavaScript code, to style elements. Possibly in combination with the css binding, but not necessarily.

Another option is to do a custom binding which does the styling as part of the KO stuff. Custom bindings are very common in KO stuff. Since KO knows it's not rendering the element, it doesn't run the custom binding against it, and there's no error.

Or of course, if you do have elements that may or may not be present because you've used with or if, test they exist before using them:

var x = document.getElementById("span");
if (x) {
    // Do stuff here
}

But again, recommendations in order:

  1. Use CSS, not JavaScript, to style elements (possibly in conjunction with the css binding to apply a class to it); or

  2. Use a custom binding if there has to be per-element code run (the great thing here is you define them once and reuse them); or

  3. Use a guard (if (x) { ... })


Here's an example of a custom binding that makes the element's color red if the value of the binding is more than 12 characters long, blue otherwise:

ko.bindingHandlers.colorByLength = {
    update: function(element, valueAccessor/*, allBindings, viewModel, bindingContext*/) {
        var val = ko.unwrap(valueAccessor());
        element.style.color = (val && val.length > 12) ? "red" : "blue";
    }
};      

Complete Example: Live Copy

<!DOCTYPE html>
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<meta charset=utf-8 />
<title>Example Custom Binding</title>
</head>
<body>
<section data-bind="with: selectedView" id="sec">
    <span data-bind="text: title, colorByLength: title" id="span"></span>
</section>
<section data-bind="with: anotherView" id="sec">
    <span data-bind="text: title, colorByLength: title" id="span"></span>
</section>
<section data-bind="with: nullView" id="sec">
    <span data-bind="text: title, colorByLength: title" id="span"></span>
</section>
  <script>
    (function() {
      "use strict";

      // Custom binding
      ko.bindingHandlers.colorByLength = {
        update: function(element, valueAccessor/*, allBindings, viewModel, bindingContext*/) {
          var val = ko.unwrap(valueAccessor());
          element.style.color = (val && val.length > 12) ? "red" : "blue";
        }
      };      

      var PseudoViewModel = {
        // This one will be red
        selectedView: {
          title: "This is the title"
        },

        // This one will be blue
        anotherView: {
          title: "Short title"
        },

        // And of course, this one isn't rendered at all
        nullView: null
      };
      ko.applyBindings(PseudoViewModel);

    })();
  </script>
</body>
</html>

OTHER TIPS

If indeed the desired behavior is for the element to not render when the data binding context isn't available, then you'll have to guard against that in your JavaScript:

function doThatThingOnPageLoad() {
  var elem = document.getElementById("sec");
  if (elem == null) return;

  continueDoingThatThingWith(elem);
}

Your JavaScript should just treat this element as optional, since it might just not be there.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top