Leon Gaban
Leon Gaban

Reputation: 39028

Why does my $timeout.cancel work here, but not here?

I'm having a strange problem in my Angular app.

In the sidebar I have some tags with code to show a hover and hide a hover. It requires the user sit over the tag for 2 secs before the hover is shown, if the user leaves before 2 secs, the $timeout.cancel will wire in the mouseleave function and kill it. So it works great.

However in another directive this does not happen, I basically/90% have the exact same code, however the $timeout.cancel is completely ignored, so as soon as you hover over a tag in this other directive, after 2 secs the hover will fire no matter what.

Working code (tagsPanel Directive)

function hoverTag(tag) {
    vs.hoverTimeout = $timeout(function() {
        ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
            console.log('getTagDataSilm: ', data.data.ticker_tag);
            tag.tickers = data.data.ticker_tag.tickers;
            tag.tagsHoverDisplay = true; 
        });
    }, 2000);
}

function leaveTag(tag) {
    $timeout.cancel(vs.hoverTimeout);
    tag.tagsHoverDisplay = false;
}

Working markup

<li ng-repeat="t in tags" ng-class="{'selected': t.selected}">
    <div class="tag-container-container">
        <div class="tag-container"
             ng-class="{'width-auto': widthAuto}"
             ng-mouseleave="leaveTag(t)">
            <div class="tag"
                 ng-click="selectTag(t)"
                 ng-mouseover="hoverTag(t)"
                 ng-class="{'positive': t.direction == 'positive',
                            'negative': t.direction == 'negative',
                            ''        : t.direction == 'stagnant'}">
                            {{t.term}}
            </div>
            <tags-hover tag="t"></tags-hover>
        </div>
    </div>
</li>

NOT working code (viewHeader Directive)

function hoverViewTag(ticker, tag) {
    console.log('hoverViewTag ', tag);
    vs.hoverViewTimeout = $timeout(function() {

        ApiFactory.getTagData(ticker, tag.term_id).then(function(data) {
            var timeSpan = TimeSpanFactory.getTimeSpan();
            var period   = createSortString(timeSpan.when);
            var singleTagArray = [];
            singleTagArray.push(data.data.ticker_tag);
            var tagDetails = TagFactory.renderDirections(singleTagArray, null, period);

            tag.tagsHoverDisplay         = true;
            tag.favorite                 = tagDetails[0].favorite;
            tag.quantity                 = tagDetails[0].quantity;
            tag.tickers                  = tagDetails[0].tickers;
            tag.tweet_percentage         = tagDetails[0].tweet_percentage;
            tag.momentum_twitter_preview = tagDetails[0].momentum_twitter_preview;
        });
    }, 2000);
}

function leaveViewTag(tag) {
    // Cancel damn it!
    $timeout.cancel(vs.hoverViewTimeout);
    tag.tagsHoverDisplay = false;
}

NOT working markup (timeout will fire despite mouseleave function)

<ul class="view-tags-ul" ng-repeat="obj in vh.viewTickerTags">
    <li ng-repeat="t in obj.tags track by $index">
        <div class="tag-container"
             ng-mouseleave="vh.leaveViewTag(t)">
            <div class="tag"
                 ng-click="vh.removeTag(obj.ticker, t)"
                 ng-mouseover="vh.hoverViewTag(obj.ticker, t)"
                 ng-class="{'positive': t.direction == 'positive',
                            'negative': t.direction == 'negative',
                            ''        : t.direction == 'stagnant'}">{{t.term}}
                <div class="close-x-sml"></div>
            </div>
            <tags-hover tag="t"></tags-hover>
        </div>
    </li>
</ul>

Extras

This is what tags looks like in the tagsPanel scope:

enter image description here

This is what the vh.viewTickerTags object looks like:

enter image description here


CSS for .tag

.tag {
    text-align: left;
    border: 1px solid $gray_light;
    background: $gray_bg;

    &:hover {
        -webkit-transition : border 2000ms ease-out; 
        -moz-transition : border 2000ms ease-out;
        -o-transition : border 2000ms ease-out;
        border: 1px solid $gray4 !important;
        background: #fff !important;
    }

    &.positive,
    &.negative { border: 1px solid $gray2; }

    &.positive:after {
        position: absolute;
        top: -10px;
        right: 0;
        @include triangle(left, 10px, $green);
    }

    &.negative:after {
        position: absolute;
        right: -10px;
        bottom: 0;
        @include triangle(up, 10px, $red);
    }

    &.blue1 { @include colored-tag($blue1) }
    &.blue2 { @include colored-tag($blue2) }
    &.blue3 { @include colored-tag($blue3) }
}

li.selected {
    .tag {
        font-family: 'robotoregular';
        border: 1px solid $gray4;
    }
}

CSS for .tag inside of the viewHeader scope

.viewing-tags {
    margin-left: 20px;

    span {
        float: left;
        position: relative;
        top: 2px;
        font-style: Condensed;
        font-weight: 700;
        font-size: em(10);
        text-transform: uppercase;
    }

    .ticker { margin-right: 0; }

    .tags-hover-container {
        .ticker { margin-right: 5px; }
    }

    .tag { margin-right: 0; padding-right: 0px; }
    .tag { padding-bottom: 6px; }
    .tag {
        max-width: auto !important;
        width: auto !important;
        border: 1px solid $gray4 !important;
        background: none !important;
    }
}

Upvotes: 1

Views: 58

Answers (1)

Kevin B
Kevin B

Reputation: 95017

In your second (non-working) example, the div has children, and since you're using mouseover, the event handler will run each time you hover over a child of that div.

To fix this, simply replace ng-mouseover with ng-mouseenter, which differs from mouseover in that it will trigger when you enter the div, but not when you hover over one of it's children. It's the opposite of mouseleave. The opposite of mouseover is mouseout.

Upvotes: 1

Related Questions