Search code examples
phplaravellaravel-blade

How to display an array representing a folder structure in Laravel Blade templates?


Let's say I have a PHP array of arrays representing a folder structure that goes like this:

array:5 [▼
  0 => array:2 [▼
    "size" => "9.8 MiB"
    "name" => "Game.exe"
  ]
  1 => array:2 [▼
    "size" => "3.9 MiB"
    "name" => "Launcher.exe"
  ]
  2 => array:2 [▼
    "size" => "2 MiB"
    "name" => "USRDIR/mask.mpk"
  ]
  3 => array:2 [▼
    "size" => "1 MiB"
    "name" => "USRDIR/subs.hrt"
  ]
  4 => array:2 [▼
    "size" => "10 MiB"
    "name" => "USRDIR/fullhd/footage.mkv"
  ]

And I want to display this information as a folder structure in HTML using an unordeded list of unordered lists, as such:

    <ul class="root-folder">
        <ul class="folder"><span>FOLDERNAME</span>
            <ul class="folder"><span>FOLDERNAME</span>
                <li class="file">FILENAME</li>
            </ul>
            <li class="file">FILENAME</li>
            <li class="file">FILENAME</li>
        <ul>
        <li class="file">FILENAME</li>
        <li class="file">FILENAME</li>
    </ul>

The first 2 arrays have a 'name' value formed by a string with the following format: "filename.fileextension", meaning these are files in the root this folder. For these I can just throw a <li> element displaying its values inside the master <ul>. However, the third array has a 'name' value of "folder/filesize.fileextension", meaning that to represent this visually, I have to make a new <ul> with new files inside as <li> elements. There's also the fact that the array will name the same folders multiple times if said folders have more than 1 file in them. Then the last array has 2 folders before the file. What would my syntax look like, and am I doing this in a controller and passing an array to the view to display, or in the Blade template by just passing it the original array?

For now this is what I have but I'm kinda lost:

Controller:

    $fileArray = [];
    foreach ($files as $key => $file) {
        array_push($fileArray, $file);
        $fileArray[$key]['size'] = formatBits($file['size']);
        $fileArray[$key]['name'] = explode('/', $file['name']);
    }

    return view('single', [
        'fileArray' => $fileArray
    ]);

Blade template:

    <ul>
        @foreach ($fileArray as $file)
            @if (count($file['name']) > 1)
                @foreach ($file['name'] as $folder)
                    @if ($folder == end($file['name']))
                    <li>
                        <span class="file-icon">🗋</span>{{ $folder }}<span class="file-size">({{ $file['size'] }})
                    </li>
                    @else 
                    <li>
                        <span class="file-icon">🗀</span>{{ $folder }}
                    </li>
                    @endif
                @endforeach
            @else
            <li>
                <span class="file-icon">🗋</span>{{ $file['name'][0] }}<span class="file-size">({{ $file['size'] }})
            </li>
            @endif
        @endforeach
    </ul>

This creates a <li> element for every instance of a string that got separated from the original pathname string in the controller. You should be able to see how this is problematic, as it creates a new <li> for every time a folder name is mentioned.


Solution

  • I have a short idea:

    $files = Paths::all()->toArray();//lets say you do this to have the array you told us ready
    
    $fileArray = [];
    foreach ($files as $file) {
        if(str_contains($file['name'], '/')){
            $split = explode('/', $file['name']);
            $name = last($split);
            $path = array_chunk($split, sizeof($split)-1)[0];
        }else{
            $name = $file['name'];
            $path = [];
        }
        $temp = [['name'=>$name, 'size' => formatBits($file['size'])]]; //single value in array too to ease future headaches
        while( count($path) > 0 ){
            $temp2 = [];
            $temp2[ '/'.last( $path ) ] = $temp;
            array_pop( $path );
            $temp3 = $temp2;
            $temp2 = $temp;
            $temp = $temp3;
        }
        $fileArray = array_merge_recursive($fileArray,$temp);
    }
    
    return view('single', compact('fileArray'));
    

    In your blade you need to check if the key is in folder syntax.

    @php
    function compareFiles(array $a, array $b){
        if(isset($a['name']) && isset($a['name'])){
            return strcmp($a['name'],$b['name']);
        }else{
            return 0;
        }
    }
    
    function fileSys($fileArray) {
        ksort($fileArray, SORT_STRING); //sorting 'folders' (by key)
        uasort($fileArray, "compareFiles"); //sorting 'files' (by value)
        foreach($fileArray as $key => $value){
            if(str_contains(strval($key), '/')) {
                echo "<li><span class=\"file-icon\">🗀</span>".substr($key, 1)."<ul>";
                filesys($fileArray[$key]);
                echo "</ul></li>";
            }
            else {
                echo "<li><span class=\"file-icon\">🗋</span>".$value['name']."<span class=\"file-size\">(".$value['size'].")</li>";
            }
        }
    }
    @endphp
    <ul>
        {{ fileSys($fileArray) }}
    </ul>