0

I'm trying to drag events from the external events box to the fullcalendar.

I recreated the issue that I face in the following CodePen:

at the first time, Dragging an external event from the list into the calendar works fine. However, when I apply the filter on a book in the search input text filter books I have the following issue.

Here are the steps to reproduce :

1- look for 111 in the search input that will filter to the only resulting 111 event book.

2- As you could observe that dragging this resulting event from the filter into the calendar could work fine : but Here we are mainly interested on the case we decide to filter but to do not drag anything into calendar.

3- so for now clear the 111 filter as text from the search input, the external events box would back all the defaults events but this time trying to drag one of them into the calendar freezes. It's no more working.
It freezes on all the external events except for the last found by the filter means the 111 event (look at the last row on the events ) that onlty one that could be dragged. and no more able to drag others events into the calendar.

4- and even If I dragged 111 into the calendar, and after that if I try to drag anothor one It will freeze.

HTML

<!DOCTYPE html>
<html ng-app="app">
  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script data-require="angular.js@1.0.x" src="https://code.angularjs.org/1.3.15/angular.min.js" data-semver="1.0.7"></script>
    <script type="text/javascript" src="https://github.com/kamilkp/angular-vs-repeat/blob/master/dist/angular-vs-repeat.js"></script>
    <script src="https://code.angularjs.org/1.3.15/angular-animate.min.js"></script>
  </head>
<body ng-controller="MainCtrl">
<div id="first">
             <input type="search" id="myInput" ng-model="searchText" placeholder="filter books..." title="filter books"/>
             <div style="width:200px;display:inline-block;vertical-align:top;color: gray;">
                    <input type='checkbox' id='drop-remove' checked='checked'/>
                    <label for='drop-remove'> Remove after a drag </label>
             </div>
 <div id='external-events'>

             <ul vs-repeat="60" class="repeater-container" title="Books darggable({{books.length}})" data-                        drag="true"  data-jqyoui-options="{revert: 'invalid'}">
                 <li class="animate-repeat fc-event item-element" ng-repeat="book in books | orderBy : sort : false | filter:searchText as results track by book.contents.name"   id="{{book.id}}">

                   <div class="circle">{{book.contents['date']}}</div>
                   <div class="left content" ng-bind-html="trustAsHtml(book.contents['name'])" id="book_{{book.id}}"></div>
                   <div class="left rating">2/10</div>
                   <div class="clear"></div>
                </li>
                <li class="animate-repeat" ng-if="results.length === 0">
                    <strong>No results found...</strong>
                </li>

             </ul>
  </div>
</div>

<div id="second"> 
  <div id='calendar-container'>
    <div id='calendar'></div>
  </div>
</div>
</body>
</html>

js

var app = angular.module("app", ['ngAnimate']);
(function(angular) {
  'use strict';
app.controller("MainCtrl", ['$scope', '$sce', function($scope, $sce){

 $scope.books = [
                {
                    id: 'id1',
                    contents: {
                        name: '<span>1Alain du sceau france</span><br><span> Canada Madagascar philipine</span>',
                        price: 'price1',
                        date: '111'
                    }
                },
                {
                    id: 'id2',
                    contents: {
                        name: '<span>2Name zu Long zu Schreiben Bis Here ist Ein Beispiel</span><br><span>Maneschester Canada Madagascar philipine</span>',
                        price: 'price2',
                        date: '22'
                    }
                },
                {
                    id: 'id3',
                    contents: {
                        name: '<span>3name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price3',
                        date: '23'
                    }
                },
                {
                    id: '4',
                    contents: {
                        name: '<span>4name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price4',
                        date: '4'
                    }
            },
            {
                    id: 'id5',
                    contents: {
                        name: '<span>5name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price5',
                        date: '5'
                    }
            },
            {
                    id: 'id6',
                    contents: {
                        name: '<span>6name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price6',
                        date: '6'
                    }
            },
            {
                    id: 'id7',
                    contents: {
                        name: '<span>7name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price7',
                        date: '7'
                    }
            },
            {
                    id: 'id8',
                    contents: {
                        name: '<span>8name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price8',
                        date: '8'
                    }
            },
            {
                    id: 'id9',
                    contents: {
                        name: '<span>9name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price9',
                        date: '9'
                    }
            },
               {
                    id: 'id10',
                    contents: {
                        name: '<span>10Alain du sceau france</span><br><span> Canada Madagascar philipine</span>',
                        price: 'price10',
                        date: '10'
                    }
                },
                {
                    id: 'id11',
                    contents: {
                        name: '<span>11Name zu Long zu Schreiben Bis Here ist Ein Beispiel</span><br><span>Maneschester Canada Madagascar philipine</span>',
                        price: 'price11',
                        date: '11'
                    }
                },
                {
                    id: 'id12',
                    contents: {
                        name: '<span>12name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price12',
                        date: '12'
                    }
                },
                {
                    id: 'id13',
                    contents: {
                        name: '<span>13name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price13',
                        date: '13'
                    }
            },
            {
                    id: 'id14',
                    contents: {
                        name: '<span>14name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price14',
                        date: '14'
                    }
            },
            {
                    id: 'id15',
                    contents: {
                        name: '<span>15name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price15',
                        date: '15'
                    }
            },
            {
                    id: 'id16',
                    contents: {
                        name: '<span>16name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price16',
                        date: '16'
                    }
            },
            {
                    id: 'id17',
                    contents: {
                        name: '<span>17name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price17',
                        date: '17'
                    }
            },
            {
                    id: 'id18',
                    contents: {
                        name: '<span>18name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price18',
                        date: '18'
                    }
            },
            {
                    id: 'id19',
                    contents: {
                        name: '<span>19name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price19',
                        date: '19'
                    }
            },
            {
                    id: 'id20',
                    contents: {
                        name: '<span>20name Aleatoire Schwer und zu Leicht Zu Schreiben</span><br><span>Mexico Canada USA France Uk Deutschland Schweiz Madagascar philipine</span>',
                        price: 'price20',
                        date: '20'
                    }
            }
            ];





  /*$scope.books.forEach(function(book) {
    book.contents.name =  $sce.trustAsHtml(book.contents.name);
  })*/
  $scope.trustAsHtml = $sce.trustAsHtml;

  $scope.h = function(html) {
    return $sce.trustAsHtml(html);
  };

  $scope.sort = function(num) {
    var newNum = parseInt(num.contents.date);
    console.log("$$newnum",newNum);
    return newNum;
  };


 $(document).ready( function(){     
        //Initialise external events
        initialise_external_event('.fc-event');
        initialise_calendar();

  });





  // initialize the external events
  // -----------------------------------------------------------------
function initialise_external_event(selector){

   /* initialize the external events
        -----------------------------------------------------------------*/

        $('#external-events .fc-event').each(function() {

            // store data so the calendar knows to render an event upon drop
            $(this).data('event', {
                title: $.trim($(this).text()), // use the element's text as the event title
                stick: true // maintain when user navigates (see docs on the renderEvent method)
            });

            // make the event draggable using jQuery UI
            $(this).draggable({
                zIndex: 999,
                revert: true,      // will cause the event to go back to its
                revertDuration: 0  //  original position after the drag
            });

        });



}
  function initialise_calendar(){
     /* initialize the calendar
        -----------------------------------------------------------------*/

        $('#calendar').fullCalendar({
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'month,agendaWeek,agendaDay'
            },
            editable: true,
            droppable: true, // this allows things to be dropped onto the calendar
            dragRevertDuration: 0,
            drop: function() {
                // is the "remove after drop" checkbox checked?
                if ($('#drop-remove').is(':checked')) {
                    // if so, remove the element from the "Draggable Events" list
                    $(this).remove();
                }
            },
            eventDragStop: function( event, jsEvent, ui, view ) {

                if(isEventOverDiv(jsEvent.clientX, jsEvent.clientY)) {
                    $('#calendar').fullCalendar('removeEvents', event._id);
                    var el = $( "<div class='fc-event'>" ).appendTo( '#external-events-listing' ).text( event.title );
                    el.draggable({
                      zIndex: 999,
                      revert: true, 
                      revertDuration: 0 
                    });
                    el.data('event', { title: event.title, id :event.id, stick: true });
                }
            }
        });


        var isEventOverDiv = function(x, y) {

            var external_events = $( '#external-events' );
            var offset = external_events.offset();
            offset.right = external_events.width() + offset.left;
            offset.bottom = external_events.height() + offset.top;

            // Compare
            if (x >= offset.left
                && y >= offset.top
                && x <= offset.right
                && y <= offset .bottom) { return true; }
            return false;

        }
 }

}]);
})(window.angular);

css

    ul[title]::before {

    content: attr(title);
     /* then add some nice styling as needed, eg: */

     font: italic 11px "Trebuchet MS", Verdana, Arial, Helvetica,    sans-serif;
    color: gray;
}

/*ul {
  list-style-type: none;
}*/

#myInput {
  /*background-image: url('/css/searchicon.png');*/
  background-position: 10px 12px;
  background-repeat: no-repeat;
  width: 77%;
  font-size: 16px;
  padding: 12px 20px 12px 40px;
  border: 1px solid #ddd;
  margin-bottom: 12px;
}

/*ul>li {
  display:block;
    padding-right: 0cm;
    margin-left: 0px;
}*/

#calendar{
 padding: 0 10px;
 width: 650px;
 float: right;
 margin: 0px 0px 10px 55px;
 }

#external-events {
  width: 500px;
  padding: 0 0px;
  border: 0px solid #ccc;/* gray moyen*/
  background: #eee;/* #5D6D7E;(Blue mat) */ /* #eee color gray*/
  text-align: left;
}

#external-events .fc-event {
  cursor: pointer;
  z-index: 9999;
  background: #eee;
  border: solid 1px black;
  border-radius: 2px;
  margin-bottom:5px;
}

.content span
{
  color: gray;
}
.fc-event span:first-child
{
  font-size: 25px;
  font-weight: bold italic;
}

.content
{
  float:left;
  max-width:75%;
}

.clear
{
  clear:both;
}

.circle {
  float:left;
  width: 10%;
  height: 25%;
  padding: 0 10px;
  border-radius: 360px;


  /* Just making it pretty */
  @shadow: rgba(0, 0, 0, .1);
  @shadow-length: 4px;
  -webkit-box-shadow: 0 @shadow-length 0 0 @shadow;
          box-shadow: 0 @shadow-length 0 0 @shadow;
  text-shadow: 0 @shadow-length 0 @shadow;
  background: #FFFFFF;/*color white*/
  color: #f05907;/* color red*/
  font-family: Helvetica, Arial Black, sans;
  font-size: 10;
  text-align: center;
}

.rating
{
  float:right;
  background: #FFFFFF;/*color white*/
  color: #f05907;/* color red*/
  font-family: Helvetica, Arial Black, sans;
  font-size: 10;
  text-align: center;
  border-radius: 360px;
}

.animate-repeat {
  line-height:30px;
  list-style:none;
  box-sizing:border-box;
}

.animate-repeat.ng-move,
.animate-repeat.ng-enter,
.animate-repeat.ng-leave {
  transition:all linear 0.5s;
}

.animate-repeat.ng-leave.ng-leave-active,
.animate-repeat.ng-move,
.animate-repeat.ng-enter {
  opacity:0;
  max-height:0;
}

.animate-repeat.ng-leave,
.animate-repeat.ng-move.ng-move-active,
.animate-repeat.ng-enter.ng-enter-active {
  opacity:1;
  max-height:30px;
}


#first {
    width: 650px;
    float: left
}
#second {
    width: 650px;
    float: left;
}


.repeater-container{
  height: 445px;
  overflow: auto;
  box-shadow: 0 0 10px;
  border-radius: 5px;
  list-style: none;
  margin: 0;
  padding: 0;
  -webkit-overflow-scrolling: touch;
}
.repeater-container .item-element {
    margin: 0 !important;
    width: 100%;
    height: 140px;
    border: 1px solid green;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
}
Schwertfisch
  • 133
  • 3
  • 16

1 Answers1

0

ng-repeat with the filter recreats items DOM.

Thing that should not be done (As you can see from my code in HTML I'm using ng-repeat with track by book.contents.name and as far as I know in

that case angularjs would not recreat the dom. Otherwise, in case we use

ng-repeat without track by id angularjs would recreat the dom you could see THE_FOLOWING_LINK ).

It's quite pretty tough for me to understand exactely why the dom is

recreated in that case with ng-repeat with the filter !

A workaround that I've done is described in text below:

1- So In order to make the items draggable again, we must call .draggable() on every filter action.

The perfect solution is to do a DOM manipulation within an angular directive via the link function. but Here to make things much simple a workaround That I just included in the detectEmpty which is a scoped function .

2- added ng-change="detectEmpty()" to detect when the input is cleared

<input type="search" id="myInput" ng-model="searchText" placeholder="filter books..." title="filter books" ng-change="detectEmpty()"/>

3- and on .js

$scope.detectEmpty = function() {
    if ($scope.searchText.trim().length === 0) {
         // it's empty
        $(document).ready( function(){     


           initialise_external_event('.fc-event');

        });

    }

Let's try to understand what's going on here?
=============================================
- All html elements loaded first.
- JS trigger all dragging functionality to loaded DOM.
- ng-repeat with filter function call to fetch new data and replace old 
  DOMs.
- Now drag functionality stops working.
- why? because JS drag function run on old DOM and currently it has been 
  removed. **why it has been moved ? I don't have any clue on that**
 - need to call the drag function again on new loaded DOM.
 - that's why added the call to initialise_external_event('.fc-event');
   when the input search is cleared. 

Hope it would help someone else ;)

Hope also AngularJS pro tell us why the dom is recreated in that case ?

Update

because proposed solution before is a bad solution .

by re-initializing external events each time when a filter is applied by doing so :

$scope.detectEmpty = function() {
        if ($scope.searchText.trim().length === 0) {
             // it's empty
            $(document).ready( function(){     


               initialise_external_event('.fc-event');

            });

        }

will add each time an additional external event to external event box. If we start for example with an array of 20 entries we will end up with 40 on the next filter apply and we will add a 20 entries to our exteranl array entries each time the filter is applied. 20,40,60,80 and so..on,

The question is How to preserve DOM elements when using ng-repeat with filter?

I have an ng-repeat with a filter.

When some items are filtered out by the filter and then they are restored after another filter change there are new DOM elements created for these items.

If there was any DOM manipulation on the item it gets lost after item is hidden and restored with filter.

Is there a way to keep the DOM elements, even when item is removed by filter?

I tried using track by which following LINK, but it doesn't help.

1- So to watch the DOM change and to confirm that. You can use the following link and use service from Basheer AL-MOMANI

2- The root of the problem is that I'm trying to do this outside of angular. When doing everything within angular, angular will update the DOM for us as needed.

If we have complex DOM manipulation code, it sounds like we need a directive to handle this. Then we just stick the directive on these elements, and the DOM will get updated as desired. and to get deep info on this see the following link and this [JSFIDDLE]

4 that is related to the answer.

3- We should also try to implement a drag&drop calendar with angular directives. To accomplish this, we use JqueryUI draggable. By Creating a directive and passing the attributes via "elem".

directive('dragMe', function() {
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      elem.data('event', {
          title: $.trim($(elem).text()), // use the element's text as the event title
          stick: true // maintain when user navigates (see docs on the renderEvent method)
        });
      elem.draggable({
          zIndex: 999,
          revert: true,      // will cause the event to go back to its
          revertDuration: 0  //  original position after the drag
        });
    }
  };
})

and we add drag-me here:

<li class="animate-repeat fc-event  item-element" drag-me    
ng-repeat="book in books | orderBy : sort : false  | filter: searchText as results track by book.id"   id="{{book.id}}"
 >                   

On the other hand I have an element which I bind html to it.

See vkammerer answer on this link

and we add compile="book.contents.name"

Here Revised. Working CodePen that works with directives to handle the add draggable to our items each time ng-repeat with the filter is applied.

Hope it would help someone else ;)

Schwertfisch
  • 133
  • 3
  • 16