arrow cursor
< javascript tutorials

How To Create an Interactive Flex Layout Designer In JavaScript | Tweet It | CSS Visual Dictionary

Written by @js_tut (Twitter) / **updated December 6, 2018

Best tutorials come from finishing real projects. When the project is done, I like sharing what I learned.

We don't often see tutorials that actually explain how to code. But I think my time is best spent explaining the creative process behind writing code and hope that it helps someone out there to become (if not better) a more knowledgeable JavaScript programmer.

This time, the project is my css flex layout designer. I will explain how I created it from start to finish and provide example source code below.

flex editor online
An instance of flex class we're going to code in this tutorial.

Our flex layout designer will look this way. This is what we're building in this tutorial.

I think it's a fun project because the built-in css flex functionality cuts out most of the work for us. The most difficult thing here is writing the "drag and drop" code, but still it's not that hard.

To create this flex layout designer we will briefly use jQuery for cross-browser click events and jQuery.css method to dynamically set CSS properties to our flex items while they are being resized.

FIRST THINGS FIRST. Why are you creating your application? Why is it needed? (Is it?)

It's fine if you get excited about ideas. But to write effective software that people actually need, you must never write a line of code without first asking if there is a need for what you are about to create. You always start in the same way. Find an existing problem that needs to be solved. In my case, I was able to answer this question. That's because most flex tutorials I found online were either too limited or contained explanations using static images.

For Christ's sake. This is flex. The name itself alludes to the fact that things will be... flexing. That is, resizing, scaling, changing. So why all the static image tutorials for flex? This didn't sit well with me. I knew that to create a truly useful tool that has high education value, I needed an interactive approach.

Your application idea should start with an abstract vision. Usually, it is something you get excited about. But remember that your software must also solve a real, existing problem.

For me, I wanted to create a tool or generator that doesn't exist yet. Something you wish existed for personal reasons. In this case, the css flex layout examples I thought of, should have the ability to dynamically add and remove flex items from the list.

First, I Plan Some Global Variables

Global variables are usually shun upon by professional software engineers and developers. That's because modular software design (you know, when you use someone else's code, and they defined global variable names that you already used in your own code) will fail if you use them in your own library.

But for the purpose of this tutorial, we will use global variables to learn how to create something. We are not distributing this code as a library. We're not sharing it with other coders. It will only be used on one site and won't be submitted to any open source communities (such as GitHub) so we are completely safe here.

Each variable is explained with a comment.

let DEFAULT_ITEM_WIDTH  = '30px';   // Default width of a flex item (when it is added or removed)
let DEFAULT_ITEM_HEIGHT = '30px';   // Default height of a flex item (when it is added or removed)

window.mx = 0;                      // Current X mouse position
window.my = 0;                      // Current Y mouse position
window.item_x = 0;                  // When item is clicked, we track its horizontal width in this variable
window.item_y = 0;                  // When item is clicked, we track its vertical width in this variable
window.dragging = false;            // A flag to indicate whether an item is currently being resized (or not)
window.drag_x = 0;                  // Mouse X screen position at the time when last item was clicked
window.drag_y = 0;                  // Mouse Y screen position at the time when last item was clicked
window.dragging_target = null;      // The actual JavaScript element object of the item being dragged
window.dragging_id = -1;            // The ID of the item being clicked on and resized
window.item_width = 0;              // Item width, at the time it was clicked on
window.item_height = 0;             // Item height, at the time it was clicked on
window.drag_width = 0;              // The difference between current X mouse position and X mouse position at the time when item was clicked on
window.drag_height = 0;             // The difference between current Y mouse position and Y mouse position at the time when item was clicked on
window.item_id = 0;                 // Clicked item's "data-id" attribute
window.flex = null;                 // For making a copy of the instance of the flex object (for updating its HTML when item number or size changes)

window.flex1 = null;                // The 7 flex objects representing an instance of the flex class
window.flex2 = null;
window.flex3 = null;
window.flex4 = null;
window.flex5 = null;
window.flex6 = null;
window.flex7 = null;

window.funcs = [];                  // I don't know what this is :-) It's unused. But I hate to delete it.

Speaking of Software Architecture

Whenever you design an app, you will be thinking of its structure. That's what Software Architecture is. It is the structure of your application. As a software engineer, your task is to create the most efficient structure for your code. Efficient in this case means that it's easy to modify in the long run without having to rewrite a lot of code all over again. This means, you should shoot for code reuse as much as possible when designing your application architecture.

Designing software means you will be working with component-based structures. One nested within another. But this flex application example is so simple, that we only have one class. This single class is our entire architecture. Real-world application, of course, will be much more extensive than this. As far as this case, there really isn't much architecture here.

The first thing you want to do is create a JavaScript class representing the main purpose of your application. Name it elegantly. In this case I chose simply, class flex.

Sure, this works. After all, we will be instantiating objects from this class that provide a flex container.

class flex {

    constructor(_target, id, width, height, items, item_width, item_height, justify_content) {

        // I noticed that when justify-content is set to stretch, and item width is less than
        // 100% of container width (or generally, something small, like 100px) then the stretch
        // effect will not even take place. It'll just look like flex-start. So the purpose of
        // this line of code is to set all item widths (by default) to 800px, (the full size of
        // flex container.) This evenly spaces out all items in the container.
        if (justify_content == 'stretch') item_width = '800';

        // Memorize the name of the HTML element's ID attribute (without leading '#' character)
        this.tag                = _target;
        this.variable_name      = _target;

        // Grab the actual JavaScript object of the HTML container
        this.target             = document.getElementById(_target);

        // Flex container dimensions
        this.width              = width;
        this.height             = height;

        this.items              = items; // Number of items

        // Create arrays holding width and height of items and populate them with defaults
        this.items_widths       = new Array(items).fill(item_width);
        this.items_heights      = new Array(items).fill(item_height);

        // Default width and height of the item
        this.item_width         = item_width;
        this.item_height        = item_height;

        // Set justify-content property
        this.justify_content    = justify_content;

        // Is there an item being dragged/resized right now?
        this.dragging           = false;

        // Bind methods (if you don't do this they won't work)
        this.setjustifycontent  = this.setjustifycontent;
        this.source             = this.source;

        // Render entire flex HTML into the container (to dynamically update it)
        this.paste();
    }

    ... class method functions follow ... (We will get to them shortly)

This is your classes' constructor. A constructor allows you to pass arguments to its parameter names. Constructors are usually used to initialize your object with a set of default values.

  • this.tag - When I create a new flex object, I want to remember the id attribute of its HTML parent container element.
  • this.variable_name - Same thing as above, it's probably redundant but I made a copy just in case (don't do this at home.)
  • this.target - The actual JavaScript object of the container HTML element.
  • this.width - The width of the container element.
  • this.height - The height of the container element.
  • this.items - The number of items in this flex container.
  • this.items_widths - A dynamic 0-index array of widths of each item on the list.
  • this.items_heights - A dynamic 0-index array of heights of each item on the list.
  • this.item_width - The default item width of all items.
  • this.item_height - The default item height of all items.
  • this.justify_content - The value of the justify-content property for this flex container.
  • this.dragging - An item is being dragged right now (false by default.)
  • this.source - We need to bind the source class member function, otherwise it will not work.
  • this.setjustifycontent - We need to bind the setjustifycontent class member function, otherwise it will not work.

And lastly... let's call the member function paste.

  • this.paste(); - all defaults are set, render the flex HTML container with items in it for the first time when object is instantiated.

The constructor of flex class allows us to do the following (later on in our code):

// Initialize the 7 flex objects and assign them to window.flexn
window.flex1 = new flex("flex1", "flex-a", 800, 40, 1, '200px', '30px', 'flex-start');
window.flex2 = new flex("flex2", "flex-b", 800, 40, 2, '200px', '30px', 'flex-end');
window.flex3 = new flex("flex3", "flex-c", 800, 40, 3, '50px', '30px', 'center');
window.flex4 = new flex("flex4", "flex-d", 800, 40, 4, '50px', '30px', 'space-between');
window.flex5 = new flex("flex5", "flex-e", 800, 40, 5, '50px', '30px', 'space-around');
window.flex6 = new flex("flex6", "flex-f", 800, 40, 6, '50px', '30px', 'stretch');
window.flex7 = new flex("flex7", "flex-g", 800, 40, 7, '50px', '30px', 'space-evenly');

The constructor instantiates each flex object separately using the JavaScript's new keyword. We provide default values to it, so our flex object can crunch some data as a starting point during initialization. Throughout the life cycle of the application, these values may change. (For example justify-content property will be changed via on-screen links displayed just above the flex container.)

Creating Class Method Functions

At this point we provided an instantiation mechanism for our flex class. As our primary (and the only) class, it will take a lot of parameters so we can set application defaults.

The application is still abstract at this time. We don't have any functions that actually get anything done. And that's the key behind designing your software using classes. They help you define your application before you even write any code. After constructor is completed, we're ready to start working on our classes' member functions (also known as methods, in some languages.)

Method: flex.source() — create the CSS and HTML source code from class property names

The source method is what builds the source code for the flex layout associated with the flex object. It will take item number, the width of each individual item, and convert it to a string of text representing the HTML and CSS code to build the flex you designed using the flex layout designer.

Oh, and of course it will also take in consideration flex's object justify-content property. This is what determines the unique default look of each flex block.

This method provides the code that is copied to the clipboard when the user clicks on Copy To Clipboard.

    source() {

        // Start with an empty string
        let I = ``;

        // Walk trhough each item on the list and create a content container for it.
        for (let i = 0; i < this.items; i++)
            I += `    <div style = 'width: ` + this.items_widths[i] +
                 `; height: ` + this.items_heights[i] + `'>` + (i + 1) +
                 `</div>\r\n`;

        // Create the css style tag, put .flex class in it, create flex container and insert the list (we just created above) into it.
        let src = `<style type = 'text/css'>\r\n    .flex { display: flex; flex-direction: row; justify-content: ` + this.justify_content + `; width: 800px; border: 1px solid gray; padding: 4px;}\r\n    .flex div { border: 1px solid gray; padding: 4px; background: #A4F2D8; font-family: verdana; font-size: 19px; text-align: center; color: gray; }\r\n</style>\r\n<div class = 'flex'>\r\n` + I + `</div>`;

        // Return the string we just constructed
        return src;
    }

We will simply call source method every time we need to copy the code to the browser's (or operating system's) built-in paste clipboard.

Method: flex.setjustifycontent() — set justify content value, and dynamically update flex HTML

Calling setjustifycontent member function will simply set a value to CSS justify-content property on that flex container. That's what happens when you click one of those links on top of the flex container.

    setjustifycontent(justify_content) {
        // Set the property value
        this.justify_content = justify_content;
        // "Paste" the HTML into the flex container (this will update flex)
        this.paste();
    }

That's a pretty simply function, isn't it? Next, let's write the add function to add items to the list. Note, all of these functions go inside the object into the same scope where constructor is. But later in this tutorial we will write some global helper functions.

    add() {
        // Memorize widths and heights of all items
        let copyW = this.items_widths.slice();
        let copyH = this.items_heights.slice();
        // Reset all items widths and heights to their default values
        this.items_widths[this.items] = this.justify_content == 'stretch' ? '800px' : DEFAULT_ITEM_WIDTH;
        this.items_heights[this.items] = DEFAULT_ITEM_HEIGHT;
        this.items++;
        // Set all items dimensions to values we memorized earlier
        for (let i = 0; i < copyW.length; i++) this.items_widths[i] = copyW[i];
        for (let i = 0; i < copyH.length; i++) this.items_heights[i] = copyH[i];
        // Generate HTML and update view
        this.paste();
    }

The comments here are self-explanatory.

    remove() { this.items--; this.paste(); }

Removing an item is as simple as decreasing the items counter. We are not actually deleting the item's width / height arrays. This is important. We don't want to delete the item dimensions, when the item is deleted. Because if the user decides to re-add it again, we want to preserve that item's previous width and height. It just creates a better user experience.

Finally, the paste method (a bit awkwardly called so) is responsible for "pasting" the actual HTML code for the flex container and its items based on the current class parameters. This is the core behind upating the view of the application.

    paste() {

        // Start with an empty string
        let list = ``;

        // Walk through all existing items, and create item content container
        for (let i = 0; i < this.items; i++)
            list += `<div class = "item" id = "` + this.target + `_item_` + (i + 1) + `" data-id = "` + (i + 1) + `" style = 'line-height: ` + this.items_heights[i] + `; height: ` + this.items_heights[i] + `; width: ` + this.items_widths[i] + `'>` + (i + 1) + `</div>`;

        // Create the main container and insert the list we created in the above for-loop
        let source = `<div class = 'justify_content_container'>` + create_justify_content_modifier(this.tag, this.justify_content, this.variable_name) + `</div><div class = 'dynamic flex' style = 'width: ` + this.width + `; height: ` + this.height + `; justify-content: ` + this.justify_content + `'>` + list + `</div>`;

        // Finally, paste the code into content of the HTML container element, effectively (and instantly) updating the browser view
        this.target.innerHTML = source;

        // The way event functions work does not allow us to reference "this" object,
        // in the following "mousedown" callback function() {...here...}, so what we
        // will do, is store a copy in "that" variable, and use it instead (that works.)
        let that = this;

        // An item inside this.target flex container was clicked!
        $('.item', this.target).on("mousedown", function(e) {

            // Assign the "that" object to global variable window.flex
            // Using "this" here will not refer to flex class "this" but the event's "this",
            // Which in this case would refer to the object that was clicked on, but
            // that's not what we need. Instead, we store this flex class globally
            // for future operations and access from our other helper functions
            window.flex             = that;

            // The item was clicked, so apply "editing" look to it (dashed border)
            $(this).addClass("editing");

            // Update the globals with useful info
            window.dragging         = true;
            window.drag_x           = e.pageX;
            window.drag_y           = e.pageY;
            window.dragging_target  = this;
            window.item_id          = parseInt($(this).attr('data-id')) - 1;

            // Simply get initial width and height of an item we just clicked on
            // We will use this to measure its new dimensions based on
            // How far the mouse traveled from its original location
            // After the item was clicked (window.drag_x and window.drag_y)
            window.item_width       = parseInt($(this).css("width"));
            window.item_height      = parseInt($(this).css("height"));

            // Store the width of the clicked item's width and height globally
            if (this.target) window.item_x = parseInt$(this.target)[0].style.width;
            if (this.target) window.item_y = parseInt$(this.target)[0].style.height;
        });
    }
}

You see, every time we "paste" the new HTML based on classes' current property values, we need to re-attach the onclick event to this newly inserted HTML. Otherwise your old event listener will be wiped out, and nothing will happen.

    function copy_to_clipboard(target) {

        // Get flex JavaScript object (window.flex1, for example if target = "flex1")
        // and call the source method of the flex class we coded earlier
        let src = window[target].source();

        // Copy and paste can only be done from a textarea, grab its value and select it
        document.getElementById('src').value = src;
        document.getElementById('src').select();

        // Execute the native "copy" command on text currently selected in the off-screen textarea
        let copied = document.execCommand('copy');

        // Flash container's border when "Copy To Clipboard" is clicked
        // ...to let the user know the copy operation was a success
        $("#" + target).animate({borderColor: '#00F'}, 200, function(){
            $("#" + target).animate({borderColor: '#EEE'}, 200);
        });

    }

Note you need an off-screen textarea to copy code from. This is why first, it is entered into the textarea, and selected. Only then the native OS "copy to clipboard" command is called to copy that selected text.

    function setjustifycontent(object, target_id, command, value) {
    $(".link", $(object).parent()).removeClass('sel');
    $(object).addClass('sel');
    window[target_id].setjustifycontent(value);
}

This is self-explanatory too. We simply call setjustify content on target flex object. But this is a global function to accomplish that (not object's member function.)

    function create_justify_content_modifier(target_id, justify_content, object_variable_name) {
    return `<p><b>justify-content</b>   
    <span class = "link` + (justify_content == 'flex-start' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'flex-start')">flex-start</span>
    <span class = "link` + (justify_content == 'flex-end' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'flex-end')">flex-end</span>
    <span class = "link` + (justify_content == 'center' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'center')">center</span>
    <span class = "link` + (justify_content == 'space-between' ? ' sel' : '') + `" onclick = "this, setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'space-between')">space-between</span>
    <span class = "link` + (justify_content == 'space-around' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'space-around')">space-around</span>
    <span class = "link` + (justify_content == 'stretch' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'stretch')">stretch</span>
    <span class = "link` + (justify_content == 'space-evenly' ? ' sel' : '') + `" onclick = "setjustifycontent(this, '` + target_id + `', '` + object_variable_name + `', 'space-evenly')">space-evenly</span></p>`;
}

We're almost done. But we need to add a few other things.

$(document).ready(function() { // All DOM items finished loading

    // Initialize the 7 flexes
    window.flex1 = new flex("flex1", "flex-a", 800, 40, 1, '200px', '30px', 'flex-start');
    window.flex2 = new flex("flex2", "flex-b", 800, 40, 2, '200px', '30px', 'flex-end');
    window.flex3 = new flex("flex3", "flex-c", 800, 40, 3, '50px', '30px', 'center');
    window.flex4 = new flex("flex4", "flex-d", 800, 40, 4, '50px', '30px', 'space-between');
    window.flex5 = new flex("flex5", "flex-e", 800, 40, 5, '50px', '30px', 'space-around');
    window.flex6 = new flex("flex6", "flex-f", 800, 40, 6, '50px', '30px', 'stretch');
    window.flex7 = new flex("flex7", "flex-g", 800, 40, 7, '50px', '30px', 'space-evenly');

In our main JavaScript program scope, first we initialize all of the flex objects, one after another.


    $("body").on("mouseup", function() {

        // Item finished resizing, disable dragging
        window.dragging = false;

        // Remove dashed border around the item
        $(".item").removeClass("editing");

        if (window.flex) { // Update width/height array with new item dimensions
            if (window.flex.items_widths && window.flex.items_widths[window.item_id]) window.flex.items_widths[window.item_id] = window.drag_width + 'px';
            if (window.flex.items_heights && window.flex.items_heights[window.item_id]) window.flex.items_heights[window.item_id] = window.drag_height + 'px';
        }
    });

This is the global mouse "up" event. Basically, regardless of what's happening, every time the mouse button is depressed (does anyone have any lithium?), we want to reset the dragging state to false. So our other code (see below) doesn't kick in and continue to change item's size.

    // Process mouse move event while dragging the item, update its dimensions and line-height
    $("body").mousemove(function(e) {

        window.mx = e.pageX;
        window.my = e.pageY;

        if (window.dragging) {

            // Calculate drag distance difference
            let diffX = window.drag_width = parseInt((window.item_width + (window.mx - window.drag_x) * 1.75));
            let diffY = window.drag_height = parseInt((window.item_height + (window.my - window.drag_y) * 1.75));

            // Absolute values ensure the item dimensions never become negative
            $(window.dragging_target).css("width", Math.abs(diffX) + 'px');
            $(window.dragging_target).css("height", Math.abs(diffY) + 'px');
            $(window.dragging_target).css("lineHeight", Math.abs(diffY) + 'px');

            // Change cursor to either left-right or up-down arrow, based on which direction the drag is the greatest
            if (diffX > diffY) { /* Optional, maybe a future feature... */ }
        }
    })
});

And finally... the window.onload event makes sure all HTML elements and media (images, etc) have finished downloading from the server. Here, I used jQuery animation to animate the first flex item in the very first flex container, 500 milliseconds after the DOM and images have been loaded into the browser, to let the visitor know that this is an interactive flex designer, and they should click on items to resize them.

    window.onload = () => {
    setTimeout(function() {
        $("#flex1 .item").addClass("editing");
        $("#flex1 .item").animate( { width: "+=75px" }, 700);
        $("#animcursor").animate( { left: "+=75px" }, 700, function() {
            $("#flex1 .item").removeClass("editing");
            $("#animcursor").animate( { opacity: 0 }, 400, function() {
                $("#animcursor").remove();
                let B = true;
                let C = 0;
                let T = setInterval( function() {
                    if (B) { $('#flex1 .item').addClass("editing"); } else { $('#flex1 .item').removeClass("editing"); }
                    B = !B;
                    if (C++ > 6) { clearInterval(T); T = null; }
                }, 60);
            });
        });
    }, 500);
}

Congratulations! You have just created a flex layout designer in vanilla JavaScript.

I already provided the links at the beginning of this tutorial but css flex online generator here it is again.

How To Create an Interactive Flex Layout Designer In JavaScript | Tweet It | CSS Visual Dictionary