24

I am working on a single page applications that has a bunch of hidden divs, binded (or is it bound?) to KnockoutJS with visible:. When page loads, they all momentarily flash on screen. I have tried moving my JS into the <head></head>, but that had no effect, so loading JS at the bottom of the page is not what's causing it.

Unfortunately, visible: binding does not propagate to CSS display attribute, so I can not use display: none; on page load, or visible: will not work at all. Unless... I load the page with display: none; and then change it the very first time I show the div to a user.

But is there a more elegant way to achieve this?

solefald
  • 1,739
  • 1
  • 15
  • 29
  • possible duplicate of [What is the best way to hide the screen while knockout js bindings are being built?](http://stackoverflow.com/questions/9532595/what-is-the-best-way-to-hide-the-screen-while-knockout-js-bindings-are-being-bui) – Rune Vejen Petersen Dec 28 '14 at 18:45

3 Answers3

57

Wth KnockoutJS, I work around this problem by defining a CSS class called hidden with display:none, then I'll add this class and binding to the flashing container:

class="hidden" data-bind="css: { hidden: false }"
Tuan
  • 5,382
  • 1
  • 22
  • 17
  • What they said ^. Thanks for this brilliant answer! – Josh Lowry Sep 20 '13 at 02:34
  • 5
    Nifty indeed. I made one small adjustment to this and replaced "hidden" with "ko-hidden", just for more context and to avoid stepping on an existing "hidden" class: `class="ko-hidden" data-bind="css: { 'ko-hidden': false }"` – Cᴏʀʏ Nov 12 '13 at 19:22
  • 5
    It is worth noting bootstrap 3 has a `.hidden` class. http://getbootstrap.com/css/#helper-classes-show-hide – mg1075 Nov 20 '13 at 12:53
  • I thought of doing this, but couldn't grasp why the `visible` binding didn't seem to do exactly this. As the question says, the `visible` binding doesn't seem to take precedent over the stylesheet rules, which is rather baffling. At least this works. – crush Jul 21 '15 at 15:24
  • 1
    @crush Because the binding doesn't take effect until you call applyBindings. – Tuan Jul 21 '15 at 17:27
  • 1
    @Tuan That's not what I'm talking about at all. In the case where you have applied a `display: none` via a CSS class, the `visible` binding won't work when the observable to which it is bound is true. When the `visible` binding's bound value evaluates to true, it simply sets `element.style.display = ''` which effectively removes `style="display: none"` from the element. Then, the underlying CSS class, which has `display: none` is applied - thus the element never shows. Therefore, the only work-arounds are to use `style="display: none"`, instead of classes, or a special presentational class. – crush Jul 21 '15 at 21:46
  • The only way that the `visible` binding could be made to work with css classes in the instance where you want the class to start as `display: none`, would be to have an optional binding argument that specifies what `display` style to use when the `visible` binding evaluates to true since it wouldn't be specified in the css class (it's specified as `none` there). Something like `visible: isVisible(), visibleDisplayType: 'block'`. Of course, this doesn't exist, so it would need to be added. – crush Jul 21 '15 at 21:48
5

I solved this by putting my "flashy" content in a script template and use ko's virtual elements to load the template when the variable is set by another virtual element.

For example:

<!-- ko if: myVariable -->
<!-- ko template: { name: 'myTemplate' } --><!-- /ko -->
<script type="text/html" id="myTemplate">
    <ul data-bind="foreach: blah...">
        <li></li>
    </ul>
</script>
<!-- /ko -->

So when myVariable is set, the content of the script container will be put in place of the template virtual element. With this method you dont see any flashing content :-)

Milaan
  • 108
  • 1
  • 5
  • 1
    this has the bonus of not putting display:none on everything (when someone else removes data-bind they may not remember to remove display:none – actual_kangaroo Feb 19 '14 at 23:22
0

I ended up writing a custom binding to use instead of the default visible.

function isHidden(el) {
    var style;

    style = window.getComputedStyle(el);

    return (style.display === 'none')
}

ko.bindingHandlers['cssVisible'] = {
    'update': function (element, valueAccessor) {
        var value,
            isCurrentlyVisible;

        value = ko.utils.unwrapObservable(valueAccessor());
        isCurrentlyVisible = !isHidden(element);

        if (value && !isCurrentlyVisible) {
            ko.utils.toggleDomNodeCssClass(element, 'ko-visible', true);
        }
        else if ((!value) && isCurrentlyVisible) {
            ko.utils.toggleDomNodeCssClass(element, 'ko-visible', false);
        }
    }
}

Then some CSS to handle visibility

[data-bind*="cssVisible"]:not(.ko-visible) {
    display: none;
}

Usage is the same as the visible binding

<div data-bind="cssVisible: true"></div>
blues_driven
  • 331
  • 2
  • 7