Blog

Categorised by 'Client-side'.

  • I've been using the gatsby-plugin-smoothscroll plugin in the majority of GatsbyJS builds to provide a nice smooth scrolling effect to a HTML element on a page. Unfortunately, it lacked the capability of providing an offset scroll to position, which is useful when a site has a fixed header or navigation.

    I decided to take the gatsby-plugin-smoothscroll plugin and simplify it so that it would not require a dependency on polyfilled smooth scrolling as this is native to most modern browsers. The plugin just contains a helper function that can be added to any onClick event with or without an offset parameter.

    Usage

    The plugin contains a smoothScrollTo helper function that can be imported onto the page:

    // This could be in your `pages/index.js` file.
    
    import smoothScrollTo from "gatsby-plugin-smoothscroll-offset";
    

    The smoothScrollTo function can then be used within an onClick event handler:

    <!-- Without offset -->
    <button onClick={() => smoothScrollTo("#some-id")}>My link without offset</button>
    
    <!-- With offset of 80px -->
    <button onClick={() => smoothScrollTo("#some-id", 80)}>My link with offset</button>
    

    Demo

    A demonstration of the plugin in use can be found by navigating to my Blog Archive page and clicking on any of the category links.

    Prior to this plugin, the category list header would be covered by the sticky navigation.

    Smooth Scrolling without Offset

    Now that an offset of 80px can be set, the category list header is now visible.

    Smooth Scrolling with Offset

    Links

  • The Google Maps Distance Matrix API gives us the capability to calculate travel distance and time between multiple locations across different modes of transportation, such as driving walking, or cycling. This is just one of the many other APIs Google provides to allow us to get the most out of location and route related data.

    I needed to use the Google Distance Matrix API (GDMA) to calculate the distance of multiple points of interests (destinations) from one single origin. The dataset of destinations consisted of sixty to one-hundred rows of data containing the following:

    • Title
    • Latitude
    • Longitude

    This dataset would need to be parsed to the GDMA as destinations in order get the information on how far each item was away from the origin. One thing came to light during integration was that the API is limited to only outputting 25 items of distance data per request.

    The limit posed by the GDMA would be fine for the majority of use-cases, but in my case this posed a small problem as I needed to parse the whole dataset of destinations to ensure all points of interests were ordered by the shortest distance.

    The only way I could get around the limits posed by the GDMA was to batch my requests 25 destinations at a time. The dataset of data I would be parsing would never exceed 100 items, so I was fairly confident this would be an adequate approach. However, I cannot be 100% certain what the implications of such an approach would be if you were dealing with thousands of destinations.

    The code below demonstrates a small sample-set of destination data that will be used to calculate distance from a single origin.

    /*
    	Initialise the application functionality.
    */
    const initialise = () => {
    	const destinationData = [
                        {
                          title: "Wimbledon",
                          lat: 51.4273717,
                          long: -0.2444923,
                        },
                        {
                          title: "Westfields Shopping Centre",
                          lat: 51.5067724,
                          long: -0.2289425,
                        },
                        {
                          title: "Sky Garden",
                          lat: 51.3586154,
                          long: -0.9027887,
                        }
                      ];
                      
    	getDistanceFromOrigin("51.7504091", "-1.2888729", destinationData);
    }
    
    /*
    	Processes a list of destinations and outputs distances closest to the origin.
    */
    const getDistanceFromOrigin = (originLat, originLong, destinationData) => {
      const usersMarker = new google.maps.LatLng(originLat, originLong);
      let distanceInfo = [];
      
      if (destinationData.length > 0) {
      	// Segregate dealer locations into batches.
        const destinationBatches = chunkArray(destinationData, 25);
    
        // Make a call to Google Maps in batches.
        const googleMapsRequestPromises = destinationBatches.map(batch => googleMapsDistanceMatrixRequest(usersMarker, batch));
    
        // Iterate through all the aynchronous promises returned by Google Maps batch requests.
        Promise.all(googleMapsRequestPromises).then(responses => {
          const elements = responses.flatMap(item => item.rows).flatMap(item => item.elements);
    
          // Set the distance for each dealer in the dealers data
          elements.map(({ distance, status }, index) => {
            if (status === "OK") {
              destinationData[index].distance = distance.text;
              destinationData[index].distance_value = distance.value;
            }
          });
          
          renderTabularData(destinationData.sort((a, b) => (a.distance_value > b.distance_value ? 1 : -1)));
        })
        .catch(error => {
          console.error("Error calculating distances:", error);
        });
      }
    }
    
    /*
    	Outputs tabular data of distances.
    */
    renderTabularData = (destinationData) => {
    	let tableHtml = "";
      
        tableHtml = `<table>
                        <tr>
                            <th>No.</th>
                            <th>Destination Name</th>
                            <th>Distance</th>
                        </tr>`;
    
    	if (destinationData.length === 0) {
            tableHtml += `<tr colspan="2">
                            <td>No data</td>
                        </tr>`;
      }
      else {
            destinationData.map((item, index) => {
      		        tableHtml += `<tr>
                                    <td>${index+1}</td>
                                    <td>${item.title}</td>
                                    <td>${item.distance}</td>
                                </tr>`;
                });
      }
      
      tableHtml += `</table>`;
      
      document.getElementById("js-destinations").innerHTML = tableHtml;
    }
    
    /*
    	Queries Google API Distance Matrix to get distance information.
    */
    const googleMapsDistanceMatrixRequest = (usersMarker, destinationBatch) => {
      const distanceService = new google.maps.DistanceMatrixService();
      let destinationsLatLong = [];
      
      if (destinationBatch.length === 0) {
      	return;
      }
      
      destinationBatch.map((item, index) => {
        destinationsLatLong.push({
          lat: parseFloat(item.lat),
          lng: parseFloat(item.long),
        });
      });
      
      const request = 
            {
              origins: [usersMarker],
              destinations: destinationsLatLong,
              travelMode: "DRIVING",
            };
    
      return new Promise((resolve, reject) => {
        distanceService.getDistanceMatrix(request, (response, status) => {
          if (status === "OK") {
            resolve(response);
          } 
          else {
            reject(new Error(`Unable to retrieve distances: ${status}`));
          }
        });
      });
    };
    
    /*
    	Takes an array and resizes to specified size.
    */
    const chunkArray = (array, chunkSize) => {
      const chunks = [];
    
      for (let i = 0; i < array.length; i += chunkSize) {
        chunks.push(array.slice(i, i + chunkSize));
      }
    
      return chunks;
    }
    
    /*
    	Load Google Map Distance Data.
    */
    initialise();
    

    The getDistanceFromOrigin() and googleMapsDistanceMatrixRequest() are the key functions that take the list of destinations, batches them into chunks of 25 and returns a tabular list of data. This code can be expanded further to be used alongside visual representation to render each destination as pins on an embedded Google Map, since we have the longitude and latitude points.

    The full working demo can be found via the following link: https://jsfiddle.net/sbhomra/ns2yhfju/. To run this demo, a Google Maps API key needs to be provided, which you will be prompted to enter on load.

  • There are times when you need to call multiple API endpoints to return different data based on the same data structure. Normally, I'd go down the approach of manually creating multiple Axios GET requests and then inserting the endpoint responses into a single object array. But there is a more concise and readable way to handle such tasks.

    With the help of DummyJson.com, we will be using the product search endpoint to search for different products to consolidate into a single array of product objects: /products/search?q=.

    As you can see from the code below, we start off by populating an array with a list of API endpoints where multiple GET request can be carried out for each endpoint from our array. The requests variable contains an array of promises based on each of these GET requests.

    Finally, axios.all() allows us to to make multiple HTTP requests to our endpoints altogether. This function can only iterate through a collection of promises. For more information regarding this Axios function, I found the following article very insightful for a better understanding: Using axios.all to make concurrent requests.

    // List all endpoints.
    let endpoints = [
      'https://dummyjson.com/products/search?q=Laptop',
      'https://dummyjson.com/products/search?q=phone',
    ];
    
    // Perform a GET request on all endpoints.
    const requests = endpoints.map((url) => axios.get(url));
    
    // Loop through the requests and output the data.
    axios.all(requests).then((responses) => {
    	let data = [];
    
      responses.forEach((resp) => {
    	  data.push(...resp.data.products)
      });
      
      // Output consolidated array to the page.
      const template = $.templates("#js-product-template");
      const htmlOutput = template.render(data);
    
      $("#result").html(htmlOutput);
    });
    

    As we're looping through each request, we push the response to our data array. It is here where we merge all requests together into a single array of objects. To make things a little more easier to display the results to the page, I use the jsrender.js templating plugin.

    A working demo can be seen on JsFiddle.

  • ActiveCampaign is a comprehensive marketing tool that helps businesses automate their email marketing strategies and create targeted campaigns. If the tracking code is used, visitors can be tracked to understand how they interact with your content and curate targeted email campaigns for them.

    I recently registered for a free account to test the waters in converting readers of my blog posts into subscribers to create a list of contacts that I could use to send emails to when I have published new content. For this website, I thought I'd create a Contact Form that will serve the purpose of allowing a user to submit a query as well as being added to a mailing list in the process.

    ActiveCampaign provides all the tools to easily create a form providing multiple integration options, such as:

    • Simple JavaScript embed
    • Full embed with generated HTML and CSS
    • Link to form
    • WordPress
    • Facebook

    As great as these out-of-the-box options are, we have no control over how our form should look or function within our website. For my use, the Contact Form should utilise custom markup, styling, validation and submission process.

    Step 1: Creating A Form

    The first step is to create our form within ActiveCampaign using the form builder. This can be found by navigating to Website > Forms section. When the "Create a form" button is clicked, a popup will appear that will give us options on the type of form we would like to create. Select "Inline Form" and the contact list you would like the form to send the registrations to.

    My form is built up based on the following fields:

    • Full Name (Standard Field)
    • Email
    • Description (Account Field)

    ActiveCampaign Form Builder

    As we will be creating a custom-built form later, we don't need to worry about anything from a copy perspective, such as the heading, field labels or placeholder text.

    Next, we need to click on the "Integrate" button on the top right and then the "Save and exit" button. We are skipping the form integration step as this is of no use to us.

    Step 2: Key Areas of An ActiveCampaign Form

    There are two key areas of an ActiveCampaign form we will need to acquire for our custom form to function:

    1. Post URL
    2. Form Fields

    To get this information, we need to view the HTML code of our ActiveCampaign Contact form. This can be done by going back to the forms section (Website > Forms section) and selecting "Preview", which will open up our form in a new window to view.

    ActiveCampaign Form Preview

    In the preview window, open up your browser Web Inspector and inspect the form markup. Web Inspector has to be used rather than the conventional "View Page Source" as the form is rendered client-side.

    ActiveCampaign Form Code

    Post URL

    The <form /> tag contains a POST action (highlighted in red) that is in the following format: https://myaccount.activehosted.com/proc.php. This URL will be needed for our custom-built form to allow us to send values to ActiveCampaign.

    Form Fields

    An ActiveCampaign form consists of hidden fields (highlighted in green) and traditional input fields (highlighted in purple) based on the structure of the form we created. We need to take note of the attribute names and values when we make requests from our custom form.

    Step 3: Build Custom Form

    Now that we have the key building blocks for what an ActiveCampaign form uses, we can get to the good part and delve straight into the code.

    import React, { useState } from 'react';
    import { useForm } from "react-hook-form";
    
    export function App(props) {
      const { register, handleSubmit, formState: { errors } } = useForm();
        const [state, setState] = useState({
            isSubmitted: false,
            isError: false
          });    
    
        const onSubmit = (data) => {
            const formData = new FormData();
    
            // Hidden field key/values.
            formData.append("u", "4");
            formData.append("f", "4");
            formData.append("s", "s");
            formData.append("c", "0");
            formData.append("m", "0");
            formData.append("act", "sub");
            formData.append("v", "2");
            formData.append("or", "c0c3bf12af7fa3ad55cceb047972db9");
    
            // Form field key/values.
            formData.append("fullname", data.fullname);
            formData.append("email", data.email);
            formData.append("ca[1][v]", data.contactmessage);
            
            // Pass FormData values into a POST request to ActiveCampaign.
            // Mark form submission successful, otherwise return error state. 
            fetch('https://myaccount.activehosted.com/proc.php', {
                method: 'POST',
                body: formData,
                mode: 'no-cors',
            })
            .then(response => {
                setState({
                    isSubmitted: true,
                });
            })
            .catch(err => {
                setState({
                    isError: true,
                });
            });
        }
    
      return (
        <div>
            {!state.isSubmitted ? 
                <form onSubmit={handleSubmit(onSubmit)}>
                    <fieldset>
                        <legend>Contact</legend>
                        <div>
                            <div>
                                <div>
                                    <label htmlFor="fullname">Name</label>
                                    <input id="fullname" name="fullname" placeholder="Type your name" className={errors.fullname ? "c-form__textbox error" : "c-form__textbox"} {...register("fullname", { required: true })} />
                                    {errors.fullname && <div className="validation--error"><p>Please enter your name</p></div>}
                                </div>
                            </div>
                            <div>
                                <div>
                                    <label htmlFor="email">Email</label>
                                    <input id="email" name="email" placeholder="Email" className={errors.contactemail ? "c-form__textbox error" : "c-form__textbox"} {...register("email", { required: true, pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/ })} />
                                    {errors.email && <div className="validation--error"><p>Please enter a valid email</p></div>}
                                </div>
                            </div>
                            <div>
                                <div>
                                    <label htmlFor="contactmessage">Message</label>
                                    <textarea id="contactmessage" name="contactmessage" placeholder="Message" className={errors.contactmessage ? "c-form__textarea error" : "c-form__textarea"} {...register("contactmessage", { required: true })}></textarea>
                                    {errors.contactmessage && <div className="validation--error"><p>Please enter your message</p></div>}
                                </div>
                            </div>
                            <div>
                                <input type="submit" value="Submit" />
                            </div>
                        </div>
                    </fieldset>
                    {state.isError ? <p>Unfortunately, your submission could not be sent. Please try again later.</p> : null}    
                </form>
                : <p>Thank you for your message. I will be in touch shortly.</p>}
        </div>
      );
    }
    

    The form uses FormData to store all hidden field and text input values. You'll notice the exact same naming conventions are used as we have seen when viewing the source code of the ActiveCampaign form.

    All fields need to be filled in and a package called react-hook-form is used to perform validation and output error messages for any field that is left empty. If an error is encountered on form submission, an error message will be displayed, otherwise, the form is replaced with a success message.

    Demo

    ActiveCampaign Custom Form Demo

    We will see Obi-Wan Kenobi's entry added to ActiveCampaign's Contact list for our test submission.

    ActiveCampaign Contact List

    Conclusion

    In this post, we have demonstrated how a form is created within ActiveCampaign and understand the key areas of what the created form consists of in order to develop a custom implementation using GatsbyJS or React.

    Now all I need to do is work on the front-end HTML markup and add this functionality to my own Contact page.

  • It's not often you happen to stumble across a piece of code written around nine or ten years ago with fond memories. For me, it's a jQuery Countdown timer I wrote to be used in a quiz for a Sky project called The British at my current workplace - Syndicut.

    It is only now, all these years later I've decided to share the code for old times sake (after a little sprucing up).

    This countdown timer was originally used in quiz questions where the user had a set time limit to correctly answer a set of multiple-choice questions as quickly as possible. The longer they took to respond, the fewer points they received for that question.

    If the selected answer was correct, the countdown stopped and the number of points earned and time taken to select the answer was displayed.

    Demonstration of the countdown timer in action:

    Quiz Countdown Demo

    Of course, the version used in the project was a lot more polished.

    Code

    JavaScript

    const Timer = {
        ClockPaused: false,
        TimerStart: 10,
        StartTime: null,
        TimeRemaining: 0,
        EndTime: null,
        HtmlContainer: null,
    
        "Start": function(htmlCountdown) {
            Timer.StartTime = (new Date()).getTime() - 0;
            Timer.EndTime = (new Date()).getTime() + Timer.TimerStart * 1000;
    
            Timer.HtmlContainer = $(htmlCountdown);
    				
            // Ensure any added styles have been reset.
            Timer.HtmlContainer.removeAttr("style");
    
            Timer.DisplayCountdown();
            
            // Ensure message is cleared for when the countdown may have been reset.
            $("#message").html("");     
            
            // Show/hide the appropriate buttons.
            $("#btn-stop-timer").show();
            $("#btn-start-timer").hide();
            $("#btn-reset-timer").hide();
        },
        "DisplayCountdown": function() {
            if (Timer.ClockPaused) {
                return true;
            }
    
            Timer.TimeRemaining = (Timer.EndTime - (new Date()).getTime()) / 1000;
    
            if (Timer.TimeRemaining < 0) {
                Timer.TimeRemaining = 0;
            }
    
            //Display countdown value in page.
            Timer.HtmlContainer.html(Timer.TimeRemaining.toFixed(2));
    
            //Calculate percentage to append different text colours.
            const remainingPercent = Timer.TimeRemaining / Timer.TimerStart * 100;
            if (remainingPercent < 15) {
                Timer.HtmlContainer.css("color", "Red");
            } else if (remainingPercent < 51) {
                Timer.HtmlContainer.css("color", "Orange");
            }
    
            if (Timer.TimeRemaining > 0 && !Timer.ClockPaused) {
                setTimeout(function() {
                    Timer.DisplayCountdown();
                }, 100);
            } 
            else if (!Timer.ClockPaused) {
                Timer.TimesUp();
            }
        },
        "Stop" : function() {
            Timer.ClockPaused = true;
            
            const timeTaken = Timer.TimerStart - Timer.TimeRemaining;
            
            $("#message").html("Your time: " + timeTaken.toFixed(2));
            
            // Show/hide the appropriate buttons.        
            $("#btn-stop-timer").hide();
            $("#btn-reset-timer").show();
        },
        "TimesUp" : function() {
            $("#btn-stop-timer").hide();
            $("#btn-reset-timer").show();
            
            $("#message").html("Times up!");        
        }
    };
    
    $(document).ready(function () {
        $("#btn-start-timer").click(function () {
        	Timer.Start("#timer");
        });
        
        $("#btn-reset-timer").click(function () {
        	Timer.ClockPaused = false;
        	Timer.Start("#timer");
        });
        
        $("#btn-stop-timer").click(function () {
            Timer.Stop();
        });
    });
    

    HTML

    <div id="container">
      <div id="timer">
        -.--
      </div>
      <br />
      <div id="message"></div>
      <br />  
      <button id="btn-start-timer">Start Countdown</button>
      <button id="btn-stop-timer" style="display:none">Stop Countdown</button>
      <button id="btn-reset-timer" style="display:none">Reset Countdown</button>
    </div>
    

    Final Thoughts

    When looking over this code after all these years with fresh eyes, the jQuery library is no longer a fixed requirement. This could just as easily be re-written in vanilla JavaScript. But if I did this, it'll be to the detriment of nostalgia.

    A demonstration can be seen on my jsFiddle account.

  • I've worked on numerous projects that required the user to upload a single or a collection of photos that they could then manipulate in some manner, whether it was adding filtering effects or morphing their face for TV show promotion.

    In any of these projects, the user's uploaded photo must be kept for a specific amount of time - long enough for the user to manipulate their image. The question that had always arisen in terms of GDPR, as well as development perspective, was: How long should the users' uploaded photos be stored?

    Previously, these photos were stored in the cloud in a temporary blob storage container, with an hourly task that removed images older than 6 hours. This also ensured that the storage container remained small in size, lowering usage costs.

    Then one day, it hit me... What if a user's uploaded photos could be stored locally through their own browser before any form of manipulation? Enter local storage...

    What Is Local Storage?

    Local storage allows data to be stored in the browser as key/value pairs. This data does not have a set expiration date and is not cleared when the browser is closed. Only string values can be stored in local storage - this will not be a problem, and we'll see in this post how we'll store a collection of images along with some data for each.

    Example: Storing Collection of Photos

    The premise of this example is to allow the user to upload a collection of photos. On successful upload, their photo will be rendered and will have the ability to remove a photo from the collection. Adding and removing a photo will also cause the browser's localStorage` to be updated.

    Screenshot: Storing Images in Local Storage

    A live demo of this page can be found on my JSFiddle account: https://jsfiddle.net/sbhomra/bts3xo5n/.

    Code

    HTML

    <div>
      <h1>
        Example: Storing Images in Local Storage
      </h1>
      <input id="image-upload" type="file" />
      <ul id="image-collection">    
      </ul>
    </div>
    

    JavaScript

    const fileUploadLimit = 1048576; // 1MB in bytes. Formula: 1MB = 1 * 1024 * 1024.
    const localStorageKey = "images";
    let imageData = [];
    
    // Render image in HTML by adding to the unordered list.
    function renderImage(imageObj, $imageCollection) {
      if (imageObj.file_base64.length) {
        $imageCollection.append("<li><img src=\"data:image/png;base64," + imageObj.file_base64 + "\"  width=\"200\" /><br />" + imageObj.name + "<br /><a href=\"#\" data-timestamp=\"" + imageObj.timestamp + "\" class=\"btn-delete\">Remove</a></li>")
      }
    }
    
    // Add image to local storage.
    function addImage(imageObj) {
      imageData.push(imageObj);
      localStorage.setItem(localStorageKey, JSON.stringify(imageData));
    }
    
    // Remove image from local storage by timestamp.
    function removeImage(timestamp) {
      // Remove item by the timestamp.
      imageData = imageData.filter(img => img.timestamp !== timestamp);
    
      // Update local storage.
      localStorage.setItem(localStorageKey, JSON.stringify(imageData));
    }
    
    // Read image data stored in local storage.
    function getImages($imageCollection) {
      const localStorageData = localStorage.getItem(localStorageKey);
    
      if (localStorageData !== null) {
        imageData = JSON.parse(localStorage.getItem(localStorageKey))
    
        for (let i = 0; i < imageData.length; i++) {
          renderImage(imageData[i], $imageCollection);
        }
      }
    }
    
    // Delete button action to fire off deletion.
    function deleteImageAction() {
      $(".btn-delete").on("click", function(e) {
        e.preventDefault();
    
        removeImage($(this).data("timestamp"));
    
        // Remove the HTML markup for this image.
        $(this).parent().remove();
      })
    }
    
    // Upload action to fire off file upload automatically.
    function uploadChangeAction($upload, $imageCollection) {
      $upload.on("change", function(e) {
        e.preventDefault();
    
        // Ensure validation message is removed (if one is present).
        $upload.next("p").remove();
    
        const file = e.target.files[0];
    
        if (file.size <= fileUploadLimit) {
          const reader = new FileReader();
    
          reader.onloadend = () => {
            const base64String = reader.result
              .replace('data:', '')
              .replace(/^.+,/, '');
    
            // Create an object containing image information.
            let imageObj = {
              name: "image-" + ($imageCollection.find("li").length + 1),
              timestamp: Date.now(),
              file_base64: base64String.toString()
            };
    
            // Add To Local storage
            renderImage(imageObj, $imageCollection)
            addImage(imageObj);
    
            deleteImageAction();
    
            // Clear upload element.
            $upload.val("");
          };
    
          reader.readAsDataURL(file);
        } else {
          $upload.after("<p>File too large</p>");
        }
      });
    }
    
    // Initialise.
    $(document).ready(function() {
      getImages($("#image-collection"));
    
      // Set action events.
      uploadChangeAction($("#image-upload"), $("#image-collection"));
      deleteImageAction();
    });
    

    The key functions to look at are:

    • addImage()
    • removeImage()
    • getImages()

    Each of these functions uses JSON methods to store uploaded photos as arrays of objects. Each photo contains: name, timestamp and a base64 string. One common piece of functionality used across these functions is the use of JSON methods to help us store our collection of photos in local storage:

    • JSON.stringify() - to convert an array to a string.
    • JSON.parse() - to convert a JSON string into an object array for manipulation.

    When saving or retrieving your saved value from local storage, a unique identifier through a "key" needs to be set. In my example, I've set the following global variable that is referenced whenever I need to use the "localStorage" methods.

    const localStorageKey = "images";
    

    When saving to localStorage, we will have to stringify our array of objects:

    localStorage.setItem(localStorageKey, JSON.stringify(imageData));
    

    Retrieving our array requires us to convert the value from a string back into an object:

    imageData = JSON.parse(localStorage.getItem(localStorageKey))
    

    After we've uploaded some images, we can see what's stored by going into your browsers (for Firefox) Web Developer Tools, navigating to the "Storage" tab and selecting your site. If using Chrome, go to the "Applications" tab and click on "Local Storage".

    Browser Developer Tools Displaying localStorage Values

    Storage Limits

    The maximum length of values that can be stored varies depending on the browser. The data size currently ranges between 2MB and 10MB.

    When I decided to use local storage to store user photos, I was concerned about exceeding storage limits, so I set an upload limit of 1MB per photo. When I get the chance to use my code in a real-world scenario, I intend to use Hermite Resize to implement some image compression and resizing techniques.

  • Published on
    -
    1 min read

    Autoplaying HTML5 Video In Chrome

    Whilst working on the new look for my website, I wanted to replace areas where I previously used low-grade animated GIF's for the more modern HTML5 video. Currently, the only place I use HTML5 video is on my 404 page as a light-hearted reference to one of the many memorable quotes that only fans of the early Star Trek films will understand. These are the films I still hold in very high regard, something the recent "kelvin timeline" films are missing. Anyway, back to the post in hand...

    Based on Chrome's new policies introduced in April 2018 I was always under the impression that as long as the video is muted, this won't hinder in any way the autoplay functionality. But for the life of me, mt HTML5 video did not autoplay, even though all worked as intended in other browsers such as Firefox.

    You can work around Chrome's restrictions through JavaScript.

    Code

    The HTML is as simple as adding your HTML5 video.

    <video id="my-video" autoplay muted loop playsinline>
         <source src="/enterprise-destruction.mp4" type="video/mp4" />
    </video>
    

    All we need to do is target our video and tell it to play automatically. I have added a timeout to the script just to ensure the video has enough time to render on the page before our script can do its thing.

    var myVideo = $("#my-video");
    
    setTimeout(function () {
        myVideo.muted = true;
        myVideo.play();
    }, 100);
    

    It's worth noting that I don't generally write much about front-end approaches (excluding JavaScript) as I am first and foremost a backend developer. So this might not be the most ideal solution and appreciate any feedback.

  • I've been doing some personal research into improving my own JavaScript development. I decided to get more familiar with the new version of JavaScript - ES6. ES6 is filled to the brim with some really nice improvements that make JavaScript development much more concise and efficient. After having the opportunity to work on React and React Native projects, I had a chance in putting my new found ES6 knowledge to good use!

    If I had to describe ES6 in a sentence:

    JavaScript has gone on a diet and cut the fat. Write less, do more!

    I have only scratched the surface to what ES6 has to offer and will continue to add more to the list as I learn. If you are familiar with server-side development, you might notice some similarities from a syntax perspective. That in itself shows how far ES6 has pushed the boundaries.

    Arrow Functions

    Arrow functions are beautiful and so easy on the eye when scrolling through vast amounts of code. You'll see with arrow functions, you'll have the option to condense a function that consists of many lines all the way down to single line.

    The traditional way we are all familiar with:

    // The "old school" way..
    function addSomeNumbers(a, b) {
        return a + b;
    }
    
    console.log(addSomeNumbers(1, 2));
    // Output: 3
    

    ES6:

    // ES6.
    const addSomeNumbers = (a, b) => {
        return a + b;
    }
    
    console.log(addSomeNumbers(1, 2));
    // Output: 3
    

    The traditional and ES6 way can still be used in the same way to achieve our desired output. But we can condense out arrow function further:

    // Condensed ES6 arrow function.
    const addSomeNumbers = (a, b) => a + b;
    
    console.log(addSomeNumbers(1, 2));
    // Output: 3
    

    Default Function Parameters

    When developing using server-side languages, such as C# you have the ability to set default values on the parameters used for your functions. This is great, since you have more flexibilty in using a function over a wider variety of circumstances without the worry of compiler errors if you haven't satisfied all function parameters.

    Lets expand our "addSomeNumbers()" function from our last section to use default parameters.

    // Condensed ES6 arrow function with default parameters.
    const addSomeNumbers = (a=0, b=0) => a + b;
    
    console.log(addSomeNumbers());
    // Output: 0
    

    This is an interesting (but somewhat useless) example where I am using "addSomeNumbers()" function without passing any parameters. As a result the value 0 is returned and even better - no compiler error.

    Destructuring

    Destructuring sounds scary and complex. In its simple terms, destructuring is the process of adding values to an object or array to an existing variable more straightforward. Lets start of with a simple object and how we can output these values:

    // Some info on my favourite Star Trek starship...
    const starship = {
      registry: "NCC-1701-E",
        captain: "Jean Luc Picard",
        launch_date: "October 30, 2372",
        spec: {
          max_warp: 9.995,
          mass: "3,205,000 metric tons",
          length: "685.7 meters",
          width: "250.6 meters",
          height: "88.2 meters"
      }
    };
    

    We would normally output the these values in the following way:

    var registry = starship.registry; // Output: NCC-1701-E
    var captain = starship.captain; // Output: Jean Luc Picard
    var launchDate = starship.launch_date; // Output: October 30, 2372
    

    This works well, but the process of returning those values is a little repetitive and spread over many lines. Lets get a bit more focus and go down the ES6 route:

    const { registry, captain, launch_date } = starship;
    
    console.log(registry); // Output: NCC-1701-E 
    

    How amazing is that? We've managed to select a handful of these fields on one line to do something with.

    My final example in the use of destructuring will evolve around an array of items - in this case names of starship captains:

    const captains = ["James T Kirk", "Jean Luc Picard", "Katherine Janeway", "Benjamin Sisko"]
    

    Here is how I would return the first two captains in ES5 and ES6:

    // ES5
    var tos = captains[0];
    var tng = captains[1];
    
    // ES6
    const [tos, tng ] = captains;
    

    You'll see similarities to our ES6 approach for getting the values out of an array as we did when using an object. The only thing I need to look into is how to get the first and last captain from my array? Maybe that's for a later post.

    Before I end the destructuring topic, I'll add this tweet - a visual feast on the basis of what destructuring is...

    Destructuring. Courtesy of @NikkitaFTW pic.twitter.com/j8OX3VyrTL
    — Burke Holland (@burkeholland) May 31, 2018

    Spread Operator

    The spread operator has to be my favourite ES6 feature, purely because in my JavaScript applications I do a lot of data manipulation. If you can get your head around destructuring and the spread operator, you'll find working with data a lot easier. A spread operator is "...". Yes three dots - ellipsis if you prefer. This allows you to copy the values of an object to be used as a basis of a new object.

    In its basic form:

    const para1 = ["to", "boldly", "go"];
    const para2 = [...para1, "where", "no", "one"];
    const para3 = [...para2, "has", "gone", "before"];
    
    console.log(para1); // Output: ["to", "boldly", "go"]
    console.log(para2); // Output: ["to", "boldly", "go", "where", "no", "one"]
    console.log(para3); // Output: ["to", "boldly", "go", "where", "no", "one", "has", "gone", "before"]
    

    As you can see from my example above, the spread operator used on variables "para1" and "para2" creates a shallow copy of the array values into our new array. Gone are the days of having to use a for loop to get the values.

  • ReactJSI've been meddling around with ReactJS over the last week or so, seeing if this is something viable to use for future client projects. I am always constantly on the lookout to whether there are better alternatives on how my fellow developers and I develop our sites.

    Throughout the sample applications I've been building, I constantly asked myself one question: Why Would I Use ReactJS In My Day To Day Development? I am ASP.NET developer who build websites either using Web Forms or MVC Razor. So I am finding it difficult to comprehend whether using ReactJS is viable in these frameworks, especially MVC.

    ReactJS is primarily a view framework where you have the ability to write component-based web applications directly into your JavaScript that then gets output to the DOM virtually - making for a very fast and responsive webpage. It's a different approach to developing websites that I quite like. But for the moment, I just don't see how it can benefit me when the full MVC framework does a pretty good job with all the bells and whistles.

    For example, I segregate all my key HTML markup into partial views in order to increase re-use throughout my web application, which works really well when making AJAX calls where the markup needs to be displayed on the page asynchronously as well as server-side. I can just see by implementing ReactJS, I will be duplicating this process at JavaScript and CSHTML level if a markup change ever needed to be made. If partial views does the job effectively, I'm not too sure the need for ReactJS in my future ASP.NET MVC creations.

    Don't get me wrong - I really like ReactJS. It makes writing JavaScript an even more enjoyable experience purely due to the JSX syntax. Long gone are the days where you have to concatenate strings to form HTML. More importantly, it's readable and truly scalable.

    Unfortunately, it doesn't look like ReactJS is a viable option for me at this moment in time. I can see how it would be a very useful framework for building web applications where there is a requirement for the view to be created strictly client-side along with heavy use of AJAX calls from an API layer to serve data to your application. But in situations where you have two frameworks that provide the ability to create views, in this case ReactJS and ASP.NET MVC, it doesn't make sense.

    I could be talking absolute nonsense and missing the whole point. If this is the case (most likely!), please leave a comment.

  • My Nexus 5 upgraded to Android version 5.0 a few months back and it's by far the best update yet (apart from the minor bugs). An OS that is as beautiful to look at as well as use.

    One of the most intriguing things I noticed was that the colour of my Chrome browser address bar would occasionally change if I went certain websites. Being a developer who works in the web industry, this peaked my interest. So I had to find out how to do this.

    After doing some online research, I found adding the this feature couldn't be simpler. Just add the following META tag to your page:

    <meta name="theme-color" content="#4c7a9f">
    

    I carried out this change on my site and it looks kinda cool!

    Before
    Android Chrome Browser Colour (Before)
    After
    Android Chrome Browser Colour (After)