Creating a JS array from PHP array (2-dimensional)

I am trying to convert a 2-dimensional PHP array to a JS array. I thought json_encode was the way to go, but when I run my code I’m getting the following console error:

TypeError: sluglist.forEach is not a function

I’m not sure if my error is in creating the array or the way in which I’m trying to use it!

<?php
$list = array();
$dir = '../galleries';
$galleries = scandir($dir);
foreach ( $galleries as $gallery ) {
  if ( $gallery !== '.' && $gallery !== '..' ) {
    $dir2 = '../galleries/'.$gallery.'/captions';
    $photos = scandir($dir2);
    foreach ( $photos as $photo ) {
      if ( $photo !== '.' && $photo !== '..' ) {
        $slug = pathinfo($photo)['filename'];
        $list[$gallery][$slug] = null;
      }
    }
  }
}
var_dump($list);
?>
<script>
var sluglist = <?= json_encode($list) ?>;
sluglist.forEach(function(el) {
  console.log(el);
});
</script>

Hey Gandalf,

What is getting output by the PHP script?

Replace your current script block with:

console.log(<?= json_encode($list) ?>);

What does that output?

1 Like

Hi Pullo, thanks. My array looks like this:

array(5) {
  ["1920s - 1930s"]=>
  array(1) {
    ["1920a"]=>
    NULL
  }
  ["1940s"]=>
  array(1) {
    ["19463buntitled"]=>
    NULL
  }
  ["1950s"]=>
  array(2) {
    ["1958CelebratingHooligans"]=>
    NULL
    ["1958WholeSchool"]=>
    NULL
  }
  ["1960s"]=>
  array(1) {
    ["1961a"]=>
    NULL
  }
  ["Hatfield Peverel"]=>
  array(1) {
    ["hp1"]=>
    NULL
  }
}

Console log output:

Object { "1920s - 1930s": {…}, 1940s: {…}, 1950s: {…}, 1960s: {…}, "Hatfield Peverel": {…} }

That’ll be your problem then. You cannot use forEach to iterate over an object (it’s an array method).

The MDN documentation explains more generally how to deal with objects and their properties.

In your case, something like:

const sluglist = <?= json_encode($list) ?>;
for (let [key, value] of Object.entries(sluglist)) {
  console.log(key, value);
}

should work.

Although please be aware that a key consisting of numbers and letters needs to be in quotes, i.e.:

1940s: {…}

needs to be:

"1940s": {…}
1 Like

Thanks squire.

It seems to be json_encode that’s removing them.

I’m wondering why the result comes out as an object, not an array.

Anyways, I’ll have another play in the morning.

I think PHP is “fixing” the JavaScript array for you. JavaScript arrays have sequential numeric keys:
0, 1, 2, ... n
Instead of overwriting the keys or throwing an error, PHP in its infinite wisdom has read your mind and knows you meant it to be an object.

1 Like

Thanks @Mittineague and @James_Hibbard. I may need to rethink this…

This post may be useful:

1 Like

If it were me I would likely just change the code so it would work with an object.

I guess it would be possible to change the structure into an array of nested arrays, or an array of nested objects, or some mix of some sort, but it feels like that could get messy and end up being more difficult to work with.

1 Like

I have made some progress with my project. The idea is that the "gallery"s should be given as options within a form select tag. When a gallery is selected, the available "slug"s should be offered as options in the next select tag.

I have the first stage working, but I am struggling to extract the slug options in order to display them.

The console log reads:

Object { 1940s: {…}, 1950s: {…} }
1940s Object { 19463buntitled: null }
1950s Object { 1958CelebratingHooligans: null, 1958WholeSchool: null }

My updated code

<?php
$list = array();
$dir = '../galleries';
$galleries = scandir($dir);
foreach ( $galleries as $gallery ) {
  if ( $gallery !== '.' && $gallery !== '..' ) {
    $dir2 = '../galleries/'.$gallery.'/captions';
    $photos = scandir($dir2);
    foreach ( $photos as $photo ) {
      if ( $photo !== '.' && $photo !== '..' ) {
        $slug = pathinfo($photo)['filename'];
        $list[$gallery][$slug] = null;
      }
    }
  }
}
#var_dump($list);
?>
<form method="post">
  <div>
    <label for="gallery">Gallery:</label>
    <select id="gallery" name="gallery">
      <option>--Select--</option>
    </select>
  </div>
  <div>
    <label for="slug">Slug:</label>
    <select id="slug" name="slug">
    </select>
  </div>
</form>
<script>
console.log(<?= json_encode($list) ?>);

const sluglist = <?= json_encode($list) ?>;
for (let [key, value] of Object.entries(sluglist)) {
  console.log(key, value);
  document.getElementById("gallery").innerHTML += "<option>"+key+"</option>";
}

document.getElementById("gallery").addEventListener("change", function() {
  let gslug = document.getElementById("slug");
  gslug.innerHTML = "<option>--Select--</option>";
  let vgall = document.getElementById("gallery").value;
  console.log(vgall);
  //need a loop here to list all options for selected gallery (vgall)
});
</script>

The slugs being the object keys?

You’ll need to convert those keys that are a mixture of numbers and letters (1940s etc) to use quotes.

1 Like

The slugs are eg 1958CelebratingHooligans and 1958WholeSchool in

1950s Object { 1958CelebratingHooligans: null, 1958WholeSchool: null }

How do I go about quoting them…

You’ll need to do that in your PHP

1 Like

Appending a " before and after the slug gives me

1940s Object { "\"19463buntitled\"": null }
1950s Object { "\"1958CelebratingHooligans\"": null, "\"1958WholeSchool\"": null }

on the console log. I kinda understand why, but I don’t think that’s what I want!?

Can you post a minimal PHP snippet that will allow me to reproduce.

<?php 
$list = ... <-- what goes here?
var_dump($list);
?>
1 Like

If you want a JS array from PHP array then I would start with something simple that works then increase complexity:

<?php 
	declare(strict_types=1);
	error_reporting(-1);
	ini_set('display_errors', 'true');

  $aCoords = [];

  $files 	= glob('*.php');
 	fred($files);

 	$iMax = 0;
  $pins =  "var pins = [ \r" ;
  foreach( $files as $id => $file ):
    $uFile = strtoupper($file);
    if($iMax++ < 4):
	    $pin = <<< ______TMP
	    	[ "$uFile", "$file" ], \r
______TMP;
	    $pins .= $pin;
	  endif;  	
  endforeach;  
  $pins .= "];";

	fred($pins);

// ONLY LIEFEED AFTER ____TMP
	$jScript = <<< ____TMP
		<script>
			$pins
		  for(i2 = 0; i2 < pins.length; i2++)
		  {
		  	alert( 'pins[i2][1] ==> ' + pins[i2][1] );
		  }

			pins.forEach
			(
				function(pins)
				{
  				console.log(pins);
  			}
  		)	
		</script>
____TMP;
// ONLY LIEFEED AFTER ____TMP;
	echo $jScript;

//=========================================
function fred($val='')
{
		echo '<pre style="width:88%; margin:2em auto; border:solid 2px red">';
			print_r($val);
		echo '</pre>';		
}

Edit:

Output:

1 Like

The array is created by nested scandir()s. This is the dump:

array(2) { ["1940s"]=> array(1) { ["19463buntitled"]=> NULL } ["1950s"]=> array(2) { ["1958CelebratingHooligans"]=>; NULL ["1958WholeSchool"]=> NULL } }

My revised (again) PHP, HTML and JS code is:

<?php
$list = array();
$dir = '../galleries';
$galleries = scandir($dir);
foreach ( $galleries as $gallery ) {
  if ( $gallery !== '.' && $gallery !== '..' ) {
    $dir2 = '../galleries/'.$gallery.'/captions';
    $photos = scandir($dir2);
    foreach ( $photos as $photo ) {
      if ( $photo !== '.' && $photo !== '..' ) {
        $slug = pathinfo($photo)['filename'];
        $list[$gallery][$slug] = null;
      }
    }
  }
}
var_dump($list);
?>
<form method="post">
  <div>
    <label for="gallery">Gallery:</label>
    <select id="gallery" name="gallery">
      <option>--Select--</option>
    </select>
  </div>
  <div>
    <label for="slug">Slug:</label>
    <select id="slug" name="slug">
    </select>
  </div>
</form>
<script>
console.log(<?= json_encode($list) ?>);

const sluglist = <?= json_encode($list) ?>;
for (let [key, value] of Object.entries(sluglist)) {
  console.log(key, value);
  document.getElementById("gallery").innerHTML += "<option>"+key+"</option>";
}

document.getElementById("gallery").addEventListener("change", function() {
  let gslug = document.getElementById("slug");
  gslug.innerHTML = "<option>--Select--</option>";
  let vgall = document.getElementById("gallery").value;
  console.log(vgall);
  for (let [key2, value2] of Object.entries(vgall)) {
    console.log(key2, value2);
    document.getElementById("slug").innerHTML += "<option>"+key2+"</option>";
  }
});
</script>

The second loop provides options but just 0, 1, 2, 3 and 4.

Can you post some code, so that I can recreate the value of $list as it is for you? (not a dump)

Doing:

$list = array(2) { ["1940s"]=> array(1) { ["19463buntitled"]=> NULL } ["1950s"]=> array(2) { ["1958CelebratingHooligans"]=>; NULL ["1958WholeSchool"]=> NULL } }

obviously won’t work :slight_smile:

If I can create a PHP script where I have the same value as you in the $list variable, I’ll be in a much better position to help.

Try using PHP glob(…) instead of scandir(…) because glob(…) returns the same array results except for . and …

This eliminates the script checks for the periods and simplifies the code.

Edit:
For recursive sub directories use PHP is_dir(…)

1 Like

I’m struggling to recreate an array like what scandir() creates!

I think this should do the job (but I’m not sure)

$list = array(
    "1940s", array("19463buntitled", null), 
    "1950s", array("1958CelebratingHooligans", null), array("1958WholeSchool", null)
    );