Autosave Draft of Form using PHP, jQuery, and Ajax Tutorial

In this tutorial, you will learn how to create an Autosave Draft of Form using PHP, MySQL Database, JS jQuery, and Ajax. The tutorial aims to provide IT/CS students and new programmers with a reference on how they can save automatically the form data. Here, are snippets that explain how are provided. A sample web application source code zip file that demonstrates the objective of this tutorial is also provided and is free to download.

Why do we need Autosave Draft?

Autosave Draft is one of a feature of the application form in web development. It is not required to implement this kind of feature to develop a web application. It is used for preventing data loss in any circumstances that may occur while the end-user is still encoding. With this, the data will be automatically saved on the database and marked as draft status even if the user hasn't submitted the form data yet.

How to implement or create Form Data Autosave Draft?

Using the PHP Language, MySQL Database, jQuery Library, and Ajax. The jQuery scripts will run a loop script that checks if there are any changes made on the form and executes an Ajax request to save the draft in the background. To achieve this feature, we can use JavaScript's setInterval() web API.

Application with Autosave Draft Snippets

Here are the snippets for building a simple web application that has an Autosave Draft Feature. The snippets load the libraries using CDNs which means that an internet connection is a must while running the scripts on your local computer or device.

Database and Schema

For this sample application, create a new MySQL Database named dummy_db. Next, use the following Table Schema to create the sample Posts Table.

Database Connection

Next, create a new PHP file and save it as db-connect.php. This file contains a PHP script that connects the application with the database.

  1. <?php
  2. $host = "localhost";
  3. $username = "root";
  4. $pw = "";
  5. $db_name = "dummy_db";
  6.  
  7. $conn = new mysqli($host, $username, $pw, $db_name);
  8.  
  9. if(!$conn){
  10. die("Database Connection Failed");
  11. }

Main Page Interface

Let's create the application's main page interface. Create a new PHP file and save it as index.php. This file contains the HTML and PHP scripts that display the list of posts from the database.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <meta charset="UTF-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Home | Autosave draft - PHP, jQuery, and Ajax</title>
  7. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  9. <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/js/all.min.js" integrity="sha512-naukR7I+Nk6gp7p5TMA4ycgfxaZBJ7MO5iC3Fp6ySQyKFHOGfpkSZkYVWV5R7u7cfAicxanwYQ5D1e17EfJcMA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  10. <script src="https://code.jquery.com/jquery-3.6.1.js" integrity="sha256-3zlB5s2uwoUzrXK3BT7AX3FyvojsraNFxCc2vC/7pNI=" crossorigin="anonymous"></script>
  11. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
  12. html, body{
  13. min-height:100%;
  14. width:100%;
  15. }
  16. tbody:empty:after{
  17. content:'No records found'
  18. }
  19. </style>
  20. </head>
  21. <nav class="navbar navbar-expand-lg navbar-dark bg-primary bg-gradient">
  22. <div class="container">
  23. <a class="navbar-brand" href="./">Autosave Draft</a>
  24. <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  25. <span class="navbar-toggler-icon"></span>
  26. </button>
  27. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  28. <ul class="navbar-nav me-auto mb-2 mb-lg-0">
  29. <li class="nav-item">
  30. <a class="nav-link active" aria-current="page" href="./">Home</a>
  31. </li>
  32. <li class="nav-item">
  33. <a class="nav-link" href="postform.php"><i class="fa fa-plus"></i>Create Post</a>
  34. </li>
  35.  
  36. </ul>
  37. </div>
  38. <div>
  39. <a href="https://sourcecodester.com" class="text-light fw-bolder h6 text-decoration-none" target="_blank">SourceCodester</a>
  40. </div>
  41. </div>
  42. </nav>
  43. <div class="container-fluid px-5 my-3" >
  44. <div class="col-lg-6 col-md-8 col-sm-12 mx-auto">
  45. <h3 class="text-center"><b>Post List</b></h3>
  46. <div class="d-flex w-100 justify-content-center">
  47. <hr class="w-50">
  48. </div>
  49. <div class="card rounded-0 shadow mb-3">
  50. <div class="card-body">
  51. <div class="container-fluid">
  52. <div class="table-responsive">
  53. <table class="table table-striped table-bordered">
  54. <tr class="bg-gradient bg-primary text-light">
  55. <th class="text-center">Title</th>
  56. <th class="text-center">Content</th>
  57. <th class="text-center">Status</th>
  58. <th class="text-center">Action</th>
  59. </tr>
  60. </thead>
  61. <?php
  62. require_once('db-connect.php');
  63. $posts = $conn->query("SELECT * FROM `posts` where `status` ='publish' or (`status` = 'draft' and `parent_id` = 0 ) ");
  64. while($row = $posts->fetch_assoc()):
  65. ?>
  66. <tr>
  67. <td class="text-truncate"><?= $row['title'] ?></td>
  68. <td class="text-truncate"><?= $row['content'] ?></td>
  69. <td class="text-truncate"><?= ucwords($row['status']) ?></td>
  70. <td class="text-center">
  71. <?php if($row['status'] == 'draft'): ?>
  72. <a href="postform.php?auto_id=<?= $row['id'] ?>" class="btn btn-outline-primary btn-sm rounded-0 py-0">Edit</a>
  73. <?php else: ?>
  74. <a href="postform.php?id=<?= $row['id'] ?>" class="btn btn-outline-primary btn-sm rounded-0 py-0">Edit</a>
  75. <?php endif; ?>
  76. </td>
  77. </tr>
  78. <?php endwhile; ?>
  79. </tbody>
  80. </table>
  81. </div>
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. </div>
  87. </body>
  88. </html>

Form Page Interface

Next, create a new PHP file and save it as postform.php. This file contains the HTML and PHP scripts that display the form and data. The form will be used for both creating new posts and editing existing posts.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <meta charset="UTF-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Post Form | Autosave draft - PHP, jQuery, and Ajax</title>
  7. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  9. <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/js/all.min.js" integrity="sha512-naukR7I+Nk6gp7p5TMA4ycgfxaZBJ7MO5iC3Fp6ySQyKFHOGfpkSZkYVWV5R7u7cfAicxanwYQ5D1e17EfJcMA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  10. <script src="https://code.jquery.com/jquery-3.6.1.js" integrity="sha256-3zlB5s2uwoUzrXK3BT7AX3FyvojsraNFxCc2vC/7pNI=" crossorigin="anonymous"></script>
  11. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
  12. html, body{
  13. min-height:100%;
  14. width:100%;
  15. }
  16. </style>
  17. </head>
  18. <?php
  19. require_once('db-connect.php');
  20. if(isset($_GET['auto_id']) || isset($_GET['id'])){
  21. $_id = isset($_GET['auto_id']) ? $_GET['auto_id'] : (isset($_GET['id']) ? $_GET['id'] : "");
  22. if(!empty($_id)){
  23. $sql = "SELECT * FROM `posts` where `id` = '{$_id}'";
  24. $get = $conn->query($sql);
  25. if($get->num_rows > 0){
  26. $result = $get->fetch_assoc();
  27. $id = $result['id'];
  28. $title = stripslashes($result['title']);
  29. $content = stripslashes($result['content']);
  30. $status = $result['status'];
  31. if($result['status'] == 'draft'){
  32. $id = $result['parent_id'] > 0 ? $result['parent_id'] : "";
  33. $auto_id = $result['id'] > 0 ? $result['id'] : "";
  34. }
  35. }
  36. }
  37. }
  38. if(isset($id) && !isset($auto_id) && isset($status) && $status == 'publish'){
  39. $check_draft_sql = "SELECT id FROM `posts` where `status` = 'draft' and `parent_id` = '{$id}' order by abs(unix_timestamp(`created_at`)) desc limit 1";
  40. $check_draft = $conn->query($check_draft_sql);
  41. if($check_draft->num_rows > 0){
  42. $draft_available_id = $check_draft->fetch_assoc()['id'];
  43. }
  44. }
  45. ?>
  46. <nav class="navbar navbar-expand-lg navbar-dark bg-primary bg-gradient">
  47. <div class="container">
  48. <a class="navbar-brand" href="./">Autosave Draft</a>
  49. <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  50. <span class="navbar-toggler-icon"></span>
  51. </button>
  52. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  53. <ul class="navbar-nav me-auto mb-2 mb-lg-0">
  54. <li class="nav-item">
  55. <a class="nav-link" href="./">Home</a>
  56. </li>
  57. <li class="nav-item">
  58. <a class="nav-link active" aria-current="page" href="postform.php"><i class="fa fa-plus"></i>Create Post</a>
  59. </li>
  60.  
  61. </ul>
  62. </div>
  63. <div>
  64. <a href="https://sourcecodester.com" class="text-light fw-bolder h6 text-decoration-none" target="_blank">SourceCodester</a>
  65. </div>
  66. </div>
  67. </nav>
  68. <div class="container-fluid px-5 my-3" >
  69. <div class="col-lg-8 col-md-10 col-sm-12 mx-auto">
  70. <h3 class="text-center"><b>Post Form</b></h3>
  71. <div class="d-flex w-100 justify-content-center">
  72. <hr class="w-50">
  73. </div>
  74. <div class="card rounded-0 shadow mb-3">
  75. <div class="card-header rounded-0">
  76. <div class="card-title"><b><?= isset($_GET['id']) ? "Edit Post" : "Create New Post" ?></b></div>
  77. </div>
  78. <div class="card-body">
  79. <div class="container-fluid">
  80. <form action="" id="post-form">
  81. <input type="hidden" name="id" value="<?= isset($id) ? $id : 0 ?>">
  82. <input type="hidden" name="auto_id" value="<?= isset($auto_id) ? $auto_id : "" ?>">
  83.  
  84. <?php if(isset($draft_available_id)): ?>
  85. <div class="alert alert-warning rounded-0">
  86. Draft Detected from Autosave of this post. <a href="./postform.php?id=<?= $id ?>&auto_id=<?= $draft_available_id ?>">Load Draft</a>
  87. </div>
  88. <?php endif; ?>
  89. <div class="mb-3">
  90. <label for="title" class="form-label">Title</label>
  91. <input type="text" class="form-control" name="title" id="title" value="<?= isset($title) ? $title : "" ?>" required>
  92. </div>
  93. <div class="mb-3">
  94. <label for="content" class="form-label">Content</label>
  95. <textarea name="content" id="content" class="form-control" rows="10"><?= isset($content) ? $content : "" ?></textarea>
  96. </div>
  97. <div class="mb-3 d-none" id="auto-saveloader">
  98. <div class="d-flex w-100 align-items-center">
  99. <div class="spinner-border spinner-border-sm" role="status">
  100. <span class="visually-hidden">Loading...</span>
  101. </div>
  102. <div class="col-auto px-4">
  103. Saving Draft
  104. </div>
  105. </div>
  106. </div>
  107. <div class="mb-3 d-none" id="saveloader">
  108. <div class="d-flex w-100 align-items-center">
  109. <div class="spinner-border spinner-border-sm" role="status">
  110. <span class="visually-hidden">Loading...</span>
  111. </div>
  112. <div class="col-auto px-4">
  113. Publishing Post
  114. </div>
  115. </div>
  116. </div>
  117. <div class="mb-3 text-center">
  118. <button id="submit-btn" class="btn btn-primary rounded-pill col-lg-3 col-md-4 col-sm-6">Publish</button>
  119. </div>
  120.  
  121. </form>
  122. </div>
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. <script src="postform.js"></script>
  128. </body>
  129. </html>

PHP API

Then, let's create the PHP API script that will process the insertion and update of the posts. The snippet below contains the logic for saving both the published posts and autosaved drafts. Save the file as save_post.php.

  1. <?php
  2. require_once('db-connect.php');
  3. function save_post(){
  4. global $conn;
  5. extract($_POST);
  6.  
  7. $title = addslashes($conn->real_escape_string($title));
  8. $content = addslashes($conn->real_escape_string($content));
  9.  
  10. if($is_draft){
  11. $id = (!empty($id) ? $id : 0);
  12. if(!empty($auto_id)){
  13. $sql = "UPDATE `posts` set `title` = '{$title}', `content` = '{$content}' where `id` = '{$auto_id}' ";
  14. }else{
  15. $sql = "INSERT INTO `posts` (`title`, `content`, `parent_id`, `status`) VALUES ('{$title}', '{$content}', '{$id}', 'draft')";
  16. }
  17. }else{
  18. if(!empty($id)){
  19. $sql = "UPDATE `posts` set `title` = '{$title}', `content` = '{$content}', `parent_id` = 0 where `id` = '{$id}' ";
  20. }else{
  21. $sql = "INSERT INTO `posts` (`title`, `content`, `parent_id`, `status`) VALUES ('{$title}', '{$content}', 0, 'publish')";
  22. }
  23. }
  24.  
  25. $save = $conn->query($sql);
  26. $id = (!empty($id) && is_numeric($id) && $id > 0) ? $id : '';
  27. $auto_id = (!empty($auto_id) && is_numeric($auto_id) && $auto_id > 0) ? $auto_id : '';
  28. if($save){
  29. $response['status'] = 'success';
  30. if($is_draft){
  31. if(empty($auto_id)){
  32. $auto_id = $conn->insert_id;
  33. }
  34. }else{
  35. if(empty($id)){
  36. $id = $conn->insert_id;
  37. }
  38. if(!empty($id))
  39. $conn->query("DELETE FROM `posts` where `parent_id` = '{$id}' or id = '{$auto_id}' ");
  40. }
  41. $response['id'] = $id;
  42. $response['auto_id'] = $auto_id;
  43. $response['sql'] = $sql;
  44. }else{
  45. $response['status'] = 'failed';
  46. $response['error'] = $conn->error;
  47. }
  48.  
  49.  
  50. return json_encode($response);
  51. }
  52.  
  53.  
  54. $save_as = $_GET['save_as'];
  55. if($save_as == 'draft'){
  56. $_POST['is_draft'] = true;
  57. $save = save_post();
  58. }else{
  59. $_POST['is_draft'] = false;
  60. $save = save_post();
  61. }
  62. echo $save;
  63. $conn->close();

JavaScript

Lastly, let's create the JavaScript file that contains the jQuery's script that triggers the autosave of drafts and executes the form submission process. Save the file as postform.js.

  1. var inputData = {};
  2. var form = $('#post-form');
  3. var auto_loader = $('#auto-saveloader');
  4. var save_loader = $('#saveloader');
  5. var submit_btn = $('#submit-btn');
  6.  
  7. $(document).ready(function(){
  8. // Input values
  9. inputData.title = form.find('#title').val()
  10. inputData.content = form.find('#content').val()
  11.  
  12. function autosave_post(){
  13. //Check first if changes has been made
  14. if(inputData.title !== form.find('#title').val() || inputData.content !== form.find('#content').val()){
  15. // Renew Saved input data
  16. inputData.title = form.find('#title').val()
  17. inputData.content = form.find('#content').val()
  18.  
  19. // Display Autosaving draft loader
  20. auto_loader.removeClass('d-none')
  21.  
  22. // Disable Submit Button
  23. submit_btn.attr('disabled', true)
  24.  
  25. //Save draft
  26. $.ajax({
  27. url:"save_post.php?save_as=draft",
  28. method:"POST",
  29. data:form.serialize(),
  30. dataType:"JSON",
  31. error: err => {
  32. console.error("Saving Post Draft failed.")
  33. console.error(err)
  34. setTimeout(function(){
  35.  
  36. // Hide Autosaving draft loader
  37. auto_loader.addClass('d-none')
  38.  
  39. // Disable Submit Button
  40. submit_btn.attr('disabled',false)
  41. console.log("Autosave has ended")
  42. },500)
  43. },
  44. success:function(response){
  45. if(!!response.status){
  46. if(response.status == "success"){
  47. $('[name="id"]').val(response.id)
  48. $('[name="auto_id"]').val(response.auto_id)
  49. var base_link = location.origin + location.pathname
  50. history.pushState("", "", base_link+"?id="+response.id+"&auto_id="+response.auto_id)
  51. }else{
  52. console.error("Saving Post Draft failed.")
  53. console.error(response)
  54. }
  55. }else{
  56. console.error("Saving Post Draft failed.")
  57. console.error(response)
  58. }
  59.  
  60. setTimeout(function(){
  61.  
  62. // Hide Autosaving draft loader
  63. auto_loader.addClass('d-none')
  64.  
  65. // Disable Submit Button
  66. submit_btn.attr('disabled',false)
  67. console.log("Autosave has ended")
  68. },500)
  69.  
  70. }
  71. })
  72.  
  73. }
  74. }
  75.  
  76. form.submit(function(e){
  77. e.preventDefault()
  78. clearInterval(autosave_interval)
  79.  
  80. // Display Autosaving draft loader
  81. save_loader.removeClass('d-none')
  82.  
  83. // Disable Submit Button
  84. submit_btn.attr('disabled', true)
  85.  
  86. //Save draft
  87. $.ajax({
  88. url:"save_post.php?save_as=publish",
  89. method:"POST",
  90. data:form.serialize(),
  91. dataType:"JSON",
  92. error: err => {
  93. console.error("Saving Post failed.")
  94. console.error(err)
  95. autosave_interval;
  96. setTimeout(function(){
  97.  
  98. // Hide Saving post loader
  99. save_loader.addClass('d-none')
  100.  
  101. // Disable Submit Button
  102. submit_btn.attr('disabled',false)
  103. },500)
  104. },
  105. success:function(response){
  106. if(!!response.status){
  107. if(response.status == "success"){
  108. $('[name="id"]').val(response.id)
  109. $('[name="auto_id"]').val(response.auto_id)
  110. alert("Post has been published successfully.")
  111. location.href = './'
  112. }else{
  113. console.error("Saving Post failed.")
  114. console.error(response)
  115. }
  116. }else{
  117. console.error("Saving Post failed.")
  118. console.error(response)
  119. }
  120.  
  121. setTimeout(function(){
  122.  
  123. // Hide Autosaving draft loader
  124. save_loader.addClass('d-none')
  125.  
  126. // Disable Submit Button
  127. submit_btn.attr('disabled',false)
  128. },500)
  129. autosave_interval;
  130. }
  131. })
  132. })
  133.  
  134. var autosave_interval = setInterval(function(){
  135. console.log("Autosave has started")
  136. autosave_post()
  137. }, 3000)
  138.  
  139.  
  140. })

That's it! You can now test the sample application on your end and check if it achieves the main objective of this tutorial. I also provided the source code zip file that I created for the tutorial. You can download it by clicking the Download Button below this article.

Snapshots

Here are some of the snapshots of the result of the snippets I provided above.

Main Page

Autosave Draft using PHP and jQuery

Form Page

Autosave Draft using PHP and jQuery

Edit Form with Draft Detected Notification

Autosave Draft using PHP and jQuery

How does the Application work?

The sample application contains simple functionalities only and focuses mainly on Autosave Draft. On the main page, saved/published and draft posts will be listed. On the form page, the user can simply fill in the text fields while the system executes the autosave script every 3 seconds. Take note that the autosave script will only execute the PHP API if changes have been made only in the form.

There you go. That's the end of this tutorial. I hope this Autosave Draft of Form using PHP, jQuery, and Ajax will help you with what you are looking for and that you'll find this useful for your current and future PHP Projects.

Explore more on this website for more Tutorials and Free Source Codes.

Happy Coding :)

Add new comment