Docs & Guides
Building a Pack Legend with the Paccurate API
15 min
making a pack request to paccurate is straightforward give us some items and some cartons to put them into, and we will cartonize those items how customers consume the pack response is up to them save the box assignment back to their erp system, capture the estimated cost and use it to make a shipping decision, or even render an image of the items in their assigned boxes in this post, we’ll be covering how to display a packed carton with an interactive legend of the items contained in the carton this example will use javascript, but the concepts should be applicable to other programming languages at a high level, creating the legend takes 4 steps make the api request render the pack svg https //developer mozilla org/en us/docs/web/svg create the list of boxes & items add event listeners to the list let’s get started all you need to make this work is an html file (call it legend html), javascript file (call it legend js), a text editor (notepad will do!), and a web browser (if you’re feeling impatient, the assembled js and html are available at the end of the article) creating the api request as covered here https //paccurate io/posts/getting started with paccurate anatomy of a pack request , the api accepts a json body with item and carton data for more information on how to construct the body of the request, please read anatomy of a pack request to keep these code samples readable, we’ll be using the variable config to represent your pack request in json in order to communicate with the api, we need the browser to make an http request this can be handled using the fetch() api (available in most modern browsers), a third party library such as axios or the long supported xmlhttprequest api which is available in all browsers we’ll be using the latter approach for this example const config = {/ your json body data /} document addeventlistener('domcontentloaded', function() { const request = new xmlhttprequest(); const method = 'post'; const url = 'https //api paccurate io'; const packobj = config; request open(method, url, true); request setrequestheader('content type', 'application/json'); request onreadystatechange = function(){ if(request readystate === xmlhttprequest done) { var status = request status; if (status === 0 || (status >= 200 && status < 400)) { // the request has been completed successfully try{ let packresponse = json parse(request responsetext); writelegend(packresponse); }catch(e){ console error(e); } } else { console log(status); } } } request send(json stringify(packobj)); }); in the above snippet, we are doing a few things first, we’re adding a listener to the webpage to make the request when it has finished loading on the domcontentloaded browser event, we then setup the function to call paccurate first we create a new xmlhttprequest object, called request then we configure the request — we set the method to post, the target url to our api, and make a reference to the request configuration the request open starts the http request once the request is opened, we set the content type to application/json so the request knows to pass it to our api correctly we then assign a state change listener to handle the response to the request when the api responds successfully, the json parse takes the string of the response and turns it into json data that we can use to hand off to writelegend which builds the markup with all of the above setup, request send tells the xmlhttprequest to send the data to our api building the html in the code sample above, there is a call to function called writelegend that accepts the response data from the paccurate api within that function, we start to assemble the markup for the legend let’s explore the function below const writelegend = (json)=> { const packdata = json; const target = document queryselector('#pack list'); const svgs = packdata svgs; const boxes = packdata boxes; boxes foreach((box, index)=>{ // create a layout for each box target appendchild(generatemarkup(svgs\[index], box)) }) // setup listeners addlegendlisteners(); } the writelegend function takes the argument json which is the parsed json of the api response (referenced as packdata ) in the third line of the function, we create a variable called target which is a reference to the dom node in the html where the legend will be placed on the page the variables svgs and boxes represent the arrays of svg markup and box data that the api has returned with the boxes array defined, we loop through each box using foreach — the target html is passed a string of html generated by the generatemarkup function (which we’ll get to next!) this function takes two arguments the first is an svg with the same index as the current box in the loop, the second is the box data from the current box in the loop after the markup has been generated from the loop, it’s time to add the hover functionality by calling addlegendlisteners (more on that later) extracting the item and svg data the generatemarkup function calls a two other helper functions, parseditems and keyitems — all three are in the following snippet const parseditems = (arr)=>{ const skus = {} arr foreach((element) => { element = element item const id = element name ? element name element refid if (typeof skus\[id] === 'undefined') { // push item into sku list skus\[id] = { refid element refid, name element name, weight element weight, dimensions element dimensions ? \[element dimensions x, element dimensions y, element dimensions z] \[1, 1, 1], boxitems \[{ id element uniqueid, index element index, color element color }] } } else { skus\[id] boxitems push({ id element uniqueid, index element index, color element color }) } }) const flattened = object keys(skus) map((element) => { return skus\[element] }) return flattened } const keyitems = (box, index)=>{ const markup = `\<tr> \<td> \<ul style="width 300px; list style type\ none; margin 0; padding 0;" class="legend"> ${box boxitems map((item)=> { return `\<li data box index="${index}" data volume index="${item index}" style="width 20px; height 20px; margin 0 5px 5px 0; float\ left; background color ${item color}">\</li>` }) join('')} \</ul> \</td> \<td>${box name || box refid}\</td> \<td>${box dimensions join(',')}\</td> \<td>${box weight}\</td> \<td>${box boxitems length}\</td> \</tr>` return markup } const generatemarkup = (svg, box)=>{ // compress total item list into a list of skus with count, dimensions, weight, and name const parsed = parseditems(box box items) // box id is important if an order has multiple boxes to be packed the svg uses this id as a parent to target the inner boxes const boxid = box box id // create wrapper for svg and legend let layout = document createelement('div') let svgwrap = document createelement('div') let itemkey = document createelement('table') itemkey innerhtml = `\<tr> \<th>item\</th> \<th>name/id\</th> \<th>dims\</th> \<th>weight\</th> \<th>qty\</th> \</tr> ${parsed map((item)=>{return keyitems(item, boxid)}) join('')} ` svgwrap innerhtml = svg // add elements to wrapper layout appendchild(svgwrap) layout appendchild(itemkey) return layout } there are two arguments provided to the generatemarkup function — the first is a string of svg markup for the box, the second is a box object that contains item data for the items packed within the box the items array within each box contains the placement data for each item individually, but in order to render the legend, we need to group the items by their name (or refid) the parseditems function does just this const parseditems = (arr) => { const skus = {}; arr foreach((element) => { element = element item; const id = element name ? element name element refid; if (typeof skus\[id] === "undefined") { // push item into sku list skus\[id] = { refid element refid, name element name, weight element weight, dimensions element dimensions ? \[element dimensions x, element dimensions y, element dimensions z] \[1, 1, 1], boxitems \[ { id element uniqueid, index element index, color element color }, ], }; } else { skus\[id] boxitems push({ id element uniqueid, index element index, color element color, }); } }); const flattened = object keys(skus) map((element) => { return skus\[element]; }); console info(flattened); return flattened; }; it accepts the array of items for each box, and returns an array of objects, one for each type of item in the box, with item data and a list of instances of the item in the box \[ { "refid" 1, "name" "larger box", "weight" 10, "dimensions" \[ 3, 6, 9 ], "boxitems" \[ { "id" "1 0", "index" 1, "color" "indigo" }, { "id" "1 1", "index" 2, "color" "indigo" } ] }, { "refid" 0, "name" "smaller box", "weight" 1, "dimensions" \[ 1, 2, 1 ], "boxitems" \[ { "id" "0 2", "index" 3, "color" "darkorange" }, { "id" "0 3", "index" 4, "color" "darkorange" }, { "id" "0 4", "index" 5, "color" "darkorange" } ] } ] in the example above, a box with 5 items packed in it (3 of type a, 2 of type b) returns an array of 2 objects one with the data for item a and an array of 3 instances of the item, a second with data for item b and an array of 2 instances of the item these nested arrays will drive the color keys that activate the hover state of the svg back to generating markup — once we’ve gotten our item data extracted to the parsed variable, it’s time to set up a few wrappers to contain the svg data and legend table // create wrapper for svg and legend let layout = document createelement("div"); let svgwrap = document createelement("div"); let itemkey = document createelement("table"); the variable layout is a parent div that will hold the svgwrap div, as well as the item information which will be contained inside the itemkey \<table> element with these initialized, we can loop through parsed to populate the itemkey table itemkey innerhtml = `\<tr> \<th>item\</th> \<th>name/id\</th> \<th>dims\</th> \<th>weight\</th> \<th>qty\</th> \</tr> ${parsed map((item) => { return keyitems(item, boxid); }) join("")} `; the legend table is comprised of one row of headers and additional rows for each item type we’re using template literals https //developer mozilla org/en us/docs/web/javascript/reference/template literals above to write the headers and then mapping each item in the parsed array to return a row of html let’s look at how keyitems builds the html for each item in parsed const keyitems = (box, index) => { const markup = `\<tr> \<td> \<ul style="width 300px; list style type\ none; margin 0; padding 0;" class="legend"> ${box boxitems map((item) => { return `\<li data box index="${index}" data volume index="${item index}" style="width 20px; height 20px; margin 0 5px 5px 0; float\ left; background color ${item color}">\</li>`; }) join("")} \</ul> \</td> \<td>${box name || box refid}\</td> \<td>${box dimensions join(",")}\</td> \<td>${box weight}\</td> \<td>${box boxitems length}\</td> \</tr>`; return markup; }; once again, we’re utilizing template literals and map() to render a representative list of each instance of the item each item in the boxitems array has a color and an index, which are written into the style and data volume index attributes of a li respectively the function also accepts an index argument which represents the index of the box where the items have been packed the box’s index is passed to the data box index attribute of the li , and is used in conjunction with data volume index to target the appropriate svg on the page when hovering over the legend additionally, we’re filling out the item name, dimensions, weight and quantity (derived from the length of boxitems array) with all of this html written in the markup variable, the function returns markup as a string const generatemarkup = (svg, boxes) => { svgwrap innerhtml = svg; // add elements to wrapper layout appendchild(svgwrap); layout appendchild(itemkey); return layout; }; as we get to the end of the generatemarkup method, all that’s left to do is insert the svg markup into the svgwrap element, append it to the layout wrapper, and then do the same with the itemkey table which has been fully populated courtesy of the keyitems function let’s take a look back at the writelegend function const writelegend = (json) => { const packdata = json; const target = document queryselector("#pack list"); const svgs = packdata svgs; const boxes = packdata boxes; boxes foreach((box, index) => { // create a layout for each box target appendchild(generatemarkup(svgs\[index], box)); }); // setup listeners addlegendlisteners(); }; now that we’ve created the layouts and legends for each box in the response, it’s time to add the hover functionality adding event listeners one of the powerful features of the svg format is that it is easily addressable via css and javascript this means we can change colors, strokes, and opacities via both languages as well for this example, we’ll be using a little of both we’ll let css rules handle color & opacity, and use javascript to simply add or remove rules first, setup these rules in a \<style> tag on your html page (or in an external stylesheet) polygon{ transform translatey(0); position relative; transition\ transform 3s, fill 3s, fill opacity 3s, stroke opacity 3s; } line volume line { stroke #666; stroke dasharray 2,1; stroke width 1; } polygon volume line { stroke #666; } x ray polygon{ fill opacity 1; stroke opacity 2; } x ray polygon active{ fill opacity 1 !important; stroke opacity 1 !important; } ul legend li{ cursor\ pointer} table{ max width 100%;} figure{ width 400px; } the above styles set some defaults for the line and polygon elements contained in our returned svg — they establish a stroke color for the item graphics, as well as a width and dash array for the outer box lines additionally, there are overrides for these rules — x ray polygon and x ray polygon active these rules apply when a user hovers over an item in the legend back in writelegend , there was a reference to a function called addlegendlisteners which we will look at here const addlegendlisteners = () => { document queryselectorall("ul legend li") foreach((element) => { element addeventlistener("mouseenter", (e) => { const box = e target getattribute("data box index"); const item = e target getattribute("data volume index"); activatebox(box, item, true); }); element addeventlistener("mouseleave", (e) => { const box = e target getattribute("data box index"); const item = e target getattribute("data volume index"); activatebox(box, item, false); }); }); }; this function attaches two mouse events for all of the li elements within the legend on mouseenter , we want to highlight the corresponding item within the svg in order to do that, we grab the data box index and data volume index attributes from the li and pass them along to the activatebox method on mouseleave , we want to deactivate that box, so we make the same call to activatebox but with false as the third argument, which tells the function we are deactivating the item const activatebox = (boxid, itemid, toggle) => { const elems = document queryselectorall( `figure\[data box index="${boxid}"] polygon\[data volume index="${itemid}"]` ); const parent = document queryselector(`figure\[data box index="${boxid}"]`); if (toggle) { // x ray class is defined in html styles; this can be updated to use inline styles, etc parent classlist add("x ray"); elems foreach((item) => { item classlist add("active"); }); } else { parent classlist remove("x ray"); elems foreach((item) => { item classlist remove("active"); }); } } the function activatebox does 4 things determines which specific polygon elements in the svg represent the item the user is hovering over determines which figure is the parent of the targeted box checks if we are activating or deactivating the box applies the class x ray to the parent figure and the class active to the specific polygons (or removes these classes if we are deactivating the box) with the listeners set up, when a user hovers over a key item, it will apply x ray to the figure, causing all of the polygons inside to have the fill opacity of 1 , making them almost totally transparent at the same time, the polygons that represent that key item will have the class active appled, and thus their fill opacity set to 1 — recall above when we wrote the rule for x ray polygon active , we set the fill and stroke opacities to 1, along with an !important flag to override the rule setting everything else to 1 putting it all together we’ve covered all of the pieces of javascript (and a little css!) necessary to make a pack request to the paccurate api, and build an interactive legend based on the response let’s bundle all of the functions into their appropriate files your legend js file should look like this // demo config used in xml request at bottom const config = { itemsets \[ { refid 0, color "darkorange", weight 1, name "smaller box", dimensions { x 1, y 2, z 1, }, quantity 3, }, { refid 1, color "indigo", weight 10, name "larger box", dimensions { x 6, y 9, z 3, }, quantity 2, }, ], boxtypesets \["usps", "fedex"] }; const parseditems = (arr) => { const skus = {}; arr foreach((element) => { element = element item; const id = element name ? element name element refid; if (typeof skus\[id] === "undefined") { // push item into sku list skus\[id] = { refid element refid, name element name, weight element weight, dimensions element dimensions ? \[element dimensions x, element dimensions y, element dimensions z] \[1, 1, 1], boxitems \[ { id element uniqueid, index element index, color element color }, ], }; } else { skus\[id] boxitems push({ id element uniqueid, index element index, color element color, }); } }); const flattened = object keys(skus) map((element) => { return skus\[element]; }); console info(flattened); return flattened; }; const keyitems = (box, index) => { const markup = `\<tr> \<td> \<ul style="width 300px; list style type\ none; margin 0; padding 0;" class="legend"> ${box boxitems map((item) => { return `\<li data box index="${index}" data volume index="${item index}" style="width 20px; height 20px; margin 0 5px 5px 0; float\ left; background color ${item color}">\</li>`; }) join("")} \</ul> \</td> \<td>${box name || box refid}\</td> \<td>${box dimensions join(",")}\</td> \<td>${box weight}\</td> \<td>${box boxitems length}\</td> \</tr>`; return markup; }; const generatemarkup = (svg, boxes) => { // compress total item list into a list of skus with count, dimensions, weight, and name const parsed = parseditems(boxes box items); // box id is important if an order has multiple boxes to be packed the svg uses this id as a parent to target the inner boxes const boxid = boxes box id; // create wrapper for svg and legend let layout = document createelement("div"); let svgwrap = document createelement("div"); let itemkey = document createelement("table"); itemkey innerhtml = `\<tr> \<th>item\</th> \<th>name/id\</th> \<th>dims\</th> \<th>weight\</th> \<th>qty\</th> \</tr> ${parsed map((item) => { return keyitems(item, boxid); }) join("")} `; svgwrap innerhtml = svg; // add elements to wrapper layout appendchild(svgwrap); layout appendchild(itemkey); return layout; }; const activatebox = (boxid, itemid, toggle) => { const elems = document queryselectorall( `figure\[data box index="${boxid}"] polygon\[data volume index="${itemid}"]` ); const parent = document queryselector(`figure\[data box index="${boxid}"]`); if (toggle) { // x ray class is defined in html styles; this can be updated to use inline styles, etc parent classlist add("x ray"); elems foreach((item) => { item classlist add("active"); }); } else { parent classlist remove("x ray"); elems foreach((item) => { item classlist remove("active"); }); } }; const addlegendlisteners = () => { document queryselectorall("ul legend li") foreach((element) => { element addeventlistener("mouseenter", (e) => { const box = e target getattribute("data box index"); const item = e target getattribute("data volume index"); activatebox(box, item, true); }); element addeventlistener("mouseleave", (e) => { const box = e target getattribute("data box index"); const item = e target getattribute("data volume index"); activatebox(box, item, false); }); }); }; const writelegend = (json) => { const packdata = json; const target = document queryselector("#pack list"); const svgs = packdata svgs; const boxes = packdata boxes; boxes foreach((box, index) => { // create a layout for each box target appendchild(generatemarkup(svgs\[index], box)); }); // setup listeners addlegendlisteners(); }; document addeventlistener("domcontentloaded", function () { const request = new xmlhttprequest(); const method = "post"; const url = "\<https //api paccurate io/>"; const packobj = config; request open(method, url, true); request setrequestheader("content type", "application/json"); request onreadystatechange = function () { if (request readystate === xmlhttprequest done) { var status = request status; if (status === 0 || (status >= 200 && status < 400)) { // the request has been completed successfully try { let packresponse = json parse(request responsetext); writelegend(packresponse); } catch (e) { console error(e); } } else { console log(status); } } }; request send(json stringify(packobj)); }); with the js assembled, let’s update our legend html file with a reference to this script, include the css we discussed earlier, and make sure our target div is included on the page open up legend html in a web browser, and you’ll see a working legend! https //codepen io/paccurate/pen/jopqebz https //codepen io/paccurate/pen/jopqebz demo example you can also view the example on codepen https //codepen io/paccurate/pen/jopqebz thanks for reading this far, and if you have any questions please reach out to product\@paccurate io mailto\ product\@paccurate io back to blog