Search code examples
javascripthtmlcontenteditable

How to save changes on multiple contenteditable elements?


I want to make my photo captions editable and then save the changes in localstorage. It works for the first photo but when I want to edit another, changes are saved to this first photo and not the one I edited. Here's part of my code where I display photos with captions:

<div class="gallery">
   <a href="<?=$path1; ?>">
      <img src="<?= $path1; ?>">
   </a>
   <div id="contenteditable">
      <p id="caption-photo" contenteditable> <?=pathinfo($path1, PATHINFO_FILENAME)?></p>
   </div>
</div>

And it is my js code to save the changes:

const editables = document.querySelectorAll("[contenteditable]");
        editables.forEach(el => {
        el.addEventListener("blur", () => {
            localStorage.setItem("dataStorage-" + el.id, el.innerHTML);
        })
        });
        for (var key in localStorage) {
            if (key.includes("dataStorage-")) {
                const id = key.replace("dataStorage-","");
                document.querySelector("#" + id).innerHTML = localStorage.getItem(key);
            }
        }    

Solution

  • Okay lemme open your mind a bit ... also pardon me for the many answers, I just like comprehensive answers: Create a new php file with the name update_file.php and put it within the same directory. Then put the php code that handles the requests in this file. Why is this important? Because we are depending on the responseText being received from the php file which will tell us whether the request was successful or not. And, when the php that handles the request is within the same file as the html, the request will return a response containing the html content of the page --- You can experiment this yourself and see that if you put the code below within the same file, the responseText will be the whole code in the page, and that's not what we want, we want either 0 or 1 ... ok, enough said: Put this in update_file.php in the same directory as the current file:

    <?php
    
        if(isset($_GET["old_name"]) && isset($_GET["new_name"]) && isset($_GET["directory"]) && isset($_GET["extension"])){
            $old_name = $_GET["old_name"];
            $new_name = $_GET["new_name"];
            // The new values from the GET request
            $directory = $_GET["directory"];
            $extension = $_GET["extension"];
            // echo $directory;
            // echo $extension;
            // Concatenate the values with the directories
    
            // 1 thing to note, if the image is in the current directory,
            // the value you will receive for $directory will be "."
            // So the method below ensures flexibility with different directories
            $dot = ".";
            // $dot is added before the extension
            $slash = "/";
            // $slash is added after the directory name
            // if ".", it will be "./' -- for current directory
            // if "some/directory" , it will be "some/directory/"
            // Then add to the file name and the extension
            $full_oldname = $directory . $slash . $old_name . $dot . $extension;
            $full_newname = $directory . $slash . $new_name . $dot . $extension;
            echo rename($full_oldname, $full_newname);
        }
    
    ?>
    

    Then this is your current file:

    <?php
        $some_records = ["record1.jpeg", "uploads/record2.jpeg", "images/subdirectory1/record3.jpeg", "images/record4.jpg", "images/subdirectory2/record5.jpg"];
        // Assuming your directory has subdirectories
        $counter = 1;
    
    ?>
    
    <div class="gallery">
        <?php foreach ($some_records as $path) {
            echo '<a href="' . $path . '">
                <img src="' . $path . '">
            </a>
            <div id="contenteditable">
                <p id="caption-photo' . $counter . '" contenteditable>' . pathinfo($path, PATHINFO_FILENAME) . '</p>
                <input type="hidden" name="directory-"' . $counter . '" value="' . pathinfo($path, PATHINFO_DIRNAME) . '" />
                <input type="hidden" name="extension-"' . $counter . '" value="' . pathinfo($path, PATHINFO_EXTENSION) . '" />
            </div>';
            // Note the 2 hidden inputs above
            $counter++;
        }
        ?>
    </div>
    
    <script>
        function update_file(old_name, new_name, directory, extension) {
            // The function has been updated with the new parameters
            var update = new XMLHttpRequest();
            update.open("GET", "update_file.php?old_name=" + old_name + "&new_name=" + new_name + "&directory=" + directory + "&extension=" + extension, true);
            // The XHR request has also been updated with the new values
            update.onreadystatechange = function() {
                if (this.readyState == this.DONE && this.status == 200) {
                    if (this.responseText != null) {
                        console.log(this.responseText);
                        // This will either be 0 or 1, if correct values have been passed to it
                        if(this.responseText == 1){
                            console.log("Renamed successfully!");
                        }
                        else {
                            console.log("Error renaming!");
                        }
                    }
                    else {
                        console.log("Not sent!!");
                    }
                }
            };
            update.send();
        }
    
        document.addEventListener("DOMContentLoaded", function(){
            const editables = document.querySelectorAll("[contenteditable]");
            editables.forEach(el => {
                var curr = el.innerHTML;
                // The next element after "el" is the first hidden input with the directory
                var dir = el.nextElementSibling.value;
                // The next element after the above hidden input is the second hidden input with the extension
                // Note the 2 ".nextElementSibling" accessors
                var ext = el.nextElementSibling.nextElementSibling.value;
                // To get the value of any input(that can lie within a form control) use "[element].value'
    
                console.log(ext);
                console.log(dir);
                el.addEventListener("blur", () => {
                    localStorage.setItem("dataStorage-" + el.id, el.innerHTML);
                    console.log(el.innerHTML);
                    update_file(curr, el.innerHTML, dir, ext);
                })
            });
            for(var i = 1; i < editables.length+1; i++){
                console.log("dataStorage-caption-photo" + i + " => " + localStorage.getItem("dataStorage-caption-photo" + i));
            }
        });
    </script>
    

    Also read the comments, the main points here are:

    1. How to add the hidden input elements with the respective values
    2. How to access the values with javascript
    3. How to add them to the XHR request and then capture them with php
    4. Finally, how to concatenate them and get your full path

    Hope this helps


    Here is some developer tools you can be using, I'm not sure which browser you are using but you might find such a similar thing, go to the console tab then click on settings and enable logging of XMLHttpRequests Options to be logged Enable logging of XHRequests You will be able to see the requests right after you remove focus on any contenteditable element and confirm whether they are being sent