7

I have a standard template in an Html file like:

<template name="cards">
  {{#each all_cards}}
    {{> card_item}}
  {{/each}}
</template>
<template name="card_item">
  <div class="card" style="left:{{position.x}}px; top:{{position.y}}px">
    {{title}}
  </div>
</template>

I want to have the cards (css selector .card) become draggable with JQuery. Now since Meteor automagically updates the DOM using the template, when and how do I know where to call .draggable() on what??

EDIT: This is so far my solution which makes pending movements on other client visible with a wobble animation (using CSS3):

Template.card_item.events = {
'mouseover .card': function (e) {
    var $target = $(e.target);
    var $cardContainer = $target.hasClass('card') ? $target : $target.parents('.card');     
    $cardContainer.draggable({containment: "parent", distance: 3});
},
'dragstart .card': function (e) {
    Session.set("dragging_id", e.target.id);
    $(e.target).addClass("drag");
    pos = $(e.target).position();
    Events.insert({type: "dragstart", id:e.target.id, left: pos.left, top: pos.top});       
},
'dragstop .card': function (e) {
    pos = $(e.target).position();
    Events.insert({type: "dragstop", id:e.target.id, left: pos.left, top: pos.top});        
    Cards.update(e.target.id, {$set: {left:pos.left, top:pos.top}});
    Session.set("dragging_id", null);
}
}

Events.find().observe({
added: function(event) {
    if (event.type == "dragstart" && !Session.equals("dragging_id", event.id)) {
        $("#"+event.id).draggable({disabled: true});
        $("#"+event.id).addClass("wobble");             
    }
    if (event.type == "dragstop" && !Session.equals("dragging_id", event.id)) {
        $("#"+event.id).animate({left: event.left, top: event.top}, 250);           
        Events.remove({id:this.id});  
        $("#"+event.id).draggable({disabled: false});
    }
}
});
Michel Löhr
  • 1,009
  • 11
  • 15
  • 1
    Use `Meteor.defer`, it isn't in the documentation, but this question explains it. I'm using it to make a list sortable at the moment and it works well. http://stackoverflow.com/questions/10109788/callback-after-the-dom-was-updated-in-meteor-js – lashleigh May 04 '12 at 17:36
  • Cheers, you are very welcome. Also, I just learned that meteor.defer is just a wrapper for setTimeout, type `Meteor.defer` in the console with no arguments to see what I mean. – lashleigh May 04 '12 at 18:11
  • I checked it out, interesting.... – Michel Löhr May 04 '12 at 19:53
  • Okay see update, `Meteor.defer` should not be used in this case.. – Michel Löhr May 10 '12 at 22:00
  • +1 Looks great now. I think I'm still stuck with defer for my sorting needs unfortunately. – lashleigh May 10 '12 at 22:43

1 Answers1

6

EDIT: This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. See my comment below.

Looks like we're working on similar things! I've got a working proof of concept for a simple Magic: The Gathering app. Here's how I have dragging implemented at the moment:

In a <head> section in one of your html files, include the jQuery UI script:

<script src="jquery-ui-1.8.20.custom.min.js"></script>

Then, in a js file, make sure elements become draggable on mouseover (note: this is sub-optimal on touchscreens since it requires two touches to drag... I'm looking for a better touchscreen solution):

Template.card_item.events['mouseover .card, touchstart .card'] = function (e) {
    var $target = $(e.target);
    if (!$target.data('isDraggable')) {
      $target.data('isDraggable', true).draggable();
    }
};

And finally, handle the drag and dragstop events:

var prevDraggedTime = 0

Template.card_item.events['drag .card'] = function (e) {
    // get the cardId from e
    var now = new Date().getTime();
    var position;

    if (now - prevDraggedTime > 250) {
        position = $(e.target).position();
        Cards.update(cardId, {$set: {x: position.top, y: position.left}});
        prevDraggedTime = now;
    }
}

Template.card_item.events['dragstop .card'] = function (e) {
    // get the cardId from e
    var position = $(e.target).position();
    Cards.update(cardId, {$set: {x: position.top, y: position.left}});
}
Community
  • 1
  • 1
Emmett
  • 14,035
  • 12
  • 56
  • 81
  • I don't think updating on 'drag' is a good idea. That could be hundreds of updates for even a small drag. Just update on dragend and use a css3 transformation to move things smoothly on other clients. – lashleigh May 10 '12 at 17:42
  • Yea, this was just to demonstrate the concept. In my actual code, I'm updating the position on `dragstop`, and then once every 250ms on `drag`. – Emmett May 10 '12 at 18:00
  • Cool, that makes a lot more sense. I think it would be worth saying as much in your answer though. – lashleigh May 10 '12 at 18:14
  • Thanks, with your help I factored out the defer function by using your setup. – Michel Löhr May 10 '12 at 21:48
  • @Emmett I'm glad, it is a +1 answer now :) – lashleigh May 10 '12 at 22:45
  • This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. When the element is re-rendered mid-drag, it loses its currently-being-dragged state. The code still works if the position is only updated upon `dragstop`, but not upon `drag`. – Emmett Oct 18 '12 at 07:56