Search code examples
javascriptjqueryhtmleonasdan-datetimepicker

Cloning Eonasdan DateTimePicker with Events


I'm creating a form that has two pickers to calculate the diference between the dates.

The user has the ability to create more date intervals and those should calculate the particular interval.

The issue I'm having is: after cloning the pickers no matter what I do, all other cloned pickers activate the first one.

The calculation happens after page load and the hide event of the pickers.

I know I have to initialise the new pickers upon creation, and I do it, but I'm not sure if it works.

How can I make this work? I know that if I remove the true from clone() the pickers work, but then the clone can't activate the events for the calculations.

var tomorrow = moment().add(1, 'day');
var startOfYearMoment = moment(tomorrow)
var endOfYearMoment = moment(tomorrow.add(1, 'year'));

function destroyPickers(nrOfSiblings) {
  // console.log("DELETED");

  for (let i = 0; i < nrOfSiblings; i++) {
    // console.log(i);
    $("#inicio" + i).data("DateTimePicker").destroy();
    $("#fim" + i).data("DateTimePicker").destroy();
  }
}

function updatePickers(nrOfSiblings) {
  // console.log("UPDATED");

  for (let i = 0; i < nrOfSiblings + 1; i++) {
    // console.log(i);
    initDatePickers("inicio" + i, "fim" + i);
  }
}

function initDatePickers(idInicio, idFim) { //, parentInicio, parentFim) {

  $("#" + idInicio).datetimepicker({
    locale: 'pt',
    ignoreReadonly: true,
    focusOnShow: false,
    viewMode: 'months',
    format: 'DD/MM/YYYY',
    defaultDate: startOfYearMoment,
    // widgetParent: $(parentInicio),
    widgetPositioning: {
      horizontal: 'auto',
      vertical: 'auto'
    },
    icons: {
      time: 'calendarIcon time',
      date: 'calendarIcon date',
      up: 'calendarIcon up',
      down: 'calendarIcon down',
      previous: 'calendarIcon prev',
      next: 'calendarIcon next',
      today: 'calendarIcon today',
      clear: 'calendarIcon clear',
      close: 'calendarIcon close'
    }
  });

  $("#" + idFim).datetimepicker({
    locale: 'pt',
    ignoreReadonly: true,
    focusOnShow: false,
    viewMode: 'months',
    format: 'DD/MM/YYYY',
    defaultDate: endOfYearMoment,
    //  widgetParent: $(parentFim),
    widgetPositioning: {
      horizontal: 'auto',
      vertical: 'auto'
    },
    icons: {
      time: 'calendarIcon time',
      date: 'calendarIcon date',
      up: 'calendarIcon up',
      down: 'calendarIcon down',
      previous: 'calendarIcon prev',
      next: 'calendarIcon next',
      today: 'calendarIcon today',
      clear: 'calendarIcon clear',
      close: 'calendarIcon close'
    }
  });

}


function getDisponibilidade(startDate, endDate, targetDiv) {

  if (endDate.isSameOrBefore(startDate, 'day month year')) {
    $("#" + targetDiv + " .fim").popover('show');
  } else $("#" + targetDiv + " .fim").popover('hide');

  //console.log(moment.duration(endDate.diff(startDate)).as('days'));


  var duration = Math.round(moment.duration(endDate.diff(startDate)).as('days'));
  if (duration <= 0) {
    $("#" + targetDiv + " .dias").replaceWith('<span class="dias">0 days</span>');

  } else if (duration > 1) {
    $("#" + targetDiv + " .dias").replaceWith('<span class="dias">' + duration + ' days</span>');
  } else $("#" + targetDiv + " .dias").replaceWith('<span class="dias">' + duration + ' day</span>');

  //console.log(duration);
  return duration
}

$(document).ready(function() {
  initDatePickers("inicio0", "fim0");
  var startDate = $("#inicio0").data("DateTimePicker").date();
  var endDate = $("#fim0").data("DateTimePicker").date();
  getDisponibilidade(startDate, endDate, "dispRow0")
});


$("input.dispDtp").on("dp.hide", function(event) {
  if (event.target.id.match(/inicio\d/)) {
    var startDateInpt = event.target.id;
    var endDateInpt = $(this).parent().siblings().children("input[id^='fim']")[0].id;

    /*     console.log(event);
        console.log($(this)); */

  } else {
    var startDateInpt = $(this).parent().siblings().children("input[id^='inicio']")[0].id;
    var endDateInpt = event.target.id;
  }
  var targetDiv = $(this).parents("div[id^='dispRow']")[0].attributes[1].value;

  /*   console.log(startDateInpt);
    console.log(endDateInpt); */

  var startDate = $("#" + targetDiv + " #" + startDateInpt).data("DateTimePicker").date();
  var endDate = $("#" + targetDiv + " #" + endDateInpt).data("DateTimePicker").date();

  getDisponibilidade(startDate, endDate, targetDiv);

});


$(".addDisp button").click(function(event) {

  $("#dispRow0").clone(true,true).appendTo(".disp").prop("id", "newDisp");


  var sibLength = $("#dispRow0").siblings().length;

  $("#newDisp div[id^='dispDtpDivInicio']").attr("class", "dispDtpDivInicio" + sibLength);
  $("#newDisp input[id^='inicio']").attr("name", "dataInicio" + sibLength).prop("id", "inicio" + sibLength);

  $("#newDisp div[id^='dispDtpDivFim']").attr("class", "dispDtpDivFim" + sibLength);
  $("#newDisp input[id^='fim']").attr("name", "dataInicio" + sibLength).prop("id", "fim" + sibLength);

  $("#newDisp .remDisp button").prop("id", "remDispBtn" + sibLength);

  destroyPickers(sibLength);
  updatePickers(sibLength);

  $("#newDisp").attr("id", "dispRow" + sibLength);
  // console.log($("#dispRow0").siblings());


})
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.css" />

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>

<div class="row">
  <div class="col-12 disp">
    <div class="form-row" id="dispRow0">
      <div class="formGroup col-2 dispDtpDivInicio0">
        <label for="dataInicio0" class=" ">start</label>
        <input name="dataInicio0" type="text" class="form-control dispDtp" id="inicio0" readonly required style="position:relative;">
      </div>
      <div class="formGroup col-2 dispDtpDivFim0">
        <label for=" dataFim0" class=" ">end</label>
        <input name="dataFim0" type="text " class="form-control dispDtp" id="fim0" readonly required style="position:relative;">
        <small class="form-text text-muted ">
          <span class="dias ">0 days</span>
        </small>
      </div>
    </div>
  </div>
</div>

<div class="row ">
  <div class="col-12 addDisp">
    <button type="button">
      <h5>
         Add
      </h5>
    </button>
  </div>
</div>

I've searched, and found a couple of questions, but none work like I need

Question 1

Question 2

Edit: JSFiddle if needed:


Solution

  • Take this as a lesson in organization, reusing code, and separation of concerns.

    Whenever you are dealing with modular components, you want to isolate them so that they are not dependent on each other. So, first, I would recommend not cloning an initialized object, and not cloning something with events.

    So, I have created a "view" template. One particular note, as you are aware, you must match the for and name attributes... I simply use a uuid for this, which will ensure two are never the same. Indexing would work fine as you have done, but then you have to keep track of that index. Up to you.

    The view template is initialized each time someone clicks add, and is initially called once when the document loads.

    The initialization preps the template, calls the date picker initialization, and adds the event handlers.

    Hope this helps.

    var tomorrow = moment().add(1, 'day');
    var startOfYearMoment = moment(tomorrow)
    var endOfYearMoment = moment(tomorrow.add(1, 'year'));
    
    function calculateDays($inicio,$fim){
      return function(event) {
        var startDate = $inicio.data("DateTimePicker").date();
        var endDate = $fim.data("DateTimePicker").date();
        var $span = $fim.parent().find('.dias');
        getDisponibilidade(startDate, endDate, $span);
      };
    }
    
    function initDatePickers($inicio,$fim) { //, parentInicio, parentFim) {
      var options = {
        locale: 'pt',
        ignoreReadonly: true,
        focusOnShow: false,
        viewMode: 'months',
        format: 'DD/MM/YYYY',
        // widgetParent: $(parentInicio),
        widgetPositioning: {
          horizontal: 'auto',
          vertical: 'auto'
        },
        icons: {
          time: 'calendarIcon time',
          date: 'calendarIcon date',
          up: 'calendarIcon up',
          down: 'calendarIcon down',
          previous: 'calendarIcon prev',
          next: 'calendarIcon next',
          today: 'calendarIcon today',
          clear: 'calendarIcon clear',
          close: 'calendarIcon close'
        }
      };
      options.defaultDate = startOfYearMoment,
      $inicio.datetimepicker(options);
      options.defaultDate = endOfYearMoment;
      $fim.datetimepicker(options);
      var hideHandler = calculateDays($inicio,$fim);
      $inicio.on("dp.hide",hideHandler);
      $fim.on("dp.hide",hideHandler);
      hideHandler(); // run once.
      
    }
    
    
    function getDisponibilidade(startDate, endDate, $span) {
      var duration = Math.round(moment.duration(endDate.diff(startDate)).as('days'));
      if (duration <= 0) {
        $span.text("0");
      }else{
        $span.text(duration);
      }
      return duration
    }
    
    $(document).ready(function() {
      addDateDurationWidget(); // Add an initial date widgit.
    });
    
    
    
    function addDateDurationWidget(){
      var $template = $("#view-templates .view").clone()
      var uu1 = uuid();
      var uu2 = uuid();
      var replaceUuid = function(uu){
        return function(i,s){return s.replace(/{{uuid}}/,uu)}
      }
      $template.find('.dispDtpDivInicio label').attr("for",replaceUuid(uu1))
      $template.find('.dispDtpDivInicio input').attr("name",replaceUuid(uu1))
      $template.find('.dispDtpDivFim label').attr("for",replaceUuid(uu2))
      $template.find('.dispDtpDivFim input').attr("name",replaceUuid(uu2))
      $template.appendTo(".disp").prop("id", "newDisp");
      $inicio = $template.find('input.inicio');
      $fim = $template.find('input.fim');
      initDatePickers($inicio,$fim);
    }
    
    $(".addDisp button").click(function(event) {
      addDateDurationWidget();
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.css" />
    
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
    
    <div class="row">
      <div class="col-12 disp">
        
      </div>
    </div>
    
    <div class="row ">
      <div class="col-12 addDisp">
        <button type="button">
          <h5>
             Add
          </h5>
        </button>
      </div>
    </div>
    <div id="view-templates" style="display:none;">
      <div class="form-row view">
        <div class="formGroup col-2 dispDtpDivInicio">
          <label for="dataInicio-{{uuid}}" class=" ">start</label>
          <input name="dataInicio-{{uuid}}" type="text" class="form-control inicio dispDtp" readonly required style="position:relative;">
        </div>
        <div class="formGroup col-2 dispDtpDivFim">
          <label for="dataFim-{{uuid}}" class=" ">end</label>
          <input name="dataFim-{{uuid}" type="text " class="form-control fim dispDtp" readonly required style="position:relative;">
          <small class="form-text text-muted ">
            <span class="dias">0</span><span> days</span>
          </small>
        </div>
      </div>
    </div>