Link to home
Start Free TrialLog in
Avatar of Crazy Horse
Crazy HorseFlag for South Africa

asked on

how to post local storage data with multiple records to php via jQuery/ajax

I have a simple form where a user fills in a few text fields. There is also a button which when pressed, launches a modal with images in it. The user can select multiple images and then close the modal. The image names are stored with HTML 5 local storage.

The user should be able to then submit the form with the text fields inserting into one database table, and the image names inserted into another table. The issue I am having is with the foreach loop when inserting the image names. I think that the issue may be the way in which the image names are sent via ajax and the format in which they are sent.

This code displays the images and after selecting and clicking the submit button for the image modal, the names are stored in local storage and outputted on the screen.

$(function () {
        $.get('t2637.php', function (resp) {
            $('#gallery').html(resp);
        });
        $('#gallery').on('click', 'img-box', function (i, e) {
            $(e).toggleClass('selected');
        });
        $(".btn-danger").on("click", function (e) {
            e.preventDefault();
            var form = $("#imgs").serialize();
            $.ajax({
                    url: 'reflect.php',
                    type: 'POST',
                    data: form,
                })
                .done(function (data) {
                    localStorage.setItem("picnames", data);
                    $("#filenames").empty().append(localStorage.getItem("picnames"));
                });
        });
    });

Open in new window


This is the code which POSTS the whole form to the php to insert the data:

$( "#add-accom" ).on("click", function(e) {
        e.preventDefault();
        var form = $( "#accom-form" ).serialize();
        var pics = localStorage.getItem("picnames");
            $.ajax({
                url: 'functions/add-accom.php',
                type: 'POST',
                dataType: 'json',
                data: $.param($( "#accom-form" ).serializeArray().concat([{name: 'pics', value: pics}])),
                beforeSend: function() {
                    $( ".form-error" ).hide();
                    $( ".form-success" ).hide();
                    $( "#add-accom" ).hide();
                    $( ".submitting" ).html("<img src='img/spinner.gif'>");
                },
            })
            .done(function(data) {
            // validation and insert

Open in new window


And here is the Php in add-accom.php

     
  $name = trim($_POST['name']);
        $description = trim($_POST['description']);
        $stmt = $link->prepare("INSERT INTO `accommodation` (`category`, `name`, `description`) VALUES (?, ?, ?)");
        $stmt->bind_param("sss", $_POST['category'], $name, $description);
        $stmt->execute();
        $stmt->close();
        $last_id = $link->insert_id;

        foreach($_POST['pics'] as $pics) {

            $stmt = $link->prepare("INSERT INTO `accom_pics` (`accom_id`, `pic_name`) VALUES (?, ?)");
            $stmt->bind_param("is", $last_id, $pics);
            $stmt->execute();
            $stmt->close();
        }

Open in new window



The error is: Invalid argument supplied for foreach()

If I var_dump $_POST then I get:

 array(5) {
  ["category"]=>
  string(7) "some category"
  ["name"]=>
  string(4) "test"
  ["description"]=>
  string(16) "testing the form"
  ["form_token"]=>
  string(44) "3jX2fabfk7emVzQ9tCJcuNEbzIA52XdrgDn+Cm+nT1k="
  ["pics"]=>
  string(54) ""asparagus-2178164_640.jpgshire-horse-2197214_640.jpg""

Open in new window


reflect.php

foreach($_POST['pics'] as $pics) {
    echo $pics;
}

Open in new window

Avatar of HainKurt
HainKurt
Flag of Canada image

$("#filenames").empty().append(localStorage.getItem("picnames"));

Open in new window


what is filenames? a hidden variable?
you should concat them with "," I guess here...
then on php side, you split with "," and loop...
Avatar of Crazy Horse

ASKER

#filenames is just a div that I append the selection to. So, if I choose 2 images, the names of the 2 images will append to the div.
.done(function (data) {
                    localStorage.setItem("picnames", data);
                    $("#filenames").empty().append(localStorage.getItem("picnames"));
                });

Open in new window


and what is data look like here? is it a json object?
Data is just:

foreach($_POST['pics'] as $pics) {

     echo $pics;
}

Open in new window

Avatar of Scott Fell
After reading, your issue is:

The error is: Invalid argument supplied for foreach()

Your data does not show that as an array or object. It's just text. So you would not use foreach in that case.  Is this an example or will there sometimes be an array?
You could use implode http://php.net/manual/en/function.implode.php to create an array if the values come through as delimited. That way even if there is only  one item, you can use foreach.
What is supposed to happen is that a user fills in a form, some text fields and then has to choose multiple images to upload. When they select multiple images, the image names are stored in local storage to be used later.

sessionStorage.setItem("picnames", data);

Open in new window


data is just the foreach loop that is found in reflect.php which is where the ajax call goes.

reflect.php

foreach($_POST['pics'] as $pics) {

     echo $pics;
}

Open in new window


So, I think in essence I am storing echo $pics in the local storage?
But, the thing is, If I change reflect.php to:

foreach($_POST['pics'] as $pics) {
			
			$stmt = $link->prepare("INSERT INTO `accom_pics` (`pic_name`) VALUES (?)");
			$stmt->bind_param("s", $pics);
			$stmt->execute();
			$stmt->close();
		}

Open in new window


Then it creates as many rows in the db as pics I selected which is 100% correct. So, why does it work for the insert record but doesn't work again later when I try to post the whole form?
 $.ajax({
                    url: 'reflect.php',
                    type: 'POST',
                    data: form,
                })
                .done(function (data) {
                    localStorage.setItem("picnames", data);
                    $("#filenames").empty().append(localStorage.getItem("picnames"));
                });

Open in new window


IF that is your ajax, then data needs to be

"picname1.jpg;picname2.jpg"

Your php that accepts the form data will look like
<?php
  echo implode(";",$_POST['pics']);
?>

Open in new window

SOLUTION
Avatar of Scott Fell
Scott Fell
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Let's go through this step by step.
$(function () {
        $.get('t2637.php', function (resp) {
            $('#gallery').html(resp);
        });
        $('#gallery').on('click', 'img-box', function (i, e) {
            $(e).toggleClass('selected');
        });
        $(".btn-danger").on("click", function (e) {
            e.preventDefault();
            var form = $("#imgs").serialize();
            $.ajax({
                    url: 'reflect.php',
                    type: 'POST',
                    data: form,
                })
                .done(function (data) {
                    localStorage.setItem("picnames", data);
                    $("#filenames").empty().append(localStorage.getItem("picnames"));
                });
        });
    });

Open in new window


Based on our earlier interactions I understand where the reflect script comes from but not sure why it is being used in this way.
If I remember correctly the code is based on this sample (http://www.marcorpsa.com/ee/t2637.html) a simple gallery selection tool. In that sample the selected images are sent to a server script that just reflects the selection - a placeholder for a more useful script that would actually process the files.

Your requirements appear to have changed to where you want to keep the list of images locally to be submitted with the form.

For this it is not necessary to go back to the server

Here is a new sample that does the the following
1. Opens gallery in a modal
2. When you click the save button it writes the selected items as a stringified array of objects to local storage
3. When you submit your form it dynamically adds a bunch of inputs to the form using the data from local storage

I have pointed the script to a reflect script again to demonstrate the output that will be produced - you would change this to your form processing script.

The HTML looks likethis
  <form action="reflect.php" method="post" id="mainForm">
    <input type="text" class="form-control" placeholder="Other usefuldata" name="usefuldata"/>
    <button type="submit" class="btn btn-primary">Submit</button>
  </form>
  <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#gallery">
    Launch demo modal
  </button>

Open in new window

Modal
<div class="modal fade" id="gallery" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <form id="gallery-body" class="modal-body">
        
      </form>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary" id="saveSelection">Save</button>
      </div>
    </div>
  </div>
</div>

Open in new window

jQuery
<script>
$(function() {
  // Handler for main form submit - add <input>
  // fields for each gallery image
  $('#mainForm').submit(function() {
    // get data from localStorage and parse to array
	// loop through and append <input>'s
    var gallery = JSON.parse(localStorage.getItem('gallery'));
    for(var i = 0; i < gallery.length; i++) {
      $(this).append(
        $('<input>',{type: 'text', name: gallery[i].name}).val(gallery[i].value)
      );
    }
    
    return true;
  });
  
  // Load gallery into modal
  // We might want to change this to use the show event
  // in the event that a gallery can change between page
  // loads. Simple implementation for demonstration
  // purposes
  $.get('t2637.php',function(resp) {
    $('#gallery-body').html(resp);
  });
  
  // Toggle selected images in gallery
  $('#gallery').on('click','img-box', function(i,e) {
    $(e).toggleClass('selected');
  });
  
  // Save selection to local storage
  $('#saveSelection').click(function() {
    var gallery = JSON.stringify($('#gallery-body').serializeArray());
    localStorage.setItem('gallery',gallery);
  });
});
</script>

Open in new window

Updated sample here
Hi Julian. In a Live chat we had the other day, it looked like he was using the reflect.php only to return the value of the names of the images that were selected. Then sending other form data and thus had two ajax requests.  What I have suggested is to grab the images from the checked fields using jquery, send that info to local storage, then submit the form fields to the processing page.
@Scott - the code originally comes from a request to me some time back - the reflect was to show the items selected. Subsquently he wanted to be able to have the selection popup in a modal. The next step was whether to AJAX the selection to the server and save in the SESSION - I recommended against it and suggested localStorage.
The implementation appears to be a merger of the two - but the call to the reflect is no longer necessary with the selection - as all it is doing is reflecting what was selected.
This is a bit complicated because some of this discussion has taken place in chats and emails so the whole picture is a bit segmented.

I think the implementation above is what is asking for - but we will have to see what he comes back with.

@Black Sulphur.

Just occurred to me you would want the images in an array rather than as I have it above which was using the name value of the selection object.

This can be simply by doing tis
  $('#mainForm').submit(function() {
    // get data from localStorage and parse to array
	// loop through and append <input>'s
    var gallery = JSON.parse(localStorage.getItem('gallery'));
    for(var i = 0; i < gallery.length; i++) {
      $(this).append(
        $('<input>',{type: 'text', name: 'gallery[' + i + ']'}).val(gallery[i].value)
      );
    }

Open in new window

Sample updated.
Hi Scott,

Julian actually wrote the original script which was a starting point to show me how to select and deselect images.

http://www.marcorpsa.com/ee/t2637.html

I have been trying to expand on that by putting it into a modal and then saving the images to local storage to send along with other data later. Julian is 100% correct in that I did suggest sessions but he advised I rather use local storage.
Just a quick note before I come back regarding the solution. Sorry if this is muddled. I have been so desperate to do this I have got anyone I could on the bandwagon at whatever times I could when whoever was available.

I acknowledge that the way I explain things isn't the best, especially because sometimes I start with something and then turn it into something else the more I think about it.

Anyway, to recap, what I am trying to do is have a form with a few text fields and a button that says for example, "select images". So, the user fills in the few text fields and then clicks on the select images button which launches the modal. The user then selects multiple images and clicks "save" or "select", the name doesn't matter. The idea was that I somehow needed to save those names for later use so that when the whole form is submitted, it submits the text field entries plus the image names which are all to be stored in a database.

I could be wrong about this, but it looks like the image selections are done with checkboxes which are just cleverly styled so they don't actually look like checkboxes. I noticed initially with the modal that if I select images and close the modal, opening reopening the modal my image selections are still there meaning I might not even have to store the images in any type of storage because they are already checked checkboxes which seems to retain my selections after closing the modal. Not having to store the image names means that I could achieve what I want with less code.

But, I could be horribly wrong here.
Maybe I did not understand correctly.  It looked like you had form fields in the modal the represented the images.  You then sent the selected images to a php page via ajax.  The ajax request returned the selected image names where you then sent that data to local storage.  There was an additional ajax request to update the database.

You were having trouble getting the returned data to loop through and store in local storage. What we found out is you were trying to treat the returned data as an array when it is a string.  We worked on sending the data from the php page with a delimiter so you could use javascript to convert to an array then loop through.  At that point I realized you didn't need the one ajax request to return the data because you can use your jquery to find the selected items.
>  if I select images and close the modal, opening reopening the modal my image selections are still there

yes, that is what I mention in my last post the other day.  You need to listen for the modal to close and clear the contents of the modal.  

('#exampleModal').on('hidden.bs.modal', function (e) {
  //  clear contents of modal
})

Open in new window

What is the purpose of storing the text in local storage when you have the same data in your db?
Maybe so we are all on the same page I will post here what I had and what was going on:

This is the html form which has a select menu and 2 text fields, and a hidden field which generates a token for CSRF protection. The "filenames" div was just there to show the filenames in text format once they had been selected. This was really just for me to make sure that the image names were coming through after selection.

<form class="form-contact" id="accom-form">
	<div class="inputs-wrapper">
		<select name="category">
			<option value="" selected>Select Type</option>
			<option value="Chalets">Chalets</option>
			<option value="B&B">B&B</option>
		</select>
		<input type="text" placeholder="Name" name="name">
		<textarea name="description" rows="9" placeholder="Description"></textarea>
	</div>
	<input type="submit" value="Add" id="add-accom">
	<div id="filenames"></div>
	<button type="button" class="btn btn-primary btn-white" data-toggle="modal" data-target="#myModal">Add Images</button>
	<input type="hidden" name="form_token" id="form_token" value="<?php echo make_form_token(); ?>">
	<div class="submitting"></div>
	<div class="form-success"></div>
	<div class="form-error"></div>
</form>

Open in new window


And this is the script for that div. I was using jQuery to POST to the reflect script which ran a foreach loop to get the image names which I was just echoing out and then I also used this to store those names in local storage.

<script type="text/javascript">
	$(function () {
		$.get('t2637.php', function (resp) {
			$('#gallery').html(resp);
		});
		$('#gallery').on('click', 'img-box', function (i, e) {
			$(e).toggleClass('selected');
		});
		$(".btn-danger").on("click", function (e) {
			e.preventDefault();
			var form = $("#imgs").serialize();
			$.ajax({
					url: 'reflect.php',
					type: 'POST',
					data: form,
				})
				.done(function (data) {
					sessionStorage.setItem("picnames", data);
					$("#filenames").empty().append(sessionStorage.getItem("picnames"));
				})
			.fail(function (jqXHR, textStatus, errorThrown) {
				console.log(textStatus + ': ' + errorThrown);
				console.warn(jqXHR.responseText);
			});
		});
	});
</script>

Open in new window


Part of the above jQuery was also to display the images in the modal, in the "gallery" div.

<div class="modal fade" id="myModal" role="dialog">
	<div class="modal-dialog">
		<div class="modal-content">
			<div class="modal-header">
				<button type="button" class="close" data-dismiss="modal">&times;</button>
				<h4 class="modal-title">Modal Header</h4>
			</div>
			<div class="modal-body">
			<form id="imgs">
			<div id="gallery"></div>
			 <button type="submit" class="btn btn-danger">Submit</button>
				</form>
			</div>
			<div class="modal-footer">
				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
			</div>
		</div>
	</div>
</div>

Open in new window


Then the last step was to use jQuery and AJAX to post ALL of this data and insert into the database.

$( "#add-accom" ).on("click", function(e) {
		e.preventDefault();
		var form = $( "#accom-form" ).serialize();
		var pics = sessionStorage.getItem("picnames");
			$.ajax({
				url: 'functions/add-accom.php',
				type: 'POST',
				dataType: 'json',
				data: $.param($( "#accom-form" ).serializeArray().concat([{name: 'pics', value: pics}])),
				beforeSend: function() {
					$( ".form-error" ).hide();
					$( ".form-success" ).hide();
					$( "#add-accom" ).hide();
					$( ".submitting" ).html("<img src='img/spinner.gif'>");
				},
			})
			.done(function(data) {
				if(!data.success) {
					$( ".form-error" ).empty().append(data.message).fadeIn();
					$( ".submitting" ).hide();
					$( "#add-accom" ).show();
					
					
				} else {
					
					$( ".form-success" ).empty().append(data.message).fadeIn();
					$( ".submitting" ).hide();
					$( "#add-accom" ).show();
					document.getElementById("accom-form").reset();
					sessionStorage.clear();

				}
			})
		.fail(function (jqXHR, textStatus, errorThrown) {
				console.log(textStatus + ': ' + errorThrown);
				console.warn(jqXHR.responseText);
			});
	});

Open in new window


if($_POST) {

	$response = array();
	$message = "";

	if(!isset($_SESSION['token']) || $_POST['form_token'] !== $_SESSION['token']) {
		
		$message .= "Invalid token <br />";
	}
	
	if($_POST['category'] == "") {
	
		$message .= "Category required <br />";
	}
	
	if(empty($_POST['name'])) {
		
		$message .= "Accommodation name required <br />";
	}
	
	if(strlen($_POST['description']) < 10) {
		
		$message .= "At least 10 characters required for description <br />";
	}

	if($message) {
	
		$response['success'] = false;
		$response['message'] = $message;
	
	} else {
	
		$name = trim($_POST['name']);
		$description = trim($_POST['description']);
		$stmt = $link->prepare("INSERT INTO `accommodation` (`category`, `name`, `description`) VALUES (?, ?, ?)");
		$stmt->bind_param("sss", $_POST['category'], $name, $description);
		$stmt->execute();
		$stmt->close();
		$last_id = $link->insert_id;
		
		foreach($_POST['pics'] as $pics) {
			
			$stmt = $link->prepare("INSERT INTO `accom_pics` (`accom_id`, `pic_name`) VALUES (?, ?)");
			$stmt->bind_param("is", $last_id, $pics);
			$stmt->execute();
			$stmt->close();
		}
		
		
		$response['success'] = true;
		$response['message'] = "All good";
		
	}
	
	echo json_encode($response);
}

Open in new window

@Scott, none of the info is in the DB. I am trying to insert it into the DB.
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks Julian, moving the modal into the form has saved me a lot of headaches and now I have this working as I had wanted it to so far. I still have a long way to go to achieve exactly what I want as a whole but this has got me over a big hurdle. I really appreciate your help.

Also, thank you Scott. After getting stuck on this question I turned to live help yesterday as I saw that you were offering FREE help for premium members. I wasn't sure if I was a premium member or how that worked exactly so I gave it a go, and obviously I am a premium member as I was able to chat to you for a significant amount of time at not cost which is very good of EE to offer in general.

A big thanks to both of you for all your time!
You are welcome.