Image Grid with HTML


By chance, I came across an Image Grid demo on W3Schools, and this got me thinking about how I might use it in App Inventor, having noticed many folks having trouble using the gallery. I had to do a fair bit of work to the html W3Schools provided, in order to make it dynamic and interactive, also a fair bit of figuring out with the dreaded Android file system!  I decided to focus on files in the ASD, because this is the easiest location to save and read files. Those cleverer than I can take on the Shared Storage or SAF options. My test device assets directory is littered with images, so useful for demoing this effort


<!DOCTYPE html>


<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">


<title>Image Grid</title>


* {

  box-sizing: border-box;


body {

  margin: 0;

  font-family: Arial, Helvetica, sans-serif;

  background: #e3e3e3;


.header {

  text-align: center;

  padding: 10px;

  background: #e3e3e3;


.row {

  display: -ms-flexbox; /* IE 10 */

  display: flex;

  -ms-flex-wrap: wrap; /* IE 10 */

  flex-wrap: wrap;

  padding: 0 4px;


/* Create two equal columns that sits next to each other */

.column {

  -ms-flex: 50%; /* IE 10 */

  flex: 50%;

  padding: 0 4px;


.column img {

  margin-top: 8px;

  vertical-align: middle;


/* Style the buttons */

.btn {

  border: none;

  outline: none;

  padding: 10px 16px;

  background-color: #f1f1f1;

  cursor: pointer;

  font-size: 18px;


.btn:hover {

  background-color: #ddd;

} {

  background-color: #666;

  color: white;


/* The sticky class is added to the header with JS when it reaches its scroll position */

.sticky {

  position: fixed;

  top: 0;

  width: 100%


/* Add some top padding to the page content to prevent sudden quick movement (as the header gets a new position at the top of the page (position:fixed and top:0) */

.sticky + .content {

  padding-top: 102px;





<div class="header" id="myHeader">

<button class="btn" onclick="one()">1</button>

<button class="btn active" onclick="two()">2</button>

<button class="btn" onclick="four()">4</button>


<div id="row" class="row">

<div id="col1" class="column"></div>

<div id="col2" class="column"></div>

<div id="col3" class="column"></div>

<div id="col4" class="column"></div>



var str = window.AppInventor.getWebViewString();

var lst = (str).split(",");

var path = lst.shift();

var chunks = chunk(lst,parseInt(lst.length/4) +1);

for (var j=0;j<chunks.length;j++) {

for (var i=0;i<chunks[j].length;i++) {

var x = document.createElement("IMG");

x.setAttribute('onclick','whichImage("' + path + chunks[j][i] + '")');

x.setAttribute('src',path + chunks[j][i]);


document.getElementById("col" + (j + 1).toString()).appendChild(x);



function whichImage(imgPath) {



        // this function splits the csv list of images to 4 items in an array

function chunk(arr, chunkSize) {

        if (chunkSize <= 0) throw "Invalid chunk size";

        var R = [];

        for (var i=0,len=arr.length; i<len; i+=chunkSize)


       return R;


// Get the elements with class="column"

var elements = document.getElementsByClassName("column");

// Declare a "loop" variable

var k;

// Full-width images

function one() {

for (k = 0; k < elements.length; k++) {

elements[k].style.flex = "100%";



// Two images side by side

function two() {

for (k = 0; k < elements.length; k++) {

elements[k].style.flex = "50%";



// Four images side by side

function four() {

for (k = 0; k < elements.length; k++) {

elements[k].style.flex = "25%";



// Add active class to the current button (highlight it)

        var header = document.getElementById("myHeader");

        var btns = header.getElementsByClassName("btn");

        for (var i = 0; i < btns.length; i++) {

        btns[i].addEventListener("click", function() {

        var current = document.getElementsByClassName("active");

        current[0].className = current[0].className.replace(" active", "");

        this.className += " active";



        // scroll grid view back to the top when changing view / getting new directory of images

        window.onbeforeunload = function () {



        // When the user scrolls the page, execute stickIt function

        window.onscroll = function() {stickIt()};

        // Get the header

        var header = document.getElementById("myHeader");

        // Get the offset position of the navbar

        var sticky = header.offsetTop;

        // Add the sticky class to the header when you reach its scroll position. Remove "sticky" when you leave the scroll


        function stickIt() {

        if (window.pageYOffset > sticky) {


        } else {








With the html/css/javascript all writtem, it is just a case uploading the resultant html file to the assets. The file will then be copied to the "files" directory of the ASD, in order to make best use of the relative file paths created by the javascript.

In the blocks, I creates a list of the directories present in the ASD, and present these in a listview. If there are no jpg or png files present, then the user is asked to select again. (I forgot to show that in the video...)

Once a folder is selected, a list of all the image files (jpg/png) is made, and added to the list is the path. This is then set as the webviewstring and the html file called from its location in the ASD. The javascript and css do their magic, and present all the image files in a grid. You can select between 1,2 and 4 columns to view. If an image in the grid is clicked, then the image is returned to an image component in the app, along with the filename and path, for use in the app.



I may rework this to use online image resources.