How to access a nested JavaScript array by a string path

Hello,

Recently, I had a problem with making some structure for storing a drag’n’drop state of an element. The best way that came to my mind was making a mixtype structure of arrays and objects. For example, I had an array with the following structure:


{
    "docs": [
        {
            "minimize": false,
            "docs": [
                {
                    "minimize": true
                }
            ]
        },
        {
            "minimize": false,
            "docs": [
                {
                    "minimize": true
                }
            ]
        }
    ]
}

So, I needed to get the last nested element of that object by the path like — docs_0_docs_0 . And also, after getting the nested element, I needed to set the value back to the element. The function with the implementation of setting data is below:


function set_data_to_object(object, path, data, type = 'object') {

  const isNumeric = (string) => /^[+-]?\d+(\.\d+)?$/.test(string)

  if (typeof (path) == 'string') {
    path = path.split('_');
  }

  if (typeof (object) !== 'object' || Object.keys(object).length == 0) {
    object = type === 'object' ? {} : [];
  }

  const element = path[0];

  if (path.length == 1) {
    object[element] = data;
  } else {
    if (isNumeric(element)) {
      object[element] = set_data_to_object(object[element], path.slice(1), data, 'object');
    }
    else {
      object[element] = set_data_to_object(object[element], path.slice(1), data, 'array');
    }
  }

  return object;
}

The first argument of the function is an object. The object can be empty empty like — {}; in this case the parent elements will be made corresponding to the path.

The second argument is the path, as I mentioned above it should be like — docs_0_docs_0 . Where the underscore symbol is delimiter symbol.

The third argument is the data that you want to the object.

And finally, the function will return the changed object with the set data.

For getting data from the object, I wrote another function:


function get_data_from_object(object, path) {

    if (typeof (path) == 'string') {
        path = path.split('_');
    }

    const element = path[0];

    if (path.length == 1) {
        return object[element];
    } else {
        return get_data_from_object(object[element], path.slice(1));
    }
}

The first argument of the function is the object that contains the structure like I described above.

The second argument is the path like — docs_0_docs_0.

I hope, it can be helpful for somebody.


How to prevent typing decimals in input type number

If you have problem with cutting decimal point from input with type number, you can just define KeyDown event on the input element:


var input_element = document.getElementById('number-input');

input_element.addEventListener('keydown',function(event) {
    if(event.keyCode == '190')
    {
      event.preventDefault();
    }
});

That’s it!


How to swipe YouTube embedded clip in Slick gallery slider on mobile

Hello there

Recently, I have run into the problem that I couldn’t swipe the slides with YouTube embedded clips in the Slick gallery. Instead of swiping, nothing was happening. In other words, when I tried to touch and swipe the slide to the left or the right on a mobile device, the slide and the slider didn’t react in any way. Nevertheless, when I just touched the YouTube embedded clip, it just started playing normaly. I tried to play with the settings of the Slick slider, and then I concluded that it was impossible to fix it that way.

I searched for the solution, and I didn’t find anything that could help me. So, I thought that it would be a very interesting task to solve.

First of all, as I understood, the problem was in the embedded objects that caught the focus of the main window. When a user tried to swipe and touch the slide, the touch event didn’t pass to the window context, and the Slick slider handler couldn’t receive the data of the event. An empty div tag, stretched above the embedded object, could resolve this problem, but in that case the user would not be able to start a Youtube clip. When the user would have tried to touch the play button, he would touch the empty div tag, not the Youtube embedded object.

After a couple of hours of googling, I found the solution. YouTube provides the API for their embedded clips, and you can control almost everything in the embedded object. You can programmatically change the volume, the timecode, the state – play/stop, etc. By the link, you can find the description of this API.

Secondly, I joint the idea of an empty div tag and the ability to control an embedded object programmatically with JavaScript. It means when the user touches the empty div tag, you can programmatically handle the event, and based on the state of the YouTube embedded clip you can start to play or stop playing the clip. So, I have made a simple example with the solution.

Explaining of the solution:

1. You need to make a simple markup, like this:

<div class="video-review-slick-slider">
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video1"></div>
          <div class="player-init" id="video1" data-yt-id="f9O3yURTBCM"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video2"></div>
          <div class="player-init" id="video2" data-yt-id="nhm8aal5F1c"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video3"></div>
          <div class="player-init" id="video3" data-yt-id="JkI2wJQ5YcM"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video4"></div>
          <div class="player-init" id="video4" data-yt-id="ZA4lISqqUXU"></div>
        </div>
           
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video5"></div>
          <div class="player-init" id="video5" data-yt-id="NIjMeUDurrk"></div>
        </div>
</div> 

The div tag with class video-review-slick-slider is a root container for the slider. It contains the slides with divs. The first of these divs is a gap or an empty div tag. This div has attributes – data-action-type that has the state of the embedded YouTube object. Also, this div has data-target-id that holds the id of the second div that used to make a YouTube embedded object. The second div has attributes like id and data-yt-id, which means the id for data-target-id of the previous div and the id of the YouTube video clip.

2. Next step, you need to initialize the YouTube API as it specified in the documentation:


var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

3. After that, you need to write a simple function that contains logic with the initialization of the YouTube embedded clips and attaches the handle for the empty div tag with that class gap. The function is below.


var players = {};

function init_players()
{
  $('.player-init').each(function() {
    
    if(!$(this).data('initialized'))
    {
    
      let player_cont_id = $(this).attr('id');
      let yt_video_id = $(this).data('yt-id');
      
      if(player_cont_id && yt_video_id)
      {
        
        players[player_cont_id] = new YT.Player(player_cont_id, {
          videoId: yt_video_id,
          height: '380',
          width: '100%'
        });
        
      } else {
        
        if(player_cont_id == '')
        {
          player_cont_id = 'video_'+Math.random(999,9999);
          $(this).attr('id',player_cont_id);
          $(this).prev().data('target-id',player_cont_id);
          
          players[player_cont_id] = new YT.Player(player_cont_id, {
            videoId: yt_video_id,
            height: '380',
            width: '100%'
          });
        }
      }
      
      
      $(this).data('initialized',true);
    }
  });

  $('.video-review-slick-slider .slide .gap').off('click.video');
  $('.video-review-slick-slider .slide .gap').on('click.video',function() {
    
    var target_id = $(this).data('target-id');
    var action_type = $(this).data('action-type');
    
    if(typeof(players[target_id]) == 'object')
    {
        if(action_type == 'play')
        {
          players[target_id].playVideo();
          $(this).data('action-type','pause');
        }
        
        if(action_type == 'pause')
        {
          players[target_id].pauseVideo();
          $(this).data('action-type','play');
        }
    }
    
  });
}

In this function:

  • Each div element with class player-init is being checked if it is initialized, and if it is not initialized then initialization starts. Then two variables are filled with the values of the id container for the embedded YouTube player and the id that is the YouTube clip id. However, when the Slick slider runs, it clones the slides for the infinity swiping. So, when the Slick slider does this, it is checking the id attribute on uniqueness. If the id already exists, then it fills the attribute with an empty value, so in the function above there is check for this case. The id attribute with an empty value is being filled with an automatically generated value and also being assigned to the gap element attribute data-target-id.
  • The two variables are used to create a new YouTube (YT) object that is added to the global variable players.
  • The click handler event is being attached to the gap elements. This handler contains a simple check based on the state of the YouTube embedded object using a data-action-type attribute./li>

4. Having defined the main function for the YouTube objects, it’s time to define the function that will be called when the YouTube API is loaded on the page. The function name is onYouTubeIframeAPIReady, and it is described in the documentation to the API.

function onYouTubeIframeAPIReady() {
  init_players();
}

5. The final step is writing the definition for the Slick slider initialization.


$('.video-review-slick-slider').slick({
  slidesToShow: 1,
  slidesToScroll: 1,
  dots: false,
  arrows:true,
  centerMode: false,
  swipe: true,
  focusOnSelect: true,
  centerPadding: '0px',
  adaptiveHeight: false,
  responsive: [{
      breakpoint: 768,
      settings: {
        arrows: false,
        centerMode: true,
        centerPadding: '40px',
        slidesToShow: 1,
        dots: true
      }
    },
    {
      breakpoint: 480,
      settings: {
        arrows: false,
        centerMode: true,
        centerPadding: '40px',
        slidesToShow: 1,
        dots: true
      }
    }
  ]
});

6. But in one row with the scripts, it is needed to add css styles for the slides and the gap elements. They are below:


.video-review-slick-slider .slide
{
  position: relative;
}

.video-review-slick-slider .slide iframe
{
  position: relative;
  z-index: 2;
  padding: 0 10px;
  margin: 0 10px;
  max-width: 95%;
}

.video-review-slick-slider .slide .gap
{
  position: absolute;
  top: 50px;
  left: 0px;
  width: 100%;
  height: 65%;
  z-index: 10;
}

In the style definition for the gap element there are important properties like height and top. In this case, they allow the user to use the control elements of the YouTube embedded objects like – play button, pause button, settings button, volume bar, timecode bar, etc.; because the gap element provides only the play or stop functionality.

7. But when you run this code on your page, you will notice that the YouTube videos do not stop playing after a swiping gesture. It means there will be a lot of YouTube clips playing simultaneously. To prevent this behaviour, you can just add a simple handler to the swipe event of the Slick slider before its initialization; the function handler is below:


$('.video-review-slick-slider').on('swipe', function(event, slick, direction) {
  
  $('.video-review-slick-slider .gap').data('action-type','play');
  for(object in players)
  {
    let player = players[object];
    player.pauseVideo();
  }

});

In this handler, after a swiping gesture all the YouTube clips are stopped.

The working example of this solution is available by the link.