I built a responsive web app using our new widgets and libraries

by Steven Crighton on August 18, 2022 7 comments

Over the last couple of months I’ve been diving into some of the new widgets and libraries that have come to LiveCode. I’ve shown you all about building powerful tables using PolyGrid. I’ve demonstrated how easy it is to produce beautiful scrolling lists with PolyList, I even created a replica of the Airbnb list. I’ve put the spotlight on the new Responsive Layout library and it was looking very good indeed whilst making light work of changing layouts as the window size changed. I took you international when showing off the new i18n library to easily manage the translation of our apps into different languages. I’ve demonstrated that even the simplest of widgets such as Circle Avatar can save time on those repeatable tasks we do time and time again. Lastly, I showed you that buttons can be beautiful with the new Power Button widget.

I’ve had a great time producing all this content but as we come to the end of the Summer MegaBundle offer I wanted to set myself one last challenge. Can I take everything that I’ve just mentioned and produce a modern beautiful web app that we can all learn something from along the way? (Don’t want to read all this and want to skip straight to the result? There is a link at the bottom of this blog post, scroll down there to see the app in action.)

The Criteria

  • It looks beautiful.
  • it works well on the web.
  • It’s responsive, so it looks good on the largest of monitors to the smallest of laptop screens. (extensive details on this in this post)
  • It must integrate all of the widgets & libraries we have put in the Spotlight.
  • It should be done with as little code as possible.

The UI

I’ll not spend too much time on this apart from say that I wanted it to look like a modern web app. I was actually inspired by the default material look of the Power Button. I also didn’t want to over complicate the UI as I wanted to make my life a little easier when it came to responsive design. I think this is an important consideration when putting together your UI, thinking about how things might look when the window is scaled up and down.

As you can see in the above screenshot we have a top menu to indicate to the user which section you are in. We then have our main body area which in this screenshot is reserved for PolyGrid. You click on the little refresh icon to see a different PolyGrid. We also have two Power Buttons, one is for proceeding to the next step and one is a circle action button that once clicked will popup with a message.

Step 1 – PolyGrid

The first page in our web app is showing off PolyGrid. We have two modern tables on display for you to see in action.

PolyGrid View 1

The first view is actually a very simple PolyGrid. It has 5 columns, 4 of them have the content-type of “text” and 1 of them has the content-type of “svg-lcname”. The svg content-type requires the LiveCode SVG name as the value in the pgData.

Here is a look at the column layout for this PolyGrid:

Here is a look at the pgData for the first column of this PolyGrid:

As you can see in this screenshot we are providing both a color and a name for the status heading. This is what displays a green check SVG icon in the last column of the first row.

PolyGrid View 2

Just like View 1, this view is a modern table style but has a few more interesting elements.

Here you can see 5 columns, the first column includes a profile picture and the fifth column includes a coloured pill/tag to indicate a status.

Here is a look at the column layout for this PolyGrid:

Notice here how we are using the content-type of text but we have it partnered with a shape of pill. This is what allows us to have the multicoloured status shown in the fifth column.

Step 2 – PolyList

The second card in our web app is showing off PolyList. Now I have recently done two very detailed posts on PolyList so I am going to refer you to them rather than dig into too much detail here. However here is a look at the two PolyList views in our web app:

PolyList View 1

If you would like to learn step by step how this list was produced – view this blog post here on recreating the Airbnb style list.

PolyList View 2

Step 3 – Date Picker

The third page in our web app is a first look at the new Date Picker widget. This simple yet very useful widget allows you to present a date picker or a simple calendar in your app.

Here is a look at the property inspector for our DatePicker

As you will see in the web app once a new date has been selected we are putting the date into the field on the right hand side. Here is the code used to achieve that

on dateChanged
     get the selectedDate of me
     put it into field "selecteddate"
end dateChanged

Step 4 – Circle Avatar

The fourth page in our web app is a good look at the Circle Avatar widget.

The Circle Avatar widget allows you to either show a silhouette, initials, or an image and you can set the colours to whatever you like.

There have been some considerations here for web specific behaviour, specifically getting a photo and uploading this photo. I will cover this in the web specific section below.

Step 5 – i18n

The final page in our web app is showing off the new i18n library to manage internationalisation in your app. We are translating this page in 4 different languages and the i18n makes light work in doing this. We’ve done a relatively detailed blog post on this already which you can see here

Responsive Layout

Now this section deserves a little more content so we can all learn how incredible the new Responsive Layout library can be for our projects. We have an app that runs in a browser and adapts its layout nicely as the window is resized. The Responsive Layout library means that is achieved without writing a single line of code. Let’s have a look at how I did that… I will use the first page in the app to demonstrate…

The first step I took was to group all of the UI elements that I positioned in the bottom right hand corner, which is called my info group.

I wanted this to always be positioned in the bottom right corner of my window as the window resized. To achieve this I selected my group and then opened the responsive layout section within the Magic Palette plugin, then applied the following settings to it:

Now let’s look at the layout of the page. There are lots of controls and it may seem a little overwhelming as to how you would approach it. However when you look closely, for the purposes of resizing there are only 4 sections. I grouped the top menu section, I grouped the title and paragraph section, I grouped the main body section, I grouped the bottom button section. So here is how that looks…

Let’s take a look at what goes into dealing with one of these group sections.

The next step I took was double clicking on the card itself whilst having the Magic Palette open. I wanted to set the content type of the card to column.

Think of the card itself as a container, and our four groups we created earlier as its children. These groups will be placed one below the other inside that container which is what you would expect to happen in a column. It is well worth noting here that any control added to the card will automatically be placed below the last group. To change the order in which objects are arranged, change the layer of the object. The library arranges from the lowest to the highest layer.

Now let’s take a look at our body section with the Polygrid in it.

This section is a group and within the group are its children, made up of a background image, a group on the left where the SVG finance icons appear and then the PolyGrid widget itself. Think of this top level group as a container and within that container there are its children where the layering of those children are in the order of the lowest to the highest layer. First lets look at the responsive layout options given to the top layer group:

I have made the content type of this top level group “row”. This sets its containers children into a row. Within our group as I said above we have 3 children so you would expect each of these children to show in a row, but in our app only 2 of its children are respecting the row layout. Lets look at why…

Here is a look at what is going on inside our top level group…

We have a background image, we have a group over on the left hand side with our finance icons and we have our PolyGrid Widget. The group on the left hand side and the PolyGrid widget are respecting the row properties set in its parent container. The background image however is ignoring this. This is because we have set our background to be a fixed type and when you do this it ignores the properties set on its parent container.

I do realise this is a slightly different way to thinking about your layouts in LiveCode but when you do start to click with this it does make building responsive UI’s a joy.

Let’s have a look at the properties we set to the child items of our group.

This is our background image with a fixed type applied and therefore ignoring our row rules. This is what allows this to act as a background image and remain in the correct position.
This is our left hand group section that contains our two SVG finance icons and as you can see there are no explicit rules set on this group so it then takes on it’s parents properties of being a child item inside a row.
This is for our PolyGrid widget. It has one additional property set to its container and that is asking it to be expandable. This will adhere to its parents row properties but take on the property of being expandable. This means that it will take up all of the free space that is has available to it.

I will put this sample app for download at the bottom of this blog post. Once you start to understand the concept of groups being the container and the items inside the group being its children Responsive Layout starts to become a very useful and powerful tool for laying out your UI.

Web Specific Considerations

LiveCode web deployment is in great shape but there are still a few things that we need to do today that we will probably not have to do in the future (or at least it will be easier) to make our applications work perfectly on the web. So I thought I had best put these things here just now and then let you know when they are no longer needed.

Web specific consideration number 1

Out of the box PolyList and PolyGrid were not scrolling on the web. This will be addressed in future iterations but here is how I got that working so you too can get it working in your own apps.

I have added the following code to my stack script specifically to deal with this.

on preopenstack
  set the cJavascriptHandlers of this stack to "scrollEvent"
end preopenstack

local sPendingScrollsA

on scrollEvent pDeltaX, pDeltaY
   local tHaveScrolled
   put false into tHaveScrolled
   
   repeat for each item tWidgetName in "PolyGrid,PolyGrid2,PolyList,PolyList2"
      if there is a widget tWidgetName of me and \
            the visible of widget tWidgetName of me and \
            the mouseLoc is within the rect of widget tWidgetName of me then
         if sPendingScrollsA[tWidgetName] is not empty then
            add pDeltaX to sPendingScrollsA[tWidgetName]["delta-x"]
            add pDeltaY to sPendingScrollsA[tWidgetName]["delta-y"]
         else
            put pDeltaX into sPendingScrollsA[tWidgetName]["delta-x"]
            put pDeltaY into sPendingScrollsA[tWidgetName]["delta-y"]
            send "doScrollEvent" && quote & tWidgetName & quote to me in 25 milliseconds
         end if
         
         put true into tHaveScrolled
      end if
   end repeat
   
   return tHaveScrolled
end scrollEvent

on doScrollEvent pWidgetName
     set the vscroll of widget pWidgetName of me to \
           the vScroll of widget pWidgetName of me + sPendingScrollsA[pWidgetName]["delta-y"]
     delete variable sPendingScrollsA[pWidgetName]
end doScrollEvent

I then added the following code to my standalone.html file

<script>
document.getElementById("canvas").addEventListener("wheel", function(event) {
  if (document.liveCode) {
    var myStack = document.liveCode.findStackWithName("bundle");
    if (myStack) {
      if (myStack.scrollEvent(event.deltaX, event.deltaY)) {
           event.preventDefault();
      }
    }
  }
});
</script>

Now what the heck is this doing? Essentially it is looking to find out if there is a widget which matches any of the specific names, then if you are inside the rect of that widget to fire our Scroll Event and temporarily disable the default browser scrolling. Feel free to ask questions about this in the comments below if you would like to know any more about it. If you want to use this, just make sure you change the JavaScript findStackWithName section to match the name of your stack, and then either change your PolyList or PolyGrid widgets to match the names I have given in the stack script or change the stack script to match the names of your widgets.

Web specific consideration number 2

On our Circle Avatar page we want to be able to upload an image from the users computer. answer file does not work on the web yet so I needed to look for other options. Here is a look at the code I have added to the stack script to achieve this:

on preopenstack
  set the cJavascriptHandlers of this stack to "FileSelected" & return & "BinaryFileSelected""
end preopenstack

on FileSelected pFilename  
end FileSelected

on BinaryFileSelected pFileData
     set the text of widget "avatar4" to pFileData
end BinaryFileSelected

Here is what I have put on the “upload your image” button on the Circle Avatar card:

on mouseup
   if the platform is "web" then 
      do "const input = document.createElement('input'); input.type = 'file'; input.onchange = e => { if (e.target.files[0]) { document.liveCode.findStackWithName('bundle').FileSelected(URL.createObjectURL(e.target.files[0])); const fileReader = new FileReader(e.target.files[0]); fileReader.onload = e => { document.liveCode.findStackWithName('bundle').BinaryFileSelected(fileReader.result); }; fileReader.readAsBinaryString(e.target.files[0]); } }; input.click();" as "JavaScript"

   end if
end mouseup

This is creating an HTML file input element. Clicking on that element brings up the file picker. Once the user has selected a file, the code then sends a callback to LiveCode with the file data the user picked, which we populate our avatar widget with. Again if you have any questions regarding any of this just ask away in the comments.

Web specific consideration number 3

At the moment launch url does not work in web deployment so I had to find a way around this. This was very simple but it’s not something you will likely have to be concerned about in the future.

Here is what I added to my button script where I wanted to launch a URL in a new tab:

on mouseup
     do "window.open('https://livecode.com/tag/enhancement-bundle/')" as javascript
end mouseup

The results

What we all came to see… Did you really read this far or did you skip to the end? Either way thank you for being here!

Click here to see our web app running in a browser

Remember to resize the browser window to see the responsive layout in action…

It’s been a great and fun challenge and I hope one that you can take something from into your own projects. I hope you have been enjoying the content on all the new items included in the Summer Mega Bundle (ending in 7 days at the time of writing this post). Here is the project zip for you to download, note that the majority of this will only work if you have the new widgets and libraries that are included in the bundle installed.

As always I want to hear from you. If you have any questions, anything I missed out, anything that was not clear, anything else you would like to see. Let me know. For now. Challenge complete.

Steven CrightonI built a responsive web app using our new widgets and libraries

Related Posts

Take a look at these posts

7 comments

Join the conversation
  • mikey - August 18, 2022 reply

    1. Launch example on an ipad in landscape
    2. re-orient to portrait
    Notice how the PG vertical scroller overlays the last column…

    Steven Crighton - August 18, 2022 reply

    Hi Mikey, Yes I see that on the dark PolyGrid layout but not so much on the Light PolyGrid layout. There are a few ways I could solve this…

    1. In this specific example I think I would probably just choose not to show the scrollbar, as it’s not needed for scrolling and I personally think it would look better. If you go to the 2nd page on the app and hit the refresh icon to show the airbnb style PolyList, you will notice I am not using the scrollbar there.

    2. I could make some adjustments to the width of my overall PolyGrid on the dark view layout and tweak the column widths to allow more free space at the last column to avoid this overlapping happening. Again I think this would be a legitimate fix for this specific issue and would not take me much time.

    3. We could make some changes at the widget development level to reserve extra space for the scrollbar. This is certainly something we are going to look at and discuss.

    Thanks for the feedback.

  • Sandro - August 19, 2022 reply

    Hi Steven,
    My report on iPhone 11:
    in general, in the portrait view, the text is too small and also the choice button of the Polygrid or Polylist. Zoom creates problems in viewing.
    In the landscape view, everything is clearly visible.

    Step 1 – Polygrid
    Portrait view: first polygrid is ok, the second cannot be scrolled through the list
    Landscape view: only a part of the polygrid is shown and you cannot flow, this applies to both views (black and white)

    Step 2 – Polylist
    Portrait view: same situation
    Landscape view: only a part of the polylist is shown and you cannot flow, this applies to both views (preferences – images)

    Step 3-4-5
    Portrait view: great
    Landscape view: great

    In the desktop the application is fine, it is more faster with Chrome compared to Safari and everything works.
    It is understood that the application was designed for the desktop, however an excellent job 🙂

    Steven Crighton - August 19, 2022 reply

    Hi Sandro,

    Thank you for taking the time to give such detailed feedback and I am glad you enjoyed the post.

    I need to give this a bit of thought as to how I would approach this to make the experience on mobile even better.

    A couple of things that I should mention, I did set out to make a desktop web app but I wanted it to display well at mobile size but it’s certainly not optimised for mobile. I think If I was to have another go at this and I was wanting one UI to work on desktop and mobile that I would have designed things a little differently. I actually think a possible scenario here would be to consider switching layouts to a mobile specific layout once a certain width breakpoint is reached. Maybe my menu would change at mobile size for example to a mobile specific designed menu. We are constantly looking to improve Responsive Layout and making that easier to do is something that is currently in testing.

    Another thing to mention is, in this blog post you will have seen that I listed some web specific considerations that we have to worry about just now but won’t have to worry about later. One of them was scrolling, my implementation is good for desktop browsers (mouse wheel / trackpad scrolling) but does not work for mobile gesture scrolling. I would need to find a different JS event to hook into to get that working. We will be taking care of this in future iterations.

    The last thing I would mention and it’s something I am going to go away and give a little bit of thought too. What would the best experience be for viewing our grids/tables on mobile, how should the layout adapt to make the experience of viewing in some cases quite complex tables at mobile size. I don’t have an immediate answer here but It’s something I will give thought too and it may spark an idea for a future post.

  • Jose - August 19, 2022 reply

    Hi – how can I close/hide the Date Picker upon selection of a date? I am trying to not take up screen real estate so only want to show the widget when needed.

    Steven Crighton - August 19, 2022 reply

    Hi Jose, I guess there are a couple of ways you could address this. You could set the visible of the widget, there is an example of how to do this in the sample app, not with the datepicker widget but with a group. If you look at the code in the info button group, that is simply setting the visible of it on demand.

    The other way would be to use the datepicker as a popup. So if you add a button to your stack, then add this code (put together pretty quickly) to the script editor for that button

    
    on mouseup
       //get answerDate( the label of me)
       set the label of me to answerDate( the label of me)
    end mouseup
    
    function answerDate pDate
       local tProps
       put "0,0,168,213" into tProps["rect"]
       put "255,255,255" into tProps["backgroundColor"]
       put "128,128,128" into tProps["borderColor"]
       put "128,128,128" into tProps["headerColor"]
       put "129,141,211" into tProps["hiliteColor"]
       put "233,233,233" into tProps["mouseOverColor"]
       if pDate is not empty then put pDate into tProps["selectedDate"]
          
       put false into tProps["AutoResizeFont"]
       put "Background Circle" into tProps["hiliteStyle"]
       put false into tProps["padDates"]
     
       popup widget "com.livecode.widget.datepicker" with properties tProps
       return it
    end answerDate

    This is popping up a Date Picker on demand, setting the properties of it in code and once a date is selected it is updating the label of the button to show the selected date. Hopefully these ideas give you something to go on.

  • James Hale - April 10, 2023 reply

    Hi Steve,
    Great post. I have been looking for docs on the responsive layout ever since getting it and this has given me some hope, and will maybe provide Heather a little respite from my questions 😉
    I didn’t know dbl clicking on a card would allow RL control of it. Something that may have saved a few hairs from being yanked. What would really help is an explanation of what effect the terms have on the group they are assigned to. I.e. what does “centered” actually mean? Is the group kept in the canter of the card vertically and horizontally or something else? Are there handlers we should avoid using in our scripts else they will interfere with the RL? My interest in using the RL is to handle object layout for mobile apps. In redoing an existing app that had handlers accounting for orientation and screen size, can these be eliminated by using the RL? Can they interfere with the RL if left in? Your screenshots of the RL does not show the breakpoint section. How does one use these? What do they do?
    Anyway thanks for the post. It has given some hope in being able to bette use this tool.
    Documentation for these tools (at least explanations of what all the options mean) is sorely needed.
    An index of blog post would help too.

Join the conversation

*