How to use HTML5 drag and drop in combination with KnockoutJS?
Asked Answered
K

3

21

I can't seem to bind to html5 drag and drop events.

Here's an example of from a template:

<script id="tabsTemplate" type="text/html">
    <div class="dropzone" for="tab"
        data-bind="event:{dragover: function(event){event.preventDefault();},
                          dragenter: function(event){event.target.addClass('dragover'); event.preventDefault();},
                          dragleave: function(event){event.target.removeClass('dragover'); event.preventDefault();}}
                          drop: function(event){console.log('blahblah!')}"></div>
    <h1 class="tab" draggable="true"
      data-bind="attr: {selected: $data.name === $item.selected()},
                 click: function(){$item.selected($data.name)},
                 event:{ dragstart: function(event){console.log('blah!!')},
                         dragend: function(event){document.getElementsByClassName('dragover')[0].removeClass('dragover')}}">
        ${name}

        <img src="icons/close-black.png" class="close button" role="button"
            data-bind="click: function(e){$item.close($data)}">
    </h1>
</script>

What I have should work as expected... and it does as long as I make them normal inline ones. However, then the other bindings don't work!

I am getting this error message:

Uncaught SyntaxError: Unexpected token '||' jquery-tmpl.js:10

What's going on here? Is there something I'm doing wrong?

Kerbstone answered 28/8, 2011 at 0:4 Comment(4)
If you haven't implemented the dragstart event how are you determining whether or not it's working? If you don't setData then no dragging will occur.Depolymerize
All you have to do is put 'draggable: true' and you can drag. My dragend handler doesn't need to know about any data and it works just fine when its inline... just not when its in a binding.Kerbstone
The way I can tell is that the 'dragover' class that gets added on dragenter changes the css considerably. Dragend should remove the class from the last dropzone to fire the dragenter event. In the binding it doesn't... inline it does.Kerbstone
I've just checked - Chrome works, Firefox requires a dragstart. I assume therefore that you're using Chrome and this isn't your problem. I'll download knockout.js and have a play with it.Depolymerize
K
20

OK, I have worked it out. It seems I missed in the documentation where it said that in knockout, by default it makes all events prevent default / return false. So all I had to do was make my dragstart handler return true, and now it works. Phew!!

Kerbstone answered 29/8, 2011 at 10:6 Comment(0)
I
6

For those (like me) who need a SSCCE working; the solution follow's [cybermotron] suggestion, also fixes an issue where handlers expect prarameters data and event.

http://jsfiddle.net/marrok/m63aJ/

HTML

<script type="application/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

<ul id="people" data-bind='template: { name: "personTmpl", foreach: people }'>
</ul>
<div class="trash" data-bind ="visible:dragging, event:{
       dragover: function(data, event){
          event.preventDefault();
       },
       drop: function(data, event){
          console.log('trash', $root.drag_start_index())
          $root.trash($root.drag_start_index())     
          event.preventDefault();
       }
}"> <span>DELETE</span> </div> 

<script id="personTmpl" type="text/html">
    <li class="draggable" draggable="true" data-bind="event:{
      dragstart:   function(data, event){ 
                    $(event.target).addClass('dragSource')
                    $root.drag_start_index($index());
                    return $(event.target).hasClass('draggable');},    

       dragend:   function(data, event){  
                   $root.drag_start_index(-1);
                   $(event.target).removeClass('dragSource')
                   return true;
       },    
       dragover:  function(data, event){event.preventDefault();},
       dragenter: function(data, event){
                $root.drag_target_index($index());
                var element = $(event.target)
                if(element.hasClass('draggable'))
                     element.toggleClass('dragover'); 
                event.preventDefault();
    },
       dragleave: function(data, event, $index){
                var element = $(event.target)
                if(element.hasClass('draggable'))
                     element.toggleClass('dragover');
                event.preventDefault();
    },
       drop: function(data, event){
                $(event.target).removeClass('dragover'); 
                console.log('swap', $root.drag_start_index(),  $root.drag_target_index() )
                $root.swap($root.drag_start_index(),  $root.drag_target_index())
               }
             }">

        <span data-bind='text: name'></span>
    </li>
</script>

Knockout

var Person = function(name) {
    this.name = ko.observable(name);

};

var PeopleModel = function() {
    var self = this;

    self.drag_start_index = ko.observable();
    self.drag_target_index = ko.observable();
    self.dragging = ko.computed(function() {
        return self.drag_start_index() >= 0;
    });
    self.people = ko.observableArray([
        new Person("Oleh"), new Person("Nick C."), new Person("Don"), new Person("Ted"), new Person("Ben"), new Person("Joe"), new Person("Ali"), new Person("Ken"), new Person("Doug"), new Person("Ann"), new Person("Eve"), new Person("Hal")]);


    self.trash = function(index) {
        self.people.splice(index, 1)
    }
    self.swap = function(from, to) {
        if (to > self.people().length - 1 || to < 0) return;

        var fromObj = self.people()[from];
        var toObj = self.people()[to];
        self.people()[to] = fromObj;
        self.people()[from] = toObj;
        self.people.valueHasMutated()
    }
};
ko.applyBindings(new PeopleModel());​
Interrupter answered 28/11, 2012 at 18:47 Comment(1)
Nice fiddle you got there, very helpful and concise!Lineal
P
3

You might have the same problem as mentioned here, although it refers to nested templates:

Warning

If you are passing templateOptions to the template binding from a nested template (so, specifying a template binding from within a template), then pay special attention to your syntax. You will encounter a problem, if your binding looks like this:

 <div data-bind="template: { name: 'items', data: newItems, templateOptions: { header: “New Items!”}}"></div> 

The jQuery Templates plugin gets confused by the }} at the end of your binding, since that is part of its syntax. Adding a space between your braces will work fine. Hopefully this prevents someone from a little unnecessary frustration.

 <div data-bind="template: { name: 'items', data: newItems, templateOptions: { header: “New Items!”} }"></div>
Pique answered 28/8, 2011 at 4:42 Comment(3)
Wow... I hadn't thought about jquery template being confused about my inconsiderate use of }} ... well I fixed them up and now I no longer get the error... and now when I start my drag the console says "blah!", just like I asked it to... however the element doesn't actually drag now! It registers the dragstart event, but the element stays put... and your link appears to be broken.Kerbstone
link fixed now :) Good article, but not what I was looking for for this.Kerbstone
I realize that :) Haven't had the time to take a closer look at it though.Pique

© 2022 - 2024 — McMap. All rights reserved.