0

I'm cloning a block of Materialize form elements including a couple of selects, a time picker and textarea with a button click event. Current code uses a counter to increment the id and label for. As expected, this works on basic inputs like the textarea but for items like the select or picker, where Materialize dynamically injects its own JS with hashed IDs on multiple elements, no dice. How do I properly clone selects and the pickers so that they work?

I've tried manually changing the cloned IDs in Chrome's dev console (looking at the matching hashes) to see if I could at least trigger the selects or picker but still not working and no error. Only the initial block works. I was thinking that maybe if I could get the elements to at least trigger, I could write some code to target the picker or select separately. Not sure If I am overthinking this.

A kludgy way I've gotten things working is by creating the max # of blocks in the html rather than cloning, hiding all but the first, then, manually updating the IDs and then showing each on button click. They all work this way but of course it's not DRY and I have to deal with state.

Here is my code for the cloning attempt:

HTML:

<div class="container">
   <div id="mealRequests" class="row">
      <!-- Meal Request #1 -->
      <div class="input-field col s12 meal-request-block">
         <div class="row ">
            <div class="input-field col s12 m6">
               <select id="mealType01">
                  <option value="" disabled selected>Choose your option</option>
                  <option value="Breakfast">Breakfast</option>
                  <option value="Lunch">Lunch</option>
                  <option value="Snack">Snack</option>
                  <option value="Cocktail">Cocktail</option>
               </select>
               <label for="mealType01">Meal type</label>
            </div>
            <div class="input-field col s12 m6">
               <input id="consumptionTime01" type="text" class="timepicker" autocomplete="off" placeholder="09:00 AM">
               <label for="consumptionTime01">Consumption time</label>
            </div>
         </div>
         <div class="row">
            <div class="input-field col s12">
               <select id="vendorChoice01">
                 <option value="" disabled selected>Choose your option</option>
                 <option value="Dig Inn">Dig Inn</option>
                 <option value="Pret">Pret</option>
                 <option value="Au Bon Pain">Au Bon Pain</option>
               </select>
               <label for="vendorChoice01">Vendor</label>
            </div>
         </div>
         <div class="row">
            <div class="input-field col s12">
               <textarea id="menuRequest01" class="materialize-textarea" placeholder="Farmers plate"></textarea>
               <label for="menuRequest01">Menu selections</label>
            </div>
         </div>
      </div>
   </div>
  <div class="row">
    <div class="col s12">
      <button id="addMealRequestBtn" class="btn-text"><i class="material-icons">+</i> Add another meal request</button>
    </div>
  </div>
</div>

JS:

// Initialize Materialize's select and datepicker
$('select').formSelect();
$('.timepicker').timepicker(); 

    // Add click handler for meal requests
    var addMealRequest = function () {

      var $row = $('.meal-request-block').parent(),
          $template = $('.meal-request-block:first').clone(),         
          counter = 1, // used for incrementing input IDs
          limit = 2; // sets a max limit on the total number of clones

      $('#addMealRequestBtn').on('click', function(e) {
        e.preventDefault();
        var $totalCount = $('.meal-request-block').length;

        counter++;

        if ($totalCount < limit) {
          $template.clone().find('.timepicker-modal, select, input, textarea').each(function () {

            //set id to store the updated section number
            var newId = this.id + counter;

            //update for label
            $(this).next().attr('for', newId).addClass('active');

            //update id
            this.id = newId;

          }).end()

          .appendTo($row);  
        }        
      });
    };

    addMealRequest();

CSS:

.input-field div.error {
  position: relative;
  top: -.25rem;
  left: 0rem;
  font-size: 0.8rem;
  color: red;
  transform: translateY(0%);
}

.meal-request-block {
  background-color: #f0f0f0;
  padding: 24px!important;
}

  .btn-text {
    background: none;
    border: none;
    cursor: pointer;
    font-weight: 600;
    opacity: 1;
    padding: 0;
    transition: all .25s ease;
    &:focus {
      background: transparent;
    }
    &:hover {
      opacity: .75;
    }
  }

And a basic pen: https://codepen.io/anon/pen/GLzEvw

Here is an example of what the code looks like after Materialize injects it's JS:

SELECT:

<div class="input-field col s12 m6">
  <div class="select-wrapper">
    <input class="select-dropdown dropdown-trigger" type="text" readonly="true" data-target="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a074">
    <ul id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a074" class="dropdown-content select-dropdown" tabindex="0">
      <li class="disabled selected" id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a0740" tabindex="0"><span>Choose your option</span></li>
      <li id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a0741" tabindex="0"><span>Breakfast</span></li>
      <li id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a0742" tabindex="0"><span>Lunch</span></li>
      <li id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a0743" tabindex="0"><span>Snack</span></li>
      <li id="select-options-db214a65-6ab6-0918-fd05-e3c7cc84a0744" tabindex="0"><span>Cocktail</span></li>
    </ul>
    <svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
      <path d="M7 10l5 5 5-5z"></path>
      <path d="M0 0h24v24H0z" fill="none"></path>
    </svg>
    <select id="mealType01" tabindex="-1">
      <option value="" disabled="" selected="">Choose your option</option>
      <option value="Breakfast">Breakfast</option>
      <option value="Lunch">Lunch</option>
      <option value="Snack">Snack</option>
      <option value="Cocktail">Cocktail</option>
    </select>
  </div>
  <label for="mealType01">Meal type</label>
</div>

TIME PICKER:

<div class="input-field col s12 m6">
  <div class="modal timepicker-modal" id="modal-1bc17566-dd57-4454-0735-057dc30e2263" tabindex="0" style="z-index: 1003; display: none; opacity: 0; top: 4%; transform: scaleX(0.8) scaleY(0.8);">
    <div class="modal-content timepicker-container">
      <div class="timepicker-digital-display">
        <div class="timepicker-text-container">
          <div class="timepicker-display-column"><span class="timepicker-span-hours">12</span>:<span class="timepicker-span-minutes text-primary">30</span></div>
          <div class="timepicker-display-column timepicker-display-am-pm">
            <div class="timepicker-span-am-pm">
              <div class="am-btn">AM</div>
              <div class="pm-btn text-primary">PM</div>
            </div>
          </div>
        </div>
      </div>
      <div class="timepicker-analog-display">
        <div class="timepicker-plate">
          <div class="timepicker-canvas">
            <svg class="timepicker-svg" width="270" height="270">
              <g transform="translate(135,135)">
                <line x1="0" y1="0" x2="1.0409497792752502e-14" y2="85"></line>
                <circle class="timepicker-canvas-bg" r="20" cx="1.2858791391047208e-14" cy="105"></circle>
                <circle class="timepicker-canvas-bearing" cx="0" cy="0" r="4"></circle>
              </g>
            </svg>
          </div>
          <div class="timepicker-dial timepicker-hours timepicker-dial-out" style="visibility: hidden;">
            <div class="timepicker-tick" style="left: 167.5px; top: 24.0673px;">1</div>
            <div class="timepicker-tick" style="left: 205.933px; top: 62.5px;">2</div>
            <div class="timepicker-tick" style="left: 220px; top: 115px;">3</div>
            <div class="timepicker-tick" style="left: 205.933px; top: 167.5px;">4</div>
            <div class="timepicker-tick" style="left: 167.5px; top: 205.933px;">5</div>
            <div class="timepicker-tick" style="left: 115px; top: 220px;">6</div>
            <div class="timepicker-tick" style="left: 62.5px; top: 205.933px;">7</div>
            <div class="timepicker-tick" style="left: 24.0673px; top: 167.5px;">8</div>
            <div class="timepicker-tick" style="left: 10px; top: 115px;">9</div>
            <div class="timepicker-tick" style="left: 24.0673px; top: 62.5px;">10</div>
            <div class="timepicker-tick" style="left: 62.5px; top: 24.0673px;">11</div>
            <div class="timepicker-tick" style="left: 115px; top: 10px;">12</div>
          </div>
          <div class="timepicker-dial timepicker-minutes" style="visibility: visible;">
            <div class="timepicker-tick" style="left: 115px; top: 10px;">00</div>
            <div class="timepicker-tick" style="left: 167.5px; top: 24.0673px;">05</div>
            <div class="timepicker-tick" style="left: 205.933px; top: 62.5px;">10</div>
            <div class="timepicker-tick" style="left: 220px; top: 115px;">15</div>
            <div class="timepicker-tick" style="left: 205.933px; top: 167.5px;">20</div>
            <div class="timepicker-tick" style="left: 167.5px; top: 205.933px;">25</div>
            <div class="timepicker-tick" style="left: 115px; top: 220px;">30</div>
            <div class="timepicker-tick" style="left: 62.5px; top: 205.933px;">35</div>
            <div class="timepicker-tick" style="left: 24.0673px; top: 167.5px;">40</div>
            <div class="timepicker-tick" style="left: 10px; top: 115px;">45</div>
            <div class="timepicker-tick" style="left: 24.0673px; top: 62.5px;">50</div>
            <div class="timepicker-tick" style="left: 62.5px; top: 24.0673px;">55</div>
          </div>
        </div>
        <div class="timepicker-footer">
          <button class="btn-flat timepicker-clear waves-effect" style="visibility: hidden;" type="button" tabindex="3">Clear</button>
          <div class="confirmation-btns"><button class="btn-flat timepicker-close waves-effect" type="button" tabindex="3">Cancel</button><button class="btn-flat timepicker-close waves-effect" type="button" tabindex="3">Ok</button></div>
        </div>
      </div>
    </div>
  </div>
  <input id="consumptionTime01" type="text" class="timepicker" autocomplete="off" placeholder="09:00 AM">
  <label for="consumptionTime01" class="active">Consumption time</label>
</div>

To sum up, how can I clone selects and pickers so that they actually work? Target their IDs properly? Leverage Materialize's API somehow to do this? As of now, I'm just initializing selects and time pickers globally.

Pete Ankelein
  • 221
  • 1
  • 6

2 Answers2

0

I have a feeling your event handlers are not attached to your dynamically created (cloned) elements.

Normally, any event handlers bound to the original element are not copied to the clone.

Take a look at this post

I would put this as a comment, but I currently can not post comments.

A_E
  • 176
  • 5
  • The event handler is on the button, which isn't being cloned. All of the cloned items show the incremented ID per the code in the handler. I think it has to do more with how Materialize builds the select and picker and what I'd imagine is targeting it correctly, if possible. – Pete Ankelein Apr 29 '19 at 14:30
0

I know this is an old question now, but I just ran into a similar issue.

The way I resolved it was to destroy all the select instances with instance.destroy() before doing the clone, then re-initializing the selects after cloning the block and inserting it into the DOM.

You can see the working jsFiddle here: https://jsfiddle.net/AJLeonardi/szt1enob/53/

AJLeonardi
  • 55
  • 4