简体   繁体   中英

Hovercard not displaying in knockoutJS with-binding

I got this hover card and I want to display it inside a sub-menu. It works int the header, but somehow the hover effect does not kick in when inside the sub-menu "with:$root.chosenMenu"-bindings.

This is my code:

HTML:

<div>
    <span class="previewCard">    
        <label class="hovercardName" data-bind="text: displayName"></label>
        <span class="hovercardDetails">     
            <span data-bind="text: miniBio"></span>.<br/>
            <a data-bind="attr: { href: webpage, title: webpage }, text: webpage">My blog</a>
        </span>
    </span> 
    <br/><br/>
    <div class="wysClear wysLeft buttonRow">
        <a href="#pid1" data-bind="click: function(){$root.chosenMenu('a');return false;}">Panel A</a>
        <a href="#pid2" data-bind="click: function(){$root.chosenMenu('b');return false;}">Panel B</a>
    </div>
</div>
<hr/>
<div data-bind="with: $root.chosenMenu">
    <div id="pid1" data-bind="visible: $root.chosenMenu() === 'a'">
        panel A: <br/><br/>
        <span class="previewCard">    
            <label class="hovercardName" data-bind="text: $root.displayName"></label>
            <span class="hovercardDetails">     
                <span data-bind="text: $root.miniBio"></span>.<br/>
                <a data-bind="attr: { href: $root.webpage, title: $root.webpage }, text: $root.webpage">My blog</a>
            </span>
        </span> 
    </div>
    <div id="pid2" data-bind="visible: $root.chosenMenu() === 'b'">
        panel B: <br/><br/>
        <span class="previewCard">    
            <label class="hovercardName" data-bind="text: $root.displayName"></label>
            <span class="hovercardDetails">     
                <span data-bind="text: $root.miniBio"></span>.<br/>
                <a data-bind="attr: { href: $root.webpage, title: $root.webpage }, text: $root.webpage">My blog</a>
            </span>
        </span> 
    </div>
</div>

Javascript:

viewModel = function(){
    var self = this;

    self.chosenMenu = ko.observable();
    self.displayName = ko.observable("Asle G");
    self.miniBio = ko.observable("Everywhere we go - there you are!");
    self.webpage = ko.observable("http://blog.a-changing.com")
};

ko.applyBindings( new viewModel() );

// hovercard
$(".previewCard").hover(function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeIn();
}, function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeOut();
});

CSS:

.previewCard {
    position:relative;            
}
.hovercardName {    
    font-weight:bold;    
    position:relative;    
    z-index:100; /*greater than details, to still appear in card*/
}
.hovercardDetails {            
    background:#fff ;    
    border:solid 1px #ddd;      
    position:absolute ;
    width:300px;
    left:-10px;
    top:-10px;
    z-index:50; /*less than name*/
    padding:2em 10px 10px; /*leave enough padding on top for the name*/   
    display:none;
}

Fiddle: http://jsfiddle.net/AsleG/jb6b61oh/

You have a classical jQuery problem: event handlers are only attached to elements that exist at the time.

This attaches the hover (mouseenter/mouseleave) event handlers to the only .previewCard there is:

$(".previewCard").hover(function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeIn();
}, function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeOut();
});

But if knockout creates a few more dynamically, they won't have any event handlers. So you have to delegate event handling to a node that's higher in the hierarchy.

$(document).on("mouseenter", ".previewCard", function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeIn();
}).on("mouseleave", ".previewCard", function() {
    $(this).find(".hovercardDetails").stop(true, true).fadeOut();
});

That's still not a very clean way to solve the problem. Why not create a hovercard binding handler and get rid entirely of the jQuery code in your view model?

ko.bindingHandlers.hovercard = {
    init: function (element) {
        $(element).hover(function() {
            $(this).find(".hovercardDetails").stop(true, true).fadeIn();
        }, function() {
            $(this).find(".hovercardDetails").stop(true, true).fadeOut();
        });
    }
};

Used as:

<span class="previewCard" data-bind="hovercard: true">

http://jsfiddle.net/jb6b61oh/6/

I think the problem was in your with binding. You're setting the context for one observable which at the time is undefined and therefore any descendant elements are removed. See knockout docs :

The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not``

That means that at the time jQuery runs, your elements aren't in the DOM so they are not bound to the hover event. So, if you change this:

data-bind="with: $root.chosenMenu">

to this:

<div data-bind="with: $root">

Your issue is fixed. That said, you really should use a custom binding handler for this.

 viewModel = function(){ var self = this; self.chosenMenu = ko.observable(); self.displayName = ko.observable("Asle G"); self.miniBio = ko.observable("Everywhere we go - there you are!"); self.webpage = ko.observable("http://blog.a-changing.com") }; ko.applyBindings( new viewModel() ); // hovercard $(".previewCard").hover(function() { $(this).find(".hovercardDetails").stop(true, true).fadeIn(); }, function() { $(this).find(".hovercardDetails").stop(true, true).fadeOut(); }); 
 .previewCard { position:relative; } .hovercardName { font-weight:bold; position:relative; z-index:100; /*greater than details, to still appear in card*/ } .hovercardDetails { background:#fff ; border:solid 1px #ddd; position:absolute ; width:300px; left:-10px; top:-10px; z-index:50; /*less than name*/ padding:2em 10px 10px; /*leave enough padding on top for the name*/ display:none; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> <div> <span class="previewCard"> <label class="hovercardName" data-bind="text: displayName"></label> <span class="hovercardDetails"> <span data-bind="text: miniBio"></span>.<br/> <a data-bind="attr: { href: webpage, title: webpage }, text: webpage">My blog</a> </span> </span> <br/><br/> <div class="wysClear wysLeft buttonRow"> <a href="#pid1" data-bind="click: function(){$root.chosenMenu('a');return false;}">Panel A</a> <a href="#pid2" data-bind="click: function(){$root.chosenMenu('b');return false;}">Panel B</a> </div> </div> <hr/> <div data-bind="with: $root"> <div id="pid1" data-bind="visible: chosenMenu() === 'a'"> panel A: <br/><br/> <span class="previewCard"> <label class="hovercardName" data-bind="text: displayName"></label> <span class="hovercardDetails"> <span data-bind="text: miniBio"></span>.<br/> <a data-bind="attr: { href: webpage, title: webpage }, text: webpage">My blog</a> </span> </span> </div> <div id="pid2" data-bind="visible: chosenMenu() === 'b'"> panel B: <br/><br/> <span class="previewCard"> <label class="hovercardName" data-bind="text: displayName"></label> <span class="hovercardDetails"> <span data-bind="text: miniBio"></span>.<br/> <a data-bind="attr: { href: webpage, title: webpage }, text: webpage">My blog</a> </span> </span> </div> </div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM