0

I've had a search around but not been able to see if this is possible.

I've developed a HTML page to display the stock status on a number of products. I currently edit each status manually each day (where the status has changed from previous day) and I wish to automate it where ever possible.

For example, I currently have the HTML page displaying by Manufacturer with each product and stock status in a separate table.

.collapse{
    cursor: pointer;
    display: block;
    background: rgb(0, 156, 0);
    color: rgb(255, 255, 255);
    padding: 6px 12px;
    border: solid white;
}
.collapse:hover{
    color: rgb(231, 230, 229);
    font-weight: bold;
}
.collapse + input{
    display: none;
}
.collapse + input + div{
    display:none;
}
.collapse + input:checked + div{
    display:block;
}
<body>
    <div><label class="collapse" for="_bmw">BMW</label>
        <input id="_bmw" type="checkbox">
        <div><br>
            <table border="1" cellpadding="1" cellspacing="1">
                
                <thead>
                    <tr style="font-weight: bold">
                        <td style="width: 75px;">Product Code</td>
                        <td style="width: 200px;">Model</td>
                        <td style="width: 200px;">Stock Status</td>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1000</td>
                        <td>M1</td>
                        <td>Available</td>
                    </tr>
                    <tr>
                        <td>1001</td>
                        <td>M3</td>
                        <td>Out of stock</td>
                    </tr>
                    <tr>
                        <td>1002</td>
                        <td>M5</td>
                        <td>Available</td>
                    </tr>
                </tbody>
            </table>
            <br>
        </div>
    </div>
    <div><label class="collapse" for="_ford" style="font-size: 17px;">Ford</label>
        <input id="_ford" type="checkbox">
        <div><br>
            <table border="1" cellpadding="1" cellspacing="1">
                <thead>
                    <tr style="font-weight: bold">
                        <td style="width: 75px;">Product Code</td>
                        <td style="width: 200px;">Model</td>
                        <td style="width: 200px;">Stock Status</td>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1003</td>
                        <td>Fiesta</td>
                        <td>Available</td>
                    </tr>
                    <tr>
                        <td>1004</td>
                        <td>Mondeo</td>
                        <td>Available</td>
                    </tr>
                    <tr>
                        <td>1004</td>
                        <td>Escort</td>
                        <td>End of life</td>
                    </tr>
                </tbody>
            </table>
            <br>
        </div>
    </div>
</body>

Is it possible to use javascript or jquery to perform a lookup of the Product Code within the HTML table and return a value from a JS (or CSV) file within the Stock Status TD?

I've created a JS file with the following data, now I just need to know how to populate the Stock Status data based on a lookup of the Product Code:-

[
 {
   "FIELD1": "1000",
   "FIELD2": "Available"
 },
 {
   "FIELD1": "1001",
   "FIELD2": "Out of stock"
 },
 {
   "FIELD1": "1002",
   "FIELD2": "Available"
 },
 {
   "FIELD1": "1003",
   "FIELD2": "Available"
 },
 {
   "FIELD1": "1004",
   "FIELD2": "Available"
 },
 {
   "FIELD1": "1005",
   "FIELD2": "End of life"
 },
]

I'm new to JS & JQuery so any help is appreciated. If I've missed anything or you need further info please ask.

6
  • Save the stock status array in a JSON file on the server. Then you can make a http call to that file to get it inside the page. Then just parse the array, filter it for the products you need, and then create extra table rows from that data. Commented Oct 12, 2018 at 8:19
  • Hi there, welcome to Stack Overflow :) out of curiosity, is there a server containing numeric stock information for your products? Whilst I understand you want to automate this, how you want to achieve this will result in you manually updating the javascript file instead of the html table - so it would be be of very little benefit to you. Commented Oct 12, 2018 at 8:20
  • Thanks for replying. Yes, I export an excel file each morning that contains the product code and daily stock status. Currently I use that stock status to manually edit the HTML page, but I can convert the data to CSV or JSON and upload to my server. I just don't know how to make the HTML page auto populate with the Stock Status Commented Oct 12, 2018 at 8:25
  • Hi @Shilly, how would I go about making the http call & parse the array etc? The code above is about the extent of my abilities at the moment :) Commented Oct 18, 2018 at 9:01
  • @Hawasho Do you actually want to learn javascript or do you just need a JQuery script to do this task? In the first case, I'll try to write something relevant. In the second case, I'll refer you to any JQuery tutorial, since the code needed for an ajax call and then populating some field is basically one of the first examples, so you just need to copy paste those and edit the values a bit. JQuery will be way easier, but I dislike it tremendously. Commented Oct 18, 2018 at 9:51

2 Answers 2

1

I'll break this down into it's seperate steps:

1) The JSON file

If we carefully choose the format of our JSON file, we can provide all of the data and even more that we need to construct the entire page. Hence I would put all the information about all cars we have inside this file, so we never have to update the HTML file after adding brands or models.

If we would only keep the availability of a car inside the JSON file, we would need to update both the JSON file AND also the HTML file to add a brand or type.

The availability is also better noted as an integer representing the amount of cars there are available instead of a string. If it was a string, we would need to parse that string to see if there's still cars available.

By seperating the id of the car from it's product code, we can keep the product code as a string so that it can contain more than only numbers and also still keep an easy way to sort our cars. Remember that strings sort differently than integers: "10" < "9" === true and 10 < 9 === false. Else this could lead to problems if we ever have a car with code "999".

An added advantage is that this nicely maps to table columns if we'd ever move this into a database.

[
    {
        "availability": 25,
        "brand": "bmw",
        "code": "1000",
        "id": 1,
        "model": "m1"
    },
    {
        "availability": null,
        "brand": "bmw",
        "code": "1001",
        "id": 2,
        "model": "m3"
    },
    {
        "availability": 10,
        "brand": "bmw",
        "code": "1002",
        "id": 3,
        "model": "m5"
    },
    {
        "availability": 7,
        "brand": "ford",
        "code": "1003",
        "id": 4,
        "model": "fiesta"
    },
    {
        "availability": 14,
        "brand": "ford",
        "code": "1004",
        "id": 5,
        "model": "mondeo"
    },
    {
        "availability": null,
        "brand": "ford",
        "code": "1005",
        "id": 6,
        "model": "escort"
    }
]

2) Fetching the file

We have two mechanisms here to do this. Either the old XMLHttpRequest() if we have to be compatible with old browsers. Or the fetch() API for new browsers. This choice will determine if we have to use callbacks or promises. ( Unless we transform the XMLHttpRequest version into a promise as well. )

XMLHttpRequest:

//  The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
//  A getJSON function that will create an ajax request to the provided URL.
const getJSON = ( url, callback ) => {
    //  Create a new XMLHttpRequest.
    //  https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
    const request = new XMLHttpRequest();
    //  Open the request before setting other properties. ( IE11 )
    request.open( 'GET', url );
    //  When the request gets the file, we want to run the callback.
    //  The responseText will be the JSON string inside our json file.
    request.onload = function() {
        callback( request.responseText );
    };
    request.send();
};
//  Use the function to get the file.
//  Parse and log the contents of the file once it arrives.
getJSON( PATH_CARS, function( response ) {
    // cars will be a string here. We want the actual JS object represented by the JSON string
    const cars = JSON.parse( response );
    console.log( cars );
});

fetch:

//  The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
//  Same thing, but using the fetch API for browsers that support it.
//  https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
//  The fetch API uses promises instead of callbacks to handle the results.
//  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
fetch( PATH_CARS )
    .then( response => response.json())
    .then( cars => {
        console.log( cars );
    });

3) Creating the table

We'll switch the logic around a little bit. Instead of having a fixed HTML that we want to update with values coming from a file, we can just create the entire table from the JSON file so that all the updates are already in the table.

If we then need to update the table again, we can just rerender the entire table instead of trying to match the HTML nodes with the correct values inside the JSON. This won't work that fast for huge amount of cars, ( think 1000+ ) but still way faster than updating every car individually.

This falls into what we call the Model-View-Controller architecture. The JSON file gives us a model of the cars. The HTML table is a view of that model. The javascript code binds it all together as the controller. The controller fetches the model, and turns the model into a view representing that model. Every time the model changes, ( you add a car to the JSON file ), we can request the controller to fetch the updated model ( load the JSON file ) and the update the view. ( render the tables again )

//  We need to create a table for each brand.
//  We need to create a table row for each car model of that type.
//  For big projects, one would use a templating language to create the HTML.
//  For something as small as thing, we can resort to simple string manipulation.
const createTables = brands => {
    //  Loop over all the brands, creating a table for each brand.
    //  I'll use a reduction this time, to show the difference and similarities between reduce() and the forEach() we used in the previous step.
    const tables = brands.reduce(( html, brand ) => {
        //  Copy the header, replacing the brand name.
        const header = `<table><thead><tr><th colspan="3">${ brand.name }</th></tr><tr><th>Product Code:</th><th>Model:</th><th>In Stock:</th></tr></thead><tbody>`;
        //  Loop over the cars and create a row for each car.
        //  Since we create the same amount of rows as we have cars inside the array, we can use .map()
        const rows = brand.cars.map( car => {
            //  Since we changed the availability to a number, we hve to recreate the string for it.
            //  This allows us to easily change the label without having to change the logic in multiple places
            const availability_label = Number.isInteger( car.availability )
                ? `${ car.availability } in stock.`
                : 'End of life.';
            return `<tr><td>${ car.code }</td><td>${ car.model }</td><td>${ availability_label }</td></tr>`;
        });
        //  Append the current header, car rows and the closing tags to the previous HTML, then return.
        return html += `${ header }${ rows.join('') }</tbody></table>`;
    }, '');
    //  Return the HTML string. We could also just return the reduction directly, wihtout using th tables variable in between.
    return tables;
};

4) Putting it all together

Using all the techniques and functions we created in the examples, we now have everything to create our entire app. I've added another helper function that groups all the cars into their brands, so creating the tables is easier and more clear.

I have mocked up the fetching of the JSON file in the example below so we can actually run the code. IN your own code, you would use the real fetch() or XMLHttpRequest() code.

//  FAKE FETCH, DO NOT USE IN THE REAL CODE
const fetch = url => Promise.resolve({json: () => JSON.parse('[{"availability":25,"brand":"bmw","code":"1000","id":1,"model":"m1"},{"availability":null,"brand":"bmw","code":"1001","id":2,"model":"m3"},{"availability":10,"brand":"bmw","code":"1002","id":3,"model":"m5"},{"availability":7,"brand":"ford","code":"1003","id":4,"model":"fiesta"},{"availability":14,"brand":"ford","code":"1004","id":5,"model":"mondeo"},{"availability":null,"brand":"ford","code":"1005","id":6,"model":"escort"}]')});


//  The path where we can find the JSON file.
const PATH_CARS = 'http://path/to/cars.json';
//  Same thing, but using the fetch API for browsers that support it.
//  https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
//  The fetch API uses promises instead of callbacks to handle the results.
//  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
const getCars = url => fetch( url )
  .then( response => response.json())
  .catch( error => console.error( error ));
//  We need to group all the different cars into their respective brands.
const groupBrands = cars => {
  // Create a temporary object we'll use to store the different brands.
  const brands = {};
  //  Loop over all the car models, grouping them into the correct brand.
  cars.forEach( car => {
  //  Extract the brand name from the car item.
  const brand = car.brand;
  //  If we haven't seen this brand yet, add it to the different brands as an array.
  if ( !brands.hasOwnProperty( brand )) brands[ brand ] = [];
    //  Push the car model to the brand.
    brands[ brand ].push( car );
  });
  //  We now have an object containign all the cars grouped by brand.
  //  It would be easier however, if we had ana rray we can loop over easily.
  //  So transform the object back into an array.
  //  We loop over the entries array of the object to extarct the name and cars at the same time, then wrap them back into an object.
  return Object.entries( brands ).map(([ name, cars ]) => ({ name, cars }));
  //  This entire step can be done in one expression by using array.reduce() instead of array.forEach()
  //  We could also just return the object and loop over the entries in the render function.
  //  My personal preference is to always use an array to represent multiples of something:
  //  A 'collection' of 'brand' objects with each brand containing a 'collection' of 'car' objects.
  //  We could also already do this grouping inside the JSON file itsself, but I preferred to keep the JSON file itsself simple for this example.
};
//  We need to create a table for each brand.
//  We need to create a table row for each car model of that type.
//  For big projects, one would use a templating language to create the HTML.
//  For something as small as thing, we can resort to simple string manipulation.
const createTables = brands => {
//  Loop over all the brands, creating a table for each brand.
//  I'll use a reduction this time, to show the difference and similarities between reduce() and the forEach() we used in the previous step.
const tables = brands.reduce(( html, brand ) => {
  //  Copy the header, replacing the brand name.
  const header = `<table><thead><tr><th colspan="3">${ brand.name }</th></tr><tr><th>Product Code:</th><th>Model:</th><th>In Stock:</th></tr></thead><tbody>`;
  //  Loop over the cars and create a row for each car.
  //  Since we create the same amount of rows as we have cars inside the array, we can use .map()
  const rows = brand.cars.map( car => {
    //  Since we changed the availability to a number, we hve to recreate the string for it.
    //  This allows us to easily change the label without having to change the logic in multiple places
    const availability_label = Number.isInteger( car.availability )
        ? `${ car.availability } in stock.`
        : 'End of life.';
    return `<tr><td>${ car.code }</td><td>${ car.model }</td><td>${ availability_label }</td></tr>`;
  });
  //  Append the current header, car rows and the closing tags to the previous HTML, then return.
  return html += `${ header }${ rows.join('') }</tbody></table>`;
  }, '');
  //  Return the HTML string. We could also just return the reduction directly, wihtout using th tables variable in between.
  return tables;
};
//  We have a JSON file, we can fetch that file, we can create tables from the contents, time to put it all together.
//  Fetch the JSON file.
getCars( PATH_CARS )
  //  Group the cars into brands.
  .then( groupBrands )
  //  Create a table for each group.
  .then( createTables )
  //  Render the tables into the page.
  .then( html => {
    const tableHook = document.querySelector( '#cars' );
    if ( tableHook ) tableHook.innerHTML = html;
    // else throw new Error(); something went wrong.
  })
  //  Catch any errors encountered.
  .catch( error => console.error( error ));
<html>
<head>
  <title>Car Stocks</title>
</head>
<body>
  <div id="cars"></div>
</body>
</html>

5) Upgrades

Alot of the code above can be written way shorter, but I intentionally used the longer versions to keep the amount of new things to learn to a minimum. Same code can be written with callbacks, in the case that promises aren't supported. Internally, the functions will mostly stay the same.

I'll leave re-adding the CSS again up to you, since that was working well already.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for all your help, I'll have a play with this and see if I can incorporate it into my pages. I currently have around 10-15 manufacturers and around 800 product lines. Ideally I'd like to keep the dropdown/collapse CSS as this is what our customer teams are used to working with. Out of curiosity, do you have any links to the JQuery solution? I've had a look but I can't see any tutorials that would assist me (or I'm just not looking in the right places). Once again, I appreciate the time you've taken to help.
Many thanks, I've marked your answer as accepted, although I've gone with a different method which I'll post below. I now have a follow up question regarding formatting json data after it's been loaded into the table, so I'll set up another question.
I'll have a look at the JSON once you have posted that question.
I managed to figure it out. I was attempting to apply a second script which added css colour styles based on the stock status (I.E In stock = green etc), but the script was not waiting for the AJAX to complete. I added "$(document).ajaxComplete(function() {" to the script and now it's working a charm.
You could make CSS class that adds the correct color to an element and then add something like if ( StockStatus === ??? ) $td.class(someClassName) inside the loop where you create the table.
1

I've implemented the following code which works with the JSON data I export from my daily stock report.

$(document).ready( function() {
      $.ajax({
        url: "data.json",
        method: "GET",
        dataType: "json",
        success: function(data) {
           var $tbody = $("table#data tbody");
           $.each(data, function(i, data) {
              var $tr = $("<tr></tr>");
              $tr.appendTo($tbody);
              var $td = $("<td></td>");
              $td.html(data.ProdCode)
                 .appendTo($tr);   
              $td = $("<td></td>");  
              $td.html(data.Model) 
                 .appendTo($tr); 
    	      $td = $("<td></td>"); 
              $td.html(data.StockStatus)
                 .appendTo($tr);   
           });
        },
        error: function(jqXHR, textStatus, errorThrown) {
          console.log("AJAX ERROR",textStatus,errorThrown,jqXHR);
        }
      });
    });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.