Jermaine Subia
Jermaine Subia

Reputation: 796

How to upload an image file url to indexedDB to render on page

The goal is when I add a customer I want to be able to upload an image as well. On the main customer page, it will list all customers and a thumbnail image of the customer.

Currently, when I upload an image to indexedDB it is not taking in the correct URL. In my current learning project when I upload an image it uploads this URL address:

C:\fakepath\01-gel-nail-designs-thecuddl.jpg

I am not sure why it keeps giving the C:\fakepath which is not the correct path so when it renders on the main customer page it is just a broken image filler.

Here is my index.html:

 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->


    <title>Customer Manager</title>

    <!-- Bootstrap core CSS -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="css/style.css" rel="stylesheet">
  </head>
  <body>

    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Customer Manager v0.1</a>
        </div>
      </div>
    </nav>

    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-3 col-md-2 sidebar">
          <ul class="nav nav-sidebar">
            <li class="active"><a href="#">Customers <span class="sr-only">(current)</span></a></li>
            <li><a href="add.html">Add Customer</a></li>
            <li><a onclick="clearCustomers()" class="danger" href="#">Clear Customers</a></li>
          </ul>
        </div>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h1 class="page-header">Customers</h1>
          <div class="table-responsive">
            <table class="table table-striped">
              <thead>
                <tr>
                  <th>#</th>
                  <th>Name</th>
                  <th>Email Address</th>
                  <th></th>
                </tr>
              </thead>
              <tbody id="customers">

              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
    <script src="js/bootstrap.min.js"></script>
    <!-- Just to make our placeholder images work. Don't actually copy the next line! -->
    <script src="../../assets/js/vendor/holder.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
    <script src="js/main.js"></script>
  </body>
</html>

Here is my add.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->


    <title>Add Customer</title>

    <!-- Bootstrap core CSS -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="css/style.css" rel="stylesheet">
  </head>
  <body>

    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Customer Manager v0.1</a>
        </div>
      </div>
    </nav>

    <div class="container-fluid">
      <div class="row">
        <div class="col-sm-3 col-md-2 sidebar">
          <ul class="nav nav-sidebar">
            <li class="active"><a href="#">Customers <span class="sr-only">(current)</span></a></li>
            <li><a href="add.html">Add Customer</a></li>
            <li><a class="danger" href="#">Clear Customers</a></li>
          </ul>
        </div>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
          <h1 class="page-header">Add Customer</h1>
          <form role="form" onsubmit="addCustomer()">
              <div class="form-group">
                <label>Customer Name</label>
                <input type="text" id="name" class="form-control" placeholder="Enter Name">
              </div>
              <div class="form-group">
                <label>Customer Email</label>
                <input type="email" id="email" class="form-control" placeholder="Enter Email">
              </div>
              <input type="file" id="imageSelector" multiple="multiple" />
              <button type="submit" class="btn btn-default">Add Customer</button>
        </form>
        </div>
      </div>
    </div>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
    <script src="js/bootstrap.min.js"></script>
    <!-- Just to make our placeholder images work. Don't actually copy the next line! -->
    <script src="../../assets/js/vendor/holder.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
    <script src="js/main.js"></script>
  </body>
</html>

Here is my main.js:

$(document).ready(function(){
    //Open database
    var request = indexedDB.open('customermanager',1);

    request.onupgradeneeded = function(e){
        var db = e.target.result;

        if(!db.objectStoreNames.contains('customers')){
              var os = db.createObjectStore('customers',{keyPath: "id", autoIncrement:true});
                //Create Index for Name
                os.createIndex('name','name',{unique:false});
        }
    }


    //Success
    request.onsuccess = function(e){
        console.log('Success: Opened Database...');
        db = e.target.result;
        //Show Customers
        showCustomers();
    }

    //Error
    request.onerror = function(){
        console.log('Error: Could Not Open Database...');
    }
});

//Add Customer
function addCustomer(){
    var name = $('#name').val();
    var email = $('#email').val();
    var image = $('#imageSelector').getAttribute('src');



    var transaction = db.transaction(["customers"],"readwrite");
    //Ask for ObjectStore
    var store = transaction.objectStore("customers");

    //Define Customer
    var customer = {
        name: name,
        email: email,
        image: image
    }

    //Perform the Add
    var request = store.add(customer);

    //Success
    request.onsuccess = function(e){
        window.location.href="index.html";
    }

    //Error
    request.onerror = function(e){
        alert("Customer was not added");
        console.log('Error', e.target.error.name);
    }
}

//Display Customers
function showCustomers(e){
    var transaction = db.transaction(["customers"],"readonly");
    //Ask for ObjectStore
    var store = transaction.objectStore("customers");
    var index = store.index('name');

    var output = '';
    index.openCursor().onsuccess = function(e){
        var cursor = e.target.result;
        if(cursor){
            output += "<tr id='customer_"+cursor.value.id+"'>";
            output += "<td>"+cursor.value.id+"</td>";
            output += "<td><span class='cursor customer' contenteditable='true' data-field='name' data-id='"+cursor.value.id+"'>"+cursor.value.name+"</span></td>";
            output += "<td><span class='cursor customer' contenteditable='true' data-field='email' data-id='"+cursor.value.id+"'>"+cursor.value.email+"</span></td>";
              output += "<td><img class='thumbnail' src='"+cursor.value.id+"'></img></td>";
            output += "<td><a onclick='removeCustomer("+cursor.value.id+")' href=''>Delete</a></td>";
            output += "</tr>";
            cursor.continue();
        }
        $('#customers').html(output);
    }
}

//Delete a Customer
function removeCustomer(id){
    var transaction = db.transaction(["customers"],"readwrite");
    //Ask for ObjectStore
    var store = transaction.objectStore("customers");

    var request = store.delete(id);

    //Success
    request.onsuccess = function(){
        console.log('Customer '+id+' Deleted');
        $('customer_'+id).remove();
    }

    //Error
    request.onerror = function(e){
        alert("The customer was not removed")
        console.log('Error', e.target.error.name);
    }
}

//Clear All Customers
function clearCustomers(){
    indexedDB.deleteDatabase('customermanager');
    window.location.href="index.html";
}

//Update Customers
$('#customers').on('blur','.customer',function(){
    //Newly entered text
    var newText = $(this).html();
    //Field
    var field = $(this).data('field');
    //Customer ID
    var id = $(this).data('id');

    //Get Transaction
    var transaction = db.transaction(["customers"],"readwrite");
    //Ask for ObjectStore
    var store = transaction.objectStore("customers");

    var request = store.get(id);

    request.onsuccess = function(){
        var data = request.result;
        if(field == 'name'){
           data.name = newText;
        } else if(field == 'email'){
            data.email = newText;
        }

        //Store Updated Text
        var requestUpdate = store.put(data);

        requestUpdate.onsuccess = function(){
            console.log('Customer field updated...');
        }

        requestUpdate.onerror = function(){
            console.log('Customer field NOT updated...');
        }
    }
});

Here is an image of what indexedDB looks like in the console and the way it looks in my index.html:

first image

Here is an image of what my add.html looks like:

second image

Any guidance would be greatly appreciated.

UPDATE 1

Based off of some additional research I will not be able to do what I want with the code I have due to security reasons I won't be able to find the true path without using other methods. I am struggling with finding a way to implement the example from this link https://hacks.mozilla.org/2012/02/storing-images-and-files-in-indexeddb/.

Upvotes: 2

Views: 3036

Answers (1)

Josh
Josh

Reputation: 18690

This is an incomplete answer, just some notes. If you insist on using indexedDB, you probably want to store the uploaded files as blobs within indexedDB. Next, you want to set an image's src to the blob's url, using URL.createObjectURL. Keep in mind that some browsers have issues with storing blobs, so whether this works depends on which browsers you are supporting.

Here is some rough code to get you started:

addEventListener('DOMContentLoaded', event => {
  attachUploadHandler();
});

function attachUploadHandler() {
  const input = document.querySelector('#uploader');
  input.setAttribute('type', 'file');
  input.setAttribute('accept', 'image/png, image/gif, image/jpeg');
  input.onchange = onchange;
}

function openDb() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(name, version);
    request.onupgradeneeded = onupgradeneeded;
    request.onsuccess = _ => resolve(request.result);
    request.onerror = _ => reject(request.error);
  });
}

function onupgradeneeded(event) {
  const conn = event.target.result;
  conn.createObjectStore('store');
}

// Handle file uploader change event 
async function onchange(event) {
  const files = event.target.files;

  const conn = await openDb();
  await new Promise((resolve, reject) => {
    const tx = conn.transaction('store', 'readwrite');
    tx.oncomplete = resolve;
    tx.onerror = _ => reject(new Error('file storage error'));

    const store = tx.objectStore('store');

    for(const file of files) {
      store.put(file);
    }
  });
  conn.close();
}

Next you probably want to use out-of-line keys with the image store, then store image id in customer store. then you load all the customers, and for each customer, query the image store for the corresponding image.

At the moment I forgot how to do out-of-line read/write stuff, so you'll have to figure that one out.

function getCustomers(conn) {
  return new Promise((resolve, reject) {
    const tx = conn.transaction(['customers', 'images']);
    tx.oncomplete = resolve(customers);
    const customer_store = tx.objectStore('customers');
    const image_store = tx.objectStore('images');

    function set_customer_image(customer, event) {
      customer.image_blob = event.target.result;
    }

    const customers_request = customer_store.getAll();
    customers_request.onsuccess = event => {
      const customers = customers_request.result;
      for(const customer of customers) {
        const image_request = image_store.get(customer.image_id);
        image_request.onsuccess = set_customer_image.bind(image_request, customer);          
      }
    };
  });
}

Then, when rendering, the trick is to use URL.createObjectURL for each blob, and set image_element.src to that.

function renderCustomers(customers) {
  const image_placeholder_url_string = 'customer-image-placeholder.gif';

  for(const customer of customers) {
     const tr = new HTMLTableRowElement();

     // now, the key part for the customer image cell
     const td = new HTMLTableCellElement();
     const image = new HTMLImageElement();
     if(customer.image_blob) {
       image.src = URL.createObjectURL(customer.image_blob);
     } else {
       image.src = image_placeholder_url_string;
     }

     td.appendChild(image);
     tr.appendChild(td);
  }
}

Upvotes: 1

Related Questions