CPU Information

I have spent the past few weeks improving major parts of this app and I am finally at the point of releasing the new and shiny interface.

My ultimate goal for this app is to pull in all of the views into 3D and outshine other similar apps in beauty and create the best app for the job ( as usual ). This is a new beginning for the app and I will likely spend the next few months improving it.


I love iron Man like HUD interfaces. As such I am building a library with multiple interfaces for different visualization purposes. The interface above is a switch-board with a connected gyroscope and the Earth centered in it.

After the initial release I want to create a terminal emulation which I can scale at will in 3D space, followed by an audio interface which coverts input from the microphone into a nice 3D visual.

There are many more possibilities and I am having fun creating these playful widgets.


Another area which I want to invest some time in is to work with some custom shaders to bring out some glowing special effects and other goodies.

OpenGL ES 2.0 is a strange beast and not necessarily the easiest environment to learn but it offers some great flexibility in what you can accomplish. The ES, which stands for Embedded Systems, introduces a few changes as compared to the desktop OpenGL implementation.

OpenGL ES 2.0 Programming guide
OpenGL ES 2.0 Programming guide

I am currently going through the “OpenGL ES 2.0 programming guide”, which is a real good book to learn OpenGL. It will help me to realize the remaining widgets and animations to convert the complete app into the 3D realm.

Please give it a try and let me know what you think of it. Also I would love to hear if you have any ideas for a widget which I could implement.

Get it on Google Play

Android Storage Wars

If you have ever tried your hand on coding for Android you certainly have found google’s JAVA based API to be somewhat of a incoherent mess in certain areas, which over time has evolved into an even larger incoherent mess in even more areas.

One of those messy areas is that of the android secondary storage API. All phones have a primary storage area, where you can store images, videos, apps, and application data, and some phones come with a secondary storage option. The API to gain access to the primary storage has evolved over time but allows you to seamlessly integrate proper functionality into your app.

Now you may assume that the Android secondary storage would follow the same paradigm, and grant the same access as the primary storage to your app. However you would be wrong making this assumption. The reason you have apps like the “ES File Manager” handling this for you seamlessly is purely through tricky code in the background which circumvent googles protective scheme for secondary storage, Aka a hack.

Dont do evil … yeah right!

Yes, google in its drive to push people into using their cloud storage is actively pressuring phone makers to get rid of that large secondary storage, or worse the micro-SD slot. That overarching goal of google is driving the API and the access rights to the Androids secondary storage media in a negative way.

Let’s start with a small bit of terminology. Since the dawn of Android, nearly every type of storage has been considered “external storage” even the non-removable flash memory that comes in every device. To distinguish, the non-removable flash memory is called “primary storage”<, while a removable micro SD card is called “secondary storage”. In the early Android versions, an app merely needed permission to WRITE_EXTERNAL_STORAGE to be able to fully access both primary and secondary storage.

In March, 2011, a drastic change to Android was made which changed how secondary storage mediums (external SD cards) were mounted by the Android OS. Specifically, the commit read like this: “Mount secondary external storage writable by AID_MEDIA_RW rather than AID_SDCARD_RW.” This meant that the media would now have to belong to the “media_rw” group to modify the contents of the SD card. As a result, a new permission, called WRITE_MEDIA_STORAGE, was added to the source code. Basically, WRITE_MEDIA_STORAGE replaced the original WRITE_EXTERNAL_STORAGE.

This change has a devastating effect on apps trying to utilize secondary storage because from now on forward only system apps could request the permission to read/write to the external storage ( I.e. sdcard ).

Android secondary storage

Since the WRITE_EXTERNAL_STORAGE permission could be used by any normal app and thus any app can write to the primary storage but not to the secondary storage (external micro SD cards). The WRITE_MEDIA_STORAGE permission could write to the external micro SD card, but regular user apps could not request the permission. This change took place on Android 3.2 Honeycomb, but due to the logistics of hardware and storage mediums at that time, it was barely noticed.

Quote: “Starting around the time of Android 4.4 4 KitKat, and even a little earlier, the implementation of the userspace FUSE filesystem, in conjunction with changes to the open source code, resolved this problem. Yet, still to today, even with Android 8.x.x Oreo, third party apps must explicitly get the user’s permission for both primary and secondary storage before the app can alter either storage medium in any way. And of course, the newer concepts of adoptable storage have alleviated problems in this regard.” — end quote.

Bottom line

Thanks to google the way that most apps now handle android secondary storage heavily depends on the Android version being used. Unfortunately at this time most phones will have a hard time working with the secondary storage, but some phones do. I have updated the file browser integration into my apps and have fixed the issue of secondary storage on some Android versions and not on others.

This whole debacle has not only cost me weeks of effort and research, it has negatively affected all apps with file system access and is the main reason that almost all apps only storing their data on the primary storage, which in most cases is much smaller than the external storage. At this tmie you can get a cheap microSD card with up to 512 GB. However what good is this in your phone if most apps can only use the built-in 16GB ?

Can the Google Play Store Stats be trusted

The Play Store Stats contain important metrics about your apps in the Google Play Store. Any app developer has noticed by now ( August 2018 ) that Google is changing the stats displayed in the developer console and in the app store.

One of the largest changes has been the removal of the “Total Installs” from the play store stats in the developer console. A move which is causing a lot of trouble to me because I relied on it for my growth goals. Google’s reasoning behind this is that it wants quality apps and as such the number of active installs is what Google wants developers to focus on.

I used to look at both numbers together and determine what the rate of adoption was ( E.g. 22% of people installing my app would retain it ). Now however I have to gobble these numbers together through a few more mouse clicks. By choosing “Number Of Installs” for the period “Lifetime” of the app.

Google Play Console Total Downloads

Another unfortunate change that I have just noticed is the elimination of the displayed download tiers in the Play Store. I have a fairly new app n the app store which has just crossed the 5k download mark. The Play store used to honor this by displaying Installs 5000+. However, as of today, with a total install of 5,535 I do see this in the Play Store:
Google Play Store Total Installs

I also noticed that frequently the displayed metrics are distorted after Google changes ( Aka tweaks ) the formula behind the metric. For example the number of active devices would unexpectedly drop from one day to another by a few hundred. However at the same time, the play store stats showed a positive difference between installed and uninstalled apps.

The number of active devices is “Number of Android devices that have been active in the previous 30 days that your app is installed on.”. If that’s the case and Google’s number would be accurate it would mean a hell of a coincidence that a few hundred users of my app stopped using their phone at the same day.

Stats Madness at Google

I wonder how many more changes there will be in the future for app developer. After all if I can not rely on the metric displayed in the app store stats, what are the chances that the numbers in Google Ads are accurate ? I guess there is really no way to tell.

Below is the link to Reverse Video Magic. Can you see how many installs ?
Get it on Google Play

Revert Video Magic Video Android-app

After returning from vacation this year I wanted to edit and send some videos to my family which should highlight some of the more outstanding experiences in a funny but interesting way. The Revert Video Magic app for Android on the google play store was exactly what was needed for this job.

I was looking to take a piece of a video, and revert parts of it and then replace this part with a sequence of forward, backward, forward action all abound. Here is the resulting video to better explain the effect I was after.

I looked around but could only find one app on the google play market which would offer me this functionality. Revert Video Magic for Android

The below images are screenshots of the main screens for Revert Video Magic for Android

The Main Screen is where you select the source video from, that is you can either browse through the media library of your phone or you open a File Browser which allows you to browse the file system of your phone. You can even use your video camera and shoot the video from within the app.

Revert Video Magic Main Screen
Main Screen
Revert Video magic settings screen
Revert Video Settings screen
Revert Video Magic Share Screen
Share Screen

Once you have selected the source video you would like to use, you arrive at the settings page. The Revert Video Magic Settings page allows you to modify the resulting video resolution, the speed of the reverted part, as well as cutting the start and end points of the target video.

Once these settings are set, you will see the Post Processing screen of the revert Video Magic app. This page allows you to actually replace the selected part in-place of where the original video part was. None of the other revert video apps on the google play store do allow for this to happen.

Using the -SHARE- button on the top right corner of the Share screen will then allow you to finally send the video to your friends or share it on social media like YouTube, FaceBook etc.

This app is focused on doing one thing but doing it right. The built in video player, Camera functionality, File Browser, and Share functionality are all part of making reversing part of your video as much fun as the final video.

Feel free to download the app for free from the link below.

Get it on Google Play

What martial arts has taught me about leadership

As a leader and manager of people you are at times confronted with tough choices and when the pressure rises and things start to get heated, I like to lean back and reflect at my younger years.

During my late teens and all the way into my thirties I have been a student of martial arts. I have spent countless hours in Dojos all around the globe and I have enjoyed it to the point of obsessing over it.

Shotokan Karate Kata bunkai
Shotokan Karate Kata bunkai, Nijushiho

My base has been traditional Shotokan Karate but I loved straying off course and discovering other martial arts such as JuJitsu, Judo, Aikido, Aikibudo, Shinjitsu, Kempo, and more.

I could go on about my glory days and the benefits of practicing these arts and the physical strengths you will gain if you are practicing between four and seven times a week. However this article is not the about physical strength but about mental strength.


Karate requires practice, and it requires a lot of it. This is due to learning the proper coordination of movements to end in a single focused blow to your opponents body.

You start out to learn your basic moves. At first, they will look and feel odd. You have to use your mind to force your body to follow the moves. Then, as you move up in the ranks, you learn to internalize these moves and as they become more natural to your body, you start to learn how to apply those moves in combination with other moves.

kihon, the basics
kihon, the basics

Oi zuki, mae geri, gyaku zuki, or in tongue-in-cheek-English “Punch, kick, other-punch”, is a basic combination you learn early on. It will take about three to four training sessions to get used to this particular combination, and it will take you between five and ten years to master it. Really, I mean it, it will take that long to learn this basic combination to the point where you can apply it as a matter of reflex rather than -Think- about applying it.

Here is where this will help you with your leadership development.

As a leader you too have to learn basic moves in order to learn and grow in your field. You usually start at level one, which John Maxwell calls the positional leader. At this level you have become a manager of sorts over a team of people. At this point you will have to learn to Earn Trust.
At first this will feel odd. You have to Think about every decision and every meeting you have with your team. Then, as you grow your trust in your team, you learn to internalize these lessons and as they become more natural to your mind, you start to learn how to get to level 2, which is the permission level.

The Oddness of leadership
The Oddness of leadership


From Wikipedia: “Traditionally, kata are taught in stages. Kata is often described as a set sequence of karate moves organised into a pre-arranged fight against imaginary opponents. The kata consists of kicks, punches, sweeps, strikes and blocks. Body movement in various kata includes stepping, twisting, turning, dropping to the ground, and jumping.”

Shotokan Kata
Shotokan Kata

While this will seem strange at first you will eventually come to realize that performing a Kata with the right emphasis and the right mindset will help you improve your techniques, your strength, your form, and your timing.

Once you have a fair grip on the basics ( kihon ) as a leader, you need to learn to combine a whole range of different qualities and environments in order to achieve the best results with and for your team. You have to study different approaches to solve certain situations to be able to pull them out when you have to. You have to be able to set and monitor goals, give feedback, encourage, request, teach, be empathetic or strong. You have to add as many tools to your repertoire as you can, and as a leader you have to be willing and able to use those tools.

In most cases you will be able to select your management style. From tough love, to lending a shoulder to cry on. There have been plenty of studies which seem to show that there is no one-size-fits-all approaches to management. However in Leadership you have to have certain characteristics which will help you achieve level two with your team members. You have to have a mindset of servanthood. You grow yourself and your team. You are very good at observing and listening.

At level two, the permission level, people will follow you because they want to. Your team trusts your decisions and they trust that you have their back. It is where you build relationships with your team, they like you and you like them. In return, they do work hard to keep their commitments to you and your company.

Now at this level, results starting to appear.

One of my Sensei in Kata Bunkai – action


Kumite is the practical application of kihon and kata. It is, in essence, a formalized fight following stringent rules. These rules are in place to allow the student to learn and practice a combat situation without the fear of a broken nose or other bodily harm. Kumite is what most people think of as a martial arts competition and is as close to combat as traditional Karate will get you.

You will not only learn to apply your techniques but you will also learn to be relaxed, concentrated, anticipate when an attack will happen and improve your reflexes. One important lesson I have taken home from practicing kumite was to always move forward, never move back. The best benefit of kumite however is that of friendship.

If you as a leader are at level three, the production level, you will lead by example. You work hard and you start to help the bottom line of the company. You start to build positive, reinforcing momentum and that momentum will help you break through the largest obstacles.
If you can get yourself and your team to this level, you will soon learn to be relaxed in a stressful environment, concentrate and focus on finding the required solution for the business, and anticipate when an you will have to make a change to the team, the project, or the goals before they become a real issues.


At this point you will always move forward. The only time you can lose is when you give up on something. You cannot be defeated for as long as you learn and grow and try again. And that folks is true in all parts of life.


Karate, which stands for empty-hand, the karate-do, or the way of karate is given great emphasis. It is where the bushido, or the way of the warrior, makes its mark on Karate and lets us reflect on the roots of the art we are students of. This is in contrast to sports like UFC, or modern self defense styles which may or may not borrow from the traditional martial arts. In the self-defense sports, the goal is to enable the trainee to defend himself in certain situations.

My personal motivation to training martial arts has always been to seek control over mind and body, as well as staying in good shape. I was never under the illusion to defend with a single kick against a knife or a semi automatic. I have had the pleasure of training kids, and lower ranks in those days and it was one of the most rewarding activities I have ever experienced.

karate - do
karate – do

After all, in nature, we are bound to grow up, Learn from others, Apply the lessons learned, and then pass the information on to The next generation. Do as I do, not as I say has become one of my rules. In order to get there though I had to become the best I could be and I had to remain in that shape for as long as I wanted to teach.

Leaders as well should always strive to follow learn from others, apply those lessons, and then pass them on to your team.

“Leadership is a journey and not a destination”

The wise Sensei

For me, those days have long since passed and I know that the younger-me could pick me apart in an instance. However the ‘do’ ( the way ) has remained with me ever since. I am working hard, I am humble in working with and for my team. As a leader, I am forward looking and I have the back of every single individual in my team.

I try to lead by example and teach and grow my team. As a leader you should strive to be the Samurai and not the Ninja. Be committed to your team and your company, learn, read, study your whole life and most importantly practice what you preach.

John Maxwell – 5 levels of leadership

Crypto Portfolio app

Keeping track of your overall Crypto Portfolio is not as straight forward as it should be. Investing into crypto currencies can leave you with multiple wallets and online resources. Each one may contain different coins and tokens for different purposes.

It is highly recommended to store all ( or as much as possible ) offline, detached from the internet. This way hackers won’t be able to get their fingers on your hard earned Bitcoins, Ethereum, etc.

I have started to look into Crypto currencies in early 2018 and I see great potential in the basic ideas behind the technologies. While my timing was especially bad I wanted to know what the fuzz was all about. The best way to understand things though is to start playing with them. So I did spend some time and money learning a new technology.

Shut up and take my money


You need to sign up with and exchange when you want to buy some coins or tokens in order to convert your dollars into a crypto-currency. With Coinbase being one of one most popular which people would recommend for n00bs.

Coinbase allows you to convert to deposit funds using your credit card or your bank account information. Paypal is no longer supported. You can then exchange your fiat currency ( I.e. Dollars, Euros etc. ) into Bitcoin, Bitcoin Cash, Etherium, or Litecoin. Any other crypto currency requires you to sign up with another exchange of which there are many to choose from.

Coinmarketcap.com, and coincap.io provide you the information as to which exchange supports the crypto token you are interested in.

Binance is one of those larger exchanges which has received good online reviews and has supposedly the lowest transaction fees.

Getting your hands on some crypto coins or tokens also requires you to safely store them somewhere. Here we have probably one of the weakest points in this area. Every crypto-currency has their own way of storing the tokens. You can keep them on your computer, in an online wallet, in offline wallets or as a paper wallet. You will quickly arrive at a point where you can lose track of your virtual wealth ( or as we have seen recently, the decline thereof ).

Crypto Portfolio

So I wrote a Crypto Portfolio app which will allow you to enter your token amount and then look at the market data for it. I will not store this information on the server but instead store it in your browsers’ cache so you don’t have to fear releasing any data to my servers.

One thing I quickly realized is that almost everything you do in crypto will cost you either a fee or a transaction cost.

Binance fees

So yeah Binance has a 0.1% trading fee however one thing which you will not see right away is that you will not be able to convert all of one crypto token into another crypto token.

Binance will always leave you with some leftover on their platform. These may vary but my worst case leftover position is worth about US$1.00.

On top of that Binance also requires a minimum transaction for certain coins. E.g. you require at least US$97.181- ( as of Feb 2 2018 ) in Etherium before you can transfer it out of Binance.

Then you have to pay for the actual transaction fees which for Etherium ar 0.01ETH, which is … well about $9.71 as of this date.

Since Binance does not display this information in a useful way I went ahead and added this into a table in my little app which you can sort to gather some more information on trading conditions of a certain coin. [ Menu -> Utils -> Binance Fees ]

So you add this all up and you end up spending a lot of money in the whole exchange process.

If you exchange E.g. $100,- by the time it leaves Coinbase you are down to about $97,- Then you exchange it for ETH on Binance and send it back to your private wallet, you end up with only about $85,- worth of ETH.

This of course does not take into account the huge market swings, which could increase or decrease your position a few percentage points in the time it takes to go through the whole operation.

To sum it all up

First though I have to remind you that; yes it is possible to lose money, which is why you should never put in more than you can afford to lose.

This app is in its early stages and I will certainly add more features as time permits. Please feel free to check it out here …

Please leave me some feedback for enhancements if you find it useful. Or follow the link below to the source code, make changes yourself, and then send them to me for integration 🙂


Graphing library used : NVD3 Re-usable charts for d3.js

If you click on the image below you can start using the app online from within my WebOS.
You can find it under [Start -> Utils -> Crypto ]. Note: This app does not send any data back to any server.

Feel you want to play with the code: follow the link to the Battlefield.

Nine weird things to code

So the weekend is here and you are sitting in front of your computer and don’t know what to do.
You are still bored after watching YouTube videos all day.

Don’t Worry, you have come to the right place. I will show you how to display funny text,
add a spinning 3D globe to your web page, build a calculator for your math homework or in case you work requires you to calculate the remaing fuel contents on your rocket entring an orbit around Mars. and more …

Here are 9 things you can code when you are bored.

Hello World

This “Hello World” jumps out and is a real attention seeking device. Your eyes are automatically caught paying more attention to it. If you want to drive people into madness you can display your whole page using this quirk.
You will certainly be rewarded with a firestorm of complaints.


<center><b><div id="OUT"></div></b></center>
  var div = document.getElementById ( "OUT" );
  div.innerHTML = "Hello World";
  function randomClr ( char )  {
    var r = parseInt ( Math.random ( ) * 255, 10 );
    var g = parseInt ( Math.random ( ) * 255, 10 );
    var b = parseInt ( Math.random ( ) * 255, 10 );
    var ret = "<font color='rgb(" + r + "," + g + "," + b + ");'>" + char + "</font>";
    return ret;
  function randomType ( char )  {
    var ret=char, r = parseInt ( Math.random ( ) * 4, 10 );
    switch ( r )  {
      case 0: ret = "<b>" + char + "</b>"; break;
      case 1: ret = "<i>" + char + "</i>"; break;
      case 2: ret = "<sup>" + char + "</sup>"; break;
      case 3: ret = "<sub>" + char + "</sub>"; break;
    return ret;
  setInterval ( function ( ) {  
    var html="", text = "Hello World";
    for ( var t=0; t<text.length; t++ )  {
      var clr = randomClr ( text[t] );
      html += randomType ( clr );
    div.innerHTML = "<h1>" + html + "</h1>";
  }, 500 );

Random Password generator

This generator will allow you to create your very own randomly generated password string.

<center><b><div id="OUT"></div></b></center>
    var div = document.getElementById ( "OUT" );
    var randomString = function(length) {
      var text = "";
      var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
      for(var i = 0; i < length; i++) {
          text += possible.charAt(Math.floor(Math.random() * possible.length));
      return text;
    div.innerHTML = randomString ( 20 );

I got some interesting feedback wrt this generator and decided to provide another version which will use the crypto API
inside the browsers instead of Math.random ( );

<!DOCTYPE html>

<center><b><div id="OUT"></div></b></center>
    var div = document.getElementById ( "OUT" );
    var randomString = function(length) {
      var text = "";
      var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
      var cryp = new  Uint8Array ( possible.length );
      var rand = getRandomValues ( cryp );

      for ( var i = 0; i < length; i++ )  {
          text += possible.charAt ( rand[i] % possible.length );
      return text;
    getRandomValues = function  ( buf ) {
      if (!(buf instanceof Uint8Array)) {
        throw new Error('Invalid type: buf not an Uint8Array');
      if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
      } else if (typeof window !== 'undefined' && typeof window.msCrypto === 'object' && typeof window.msCrypto.getRandomValues === 'function') {
      } else if (nodeCrypto) {
        var bytes = nodeCrypto.randomBytes(buf.length);
      } else if (this.randomBuffer.buffer) {
      } else {
        throw new Error('No secure random number generator available.');
      return buf;
    div.innerHTML = randomString ( 20 );

Santas trip

Want to move Santa across your web page to celebrate the season or have a selfie take a trip over your web page …
Here is a fun snippet to achieve that.

<!DOCTYPE html>
<body bgcolor="black">
  <img src="flying.gif" id="sleigh" style="position: absolute" />
  <audio src="bells.mp3" autoplay loop></audio>
  var img = document.getElementById ( "sleigh" );
  var left = 0;
  setInterval ( function ( ) {
    left += 3;
    img.style.left = left+"px";
    if ( left > window.innerWidth )
         left = -400;
  }, 100 );


Still don’t know what to do ?
Have you ever wondered what your monthly mortgage rate would be like if you change the interest rate or the length of the loan ?
Or are you looking for a simple tip calculator. This snippet is using evil-eval to accomplish this task.

<!DOCTYPE html>
      function calc ( )  {
        var out = document.getElementById ( "OUT" );
        var txt = document.getElementById ( "eq" );
        var res = eval ( txt.value );
        out.innerHTML = res+"";
    <input type="text" id="eq" value='var r=5.00, N=12*15, P=100000; var R=r/12/100; var M=R/(1-(1+R) ** -N ) * P; P+"@"+r+"% = "+M' size="70" \>
    <input type="button" value="Calculate" onclick="calc();" \>
<p><b><div id="OUT"> -- VALUE --</div></b>

Polar Clock

Want to know what tmie it is but you wnat to realy try to use as much brain power as possible
Try the Polar Clock. A fun way to display time.

    <script src="clock.js"> </script>
<body bgcolor="white">
<div id="logo" >
  <canvas id="canvas" width="120" height="120" ></canvas>

Random Image

Do you want to use a random background image from Flicker.
This code snippet does just that using the flickr API.

body {
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script type="text/javascript">
    var keyword = "mountains";
    function getImg ( )  {
      keyword = document.getElementById ( "key" ).value;
      var req = { tags: keyword, tagmode: "any", format: "json" };
      var url = "https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?"; 
      $.getJSON ( url, req, function ( data )  {
        var rnd = Math.floor(Math.random() * data.items.length);
        var image_src = data.items[rnd]['media']['m'].replace("_m", "_b");
        $('body').css('background-image', "url('" + image_src + "')");
      } );
      getImg ( );
  <button onclick="getImg ( );">New Image</button>
  <input type="text" value="mountain" id="key"/>

Chess Game

Granted this one is a bit too much to invent yourself. But fear not, you can get the tiny 1k code for a chess game from Here …

<!doctype html>
  (c) &copy; js1k.com, 2010 - 2017
  Note: submissions belong to their respectful owner, do not copy without their consent
    <title>JS1k 2010 - Demo 750 </title>
    <meta charset="utf-8">
    <meta name="author" content="Óscar Toledo G.">
    <meta name="description" content="JS1k 2010 demo: Tiny Chess.">
    <meta name="pubdate" content="20100909">
    <link rel="icon" type="image/png" href="http://js1k.com/favicon.png">
    <link rel="canonical" href="http://js1k.com/2010-first/demo/750">
    <link rel="alternate" type="application/rss+xml" title="New demo feed" href="http://js1k.com/feed.xml">
    <link rel="shortlink" href="http://js1k.com/750">
        var ga = document.createElement('script');
        ga.async = true;
        ga.defer = true;
        ga.src = 'http://www.google-analytics.com/ga.js';
        ga.onload = function(){try{_gat._getTracker('UA-19882353-1')._trackPageview();}catch(e){window.console&&console.log("ga fail :'( ");};};
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ga, s);
      }, 10);
      /* http://qfox.nl/notes/333 */
      header{position:relative;z-index:1;height:47px;padding-top:2px;border-bottom:1px solid #000;box-shadow:0 -10px 25px #ccc inset;background-color:#eee}
      a{color:#000;text-decoration:none;border-bottom:1px dashed #000}
      a:hover{border-bottom:1px solid red}
      .button{float:left;width:40px;height:40px;line-height:40px;text-align:center;padding:0;margin:2px 0 0 10px;border:1px solid #888;border-color:#ddd #888 #888 #ddd;font-family:sans-serif;font-size:30px;font-weight:700;cursor:pointer}

          <a href="http://js1k.com">JS1k</a>
          <a href="http://js1k.com/2010-first">2010</a>
          <strong></strong> demo
           by Óscar Toledo G.
            Tiny Chess.
          1023 bytes
          <!-- -->
          <a href="http://js1k.com/2010-first/details/750">demo details</a>
          <a href="http://js1k.com/2010-first/demos">list of demos</a>
          <b><a href="http://js1k.com/2010-first/demo/">
            Update available here!
          <!-- -->
          <a href="http://js1k.com/750" title="short link for your mobile devices" rel="nofollow">js1k.com/750</a>

      <a href="749" class="button p">&Larr;</a>
      <a href="751" class="button n">&Rarr;</a>

    <script type="demo">
for(B=i=y=u=b=i=5-5,x=10,I=[],l=[];B++<304;I[B-1]=B%x?B/x%x<2|B%x<2?7:B/x&4?0:l[i++]="ECDFBDCEAAAAAAAAIIIIIIIIMKLNJLKM@G@TSb~?A6J57IKJT576,+-48HLSUmgukgg OJNMLK  IDHGFE".charCodeAt(y++)-64:7);function X(c,h,e,s){c^=8;for(var o,S,C,A,R,T,G,d=e&&X(c,0)>1e4,n,N=-1e8,O=20,K=78-h<<9;++O<99;)if((o=I[T=O])&&(G=o^c)<7){A=G--&2?8:4;C=o-9?l[61+G]:49;do if(!(R=I[T+=l[C]])&&!!G|A<3||(R+1^c)>9&&G|A>2){if(!(R-2&7))return K;n=G|(c?T>29:T<91)?o:6^c;S=(R&&l[R&7|32]*2-h-G)+(n-o?110:!G&&(A<2)+1);if(e>h||1<e&e==h&&S>2|d){I[T]=n;I[O]=0;S-=X(c,h+1,e,S-N);if(!(h||e-1|B-O|T-b|S<-1e4))return W(),c&&setTimeout("X(8,0,2),X(8,0,1)",75);I[O]=o;I[T]=R}if(S>N||!h&S==N&&Math.random()<.5)if(N=S,e>1)if(h?s-S<0:(B=O,b=T,0))break}while(!R&G>2||(T=O,(G||A>2|(c?O>78:O<41)&!R)&&++C*--A))}return-K+768<N|d&&N}function W(){i="<table>";for(u=18;u<99;document.body.innerHTML=i+=++u%x-9?"<th width=60 height=60 onclick='I[b="+u+"]>8?W():X(0,0,1)'style='font-size:50px'bgcolor=#"+(u-B?u*.9&1||9:"d")+"0f0e0>&#"+(I[u]?9808+l[67+I[u]]:160):u++&&"<tr>")B=b}W()
      (function(){var doc=document;var header=doc.getElementsByTagName("header")[0];var firstChild=header.firstChild;var p=doc.getElementsByClassName("p")[0];var n=doc.getElementsByClassName("n")[0];header.insertBefore(p,firstChild);header.insertBefore(n,firstChild);header.appendChild(doc.getElementsByTagName("p")[0])})();
      (function reload(){var doc=document;var header=doc.getElementsByTagName("header")[0];var iframe=doc.createElement("iframe");doc.body.appendChild(iframe);var iwin=iframe.contentWindow;var idoc=iframe.contentDocument;idoc.open();idoc.close();idoc.write('<!doctype html><head><meta charset="utf-8"><body>');idoc.head.innerHTML="<style>\n"+"html, body { margin: 0; padding: 0; border: 0; width: 100%; height: 100%; }\n"+"</style>\n";idoc.body.innerHTML="\n\t\t"+"<canvas"+' id="c"'+''+"></canvas>\n"+
      (false?"<script>\x3c/script>\n":"")+"";var Audio=iwin.Audio;iwin.Audio=function(x){return new Audio(x)};if(false){var canvas=idoc.getElementsByTagName("canvas")[0];iwin.a=canvas.getContext("2d");iwin.b=idoc.body;iwin.c=canvas;var p2d=iwin.Path2D;function wrap(ctx){var fill=ctx.fill,clip=ctx.clip,stroke=ctx.stroke;ctx.scale=ctx.scale;ctx.drawFocusIfNeeded=ctx.drawFocusIfNeeded;ctx.ellipse=ctx.ellipse;ctx.fill=function(r){fill.call(ctx,r==="evenodd"?"evenodd":"nonzero")};ctx.stroke=
        function(p){if(p&&p2d&&p instanceof p2d)stroke.call(ctx,p);else stroke.call(ctx)};ctx.clip=function(p){if(p&&p2d&&p instanceof p2d)clip.call(ctx,p);else clip.call(ctx)};return ctx}if(false){var cvs=iwin.c;var cNode=cvs.cloneNode;cvs.cloneNode=function(){var clone=cNode.apply(cvs,arguments);var cloneGet=clone.getContext;clone.getContext=function(){return wrap(cloneGet.call(clone,"2d"))};return clone};var get=cvs.getContext;cvs.getContext=function(){return wrap(get.call(cvs,"2d"))}}if(false)wrap(iwin.a)}idoc.body.clientWidth;
        var demo=idoc.createElement("script");var scrpt=doc.querySelector('script[type="demo"]').textContent.replace(/m.location=m.location;/,"top.reload();");if(false)scrpt="A=0,B=0;"+scrpt;demo.textContent=scrpt;idoc.body.appendChild(demo);idoc.close();iframe.contentWindow.focus();var r=doc.createElement("div");r.innerHTML="&#8635;";r.className="button r";r.title="restart just the demo (local, without remote fetch)";window.reload=r.onclick=function(){doc.body.removeChild(iframe);r.parentElement.removeChild(r);
          iframe=null;r=null;idoc=null;header=null;reload()};var firstLine=doc.getElementsByTagName("div")[0];header.insertBefore(r,firstLine)})();

3D Globe

And finally here is a quick way to add a 3D globe to your web page using OpenGlobus.

<head><title>OpenGlobus - Earth planet</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.rawgit.com/OpenGlobus/OpenGlobus/eb8392c2/build/og.css" type="text/css" />
    <script src="https://cdn.rawgit.com/OpenGlobus/OpenGlobus/eb8392c2/build/og.js"></script>
    <div id="globus" style="width:100%;height:80%"></div>
    <button id="btnOSM">OSM</button>
    <button id="btnMQS">MapQuest Sat.</button>

        document.getElementById("btnOSM").onclick = function () {

        document.getElementById("btnMQS").onclick = function () {

        var osm = new og.layer.XYZ("OpenStreetMap", {
            specular: [0.0003, 0.00012, 0.00001],
            shininess: 20,
            diffuse: [0.89, 0.9, 0.83],
            isBaseLayer: true,
            url: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
            visibility: true,
            attribution: 'Data @ OpenStreetMap contributors, ODbL'

        var sat = new og.layer.XYZ("MapQuest Satellite", {
            shininess: 20,
            specular: og.math.vector3(0.00048, 0.00037, 0.00035),
            diffuse: og.math.vector3(0.88, 0.85, 0.8),
            ambient: og.math.vector3(0.15, 0.1, 0.23),
            isBaseLayer: true,
            url: "https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoibWdldmxpY2giLCJhIjoiY2o0ZmVudncwMGZvbjJ3bGE0OGpsejBlZyJ9.RSRJLS0J_U9_lw1Ti1CmsQ",
            visibility: false,
            attribution: '@2014 MapQuest - Portions @2014 "Map data @ <a target="_blank" href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, <a target="_blank" href="http://opendatacommons.org/licenses/odbl/"> CC-BY-SA</a>"'

        var controls = [

        globus = new og.Globus({
            "target": "globus",
            "name": "Earth",
            "controls": controls,
            "terrain": new og.terrainProvider.TerrainProvider("OpenGlobus"),
            "layers": [osm, sat]


So now that you know what to do it should be easy to take some of these suggestions and run with it. If nothing else you can find plenty more on gihub.com to help you kill off some of your time.

You can directly follow the link to the Battlefield to play with the code.

I also created a video for these drive-by-coding samples, which you can find here …

Login Form with php and QooxDoo

Sooner or later all web developer will have to create a Login form for one of their projects. You can find a lot of sample code and frameworks out there. In fact this is one of those times where you end up in information overload. There are millions of options but which one to pick ?

QooxDoo is a great framework to write complex web applications which acts and feels like a standard windows client. These so called SPAs ( Single Page Applications ) are a great way to create a valuable presence online and to offer your customers a very special treat.

But is QooxDoo flexible enough to build a stunning login form with it ?

I found that QooxDoo and php go together like peanut butter and jelly. And yes you can also use a RESTFul API to achieve the same however using PHP will offer you one advantage over E.g. a NodeJS based back-end. With most shared hosting services you will not be able to use NodeJS.

Form and Function:

So what are we looking for in a login form ? We want a nice and clean looking interface and we want to have the ability to

  1. Login
  2. Sign Up
  3. Recover password

So we are actually looking at three different forms and not just a single one.

three Login forms
three Login forms

That serves me well

For the PHP back-end I chose a single user class which I found on Github because it keeps things simple enough while at the same time offering some basic type of security. There are plenty of other options for the back-end available and you can shoe-horn any back-end into this solution really.

The PHP code was adjusted a bit and I added the code in here for your convenience. You can also download the PHP code from the following link Secure-User-Login-And-Authentication-Class

Here is the code which I added. Obviously in a real environment you want to separate this code out into its own file ( or files ).

// Simple input handling.
// Please note that no user input validation is done
$auth = new userAuth;
$type = $_GET['type'];
$name = $_GET['username'];
$pass = $_GET['password'];
$email= $_GET['email'];
$remember = $GET['remember'];
switch ( $type )  {
  case "login":  echo $auth->user_login  ( $name, $pass, $remember ) ? "true" : "false"; break;
  case "logout": echo $auth->user_logout ( ) ? "true" : "false"; break;
  case "forgot": echo $auth->forgot_password ( $email ) ? "true" : "false"; break;
  case "signup": echo $auth->create_user ( $name, $pass, $email, $remember ) ? "true" : "false"; break;
  case "check":  echo $auth->is_logged_in ( ) ? "true" : "false"; break;
  case "newDB":  echo $auth->create_user_table ( ) ? "true" : "false"; break;
  default: echo "Bad type"; break;

Now since we are going to use Ajax calls to the backend please keep in mind that any additional request to the backend ( E.g. like to display user data ) must be secured on the server side. You cannot rely on any security checks on the client side.

Login Form

In this post I want to mostly focus on the front-end part of the Login Form. Using QooxDoo to build the UI will require a few tweaks to the look and feel of the widgets in here.

I could have gone and created new styles following the qooxdoo way and I could have also created the LoginForm using the standard generate.py server side script. However I chose to use a functional complete, minified, qooxdoo library instead to bootstrap this application.

<!DOCTYPE html>
    <meta charset="utf-8">
    <meta name="Author" CONTENT="SoftwareSamurai">
    <meta name="Description" CONTENT="login" />
    <meta name="Keywords" CONTENT="some keywords" />
    <meta name="Classification" CONTENT="Klassifizierung">
    <script type="text/javascript" src="script/qx-6.0.0a.min.js"></script>
    <script type="text/javascript" src="LoginWindow.js"></script>
    <script type="text/javascript" src="SignupWindow.js"></script>
    <script type="text/javascript" src="ForgotWindow.js"></script>
    <script type="text/javascript" src="ApplicationWindow.js"></script>
  <script type="text/javascript">
    qx.Class.define ( "LoginWindow.Application", {
      extend : qx.application.Standalone,
      members: {
        main : function ( )  {
          this.base ( arguments );
          if ( qx.core.Environment.get ( "qx.debug" ) )  {
          var root = this.getRoot ( );
          var decorator = new qx.ui.decoration.Decorator ( );
          decorator.setBackgroundImage ( "background.jpg" );
          decorator.setBackgroundRepeat( "scale" );
          this.getRoot ( ).setDecorator( decorator );
          this.rpc ( "type=check", function ( success )  {
            var win = null;
            if ( success )
              win = new ao.apps.users.SoftwareSamurai.ApplicationWindow ( );
              win = new ao.apps.users.SoftwareSamurai.LoginWindow ( );
            win.show ( );
          },this );
        rpc : function ( str, cb, ctx )  {
          var rpc = new qx.io.request.Xhr ( "user.class.php?"+str );
          rpc.addListener ( "success", function ( e, x, y ) {
             var req = e.getTarget ( );
             var rsp = req.getResponse ( );
             cb.call ( ctx, ( rsp === "true" ) );
          }, this );
          rpc.send ( );
    } );
    qx.core.Environment.add ( "qx.application", "LoginWindow.Application" );

Using this approach has the advantage of being able to rapidly develop your application and then use the standard QooxDoo tools to compile and minify once you are sure you have all you want.
For a standard QooxDoo application you have the Application.js file, which I have folded into this html file.

So lets go though this code above:
Line 10 : Here we load the complete qooxdoo library in a minified version.
Linew 11 – 14 : These are the classes we are going to create today.
Line 18 : Every QooxDoo application has to be derived off of qx.application.Standalone.
Line 21 : This is the main entry function which is called once the application is completely loadedinto the browser and the widgets are initialized.
Line 28 – 31 : In order to set the background image for the complete window we have to set the applications decorator.
Line 32 – 39: These lines of code do call the backend and if the user is already logged in, then we display the main application window, otherwise we display the login form.
Line 41 – 49 : This function will call the backend form ( “user.class.php ), and call the supplied callback function back in the supplied context ( object ).
Line 52 : In order to create a qooxdoo application you have to set this variable to allow the framework to instantiate the appropriate class as the main application.

Log me in Scotty

If you are not currently already logged in you will create a new instance of LoginWindow. So lets take a look at this class.

One word upfront though first. All three classes are very similar in structure, so I will only talk about the LoginWindow here. You can find the code of the other classes following the link below this blog.

The first thing we have to take care of is to create the actual widgets.

qx.Class.define ( "ao.apps.users.SoftwareSamurai.LoginWindow",
  extend : qx.ui.container.Composite,
  construct : function ( ) {
    this.base ( arguments );
    this.setLayout ( new qx.ui.layout.Canvas ( ) );
    this.set ( { width: 300, height: 200 } );
    this._buildLogin ( );
    var top  = parseInt ( ( qx.bom.Document.getHeight ( ) - 200 ) / 2, 10 );
    var left = parseInt ( ( qx.bom.Document.getWidth  ( ) - 300 ) / 2, 10 );
    var app  = qx.core.Init.getApplication ( );
    var root = app.getRoot( );
    root.add ( this, { top: top, left: left } );
  destruct : function ( )  {
  members  :  {
    _buildLogin : function ( )  {
      var semi = new qx.ui.core.Widget ( );
      semi.set ( { opacity: 0.6, backgroundColor: "#404040" } );
      this.add ( semi, { top: 1, left: 1, right: 1, bottom: 1 } );

      var font = new qx.bom.Font ( 24, [ "Arial" ] );
      font.setBold ( true );
      //font.setColor ( "#FFFFFF" );
      var lbl = new qx.ui.basic.Label ( "<center><b style='color: #FFFFFF'>" + this.tr ( "Log in" ) + "</b></center>" );
      lbl.setFont ( font );
      lbl.setRich ( true );
      lbl.setWidth( this.getWidth ( ) - 20  );
      this.add ( lbl, { top: 10, left: 10 } );

      var line = new qx.ui.core.Widget ( );
      line.set ( { height: 2, backgroundColor: '#FFFFFF' } );
      this.add ( line, { top: 50, left: 10, right: 10 } );

      var name = new qx.ui.form.TextField ( );
      name.setPlaceholder ( this.tr ( "Username" ) );
      this.add ( name, { top: 65, left: 10, right: 10 } );
      this._name = name;

      var pass = new qx.ui.form.PasswordField ( );
      pass.setPlaceholder ( this.tr ( "Password" ) );
      this.add ( pass, { top: 100, left: 10, right: 10 } );
      this._pass = pass;

      var chk = new qx.ui.form.CheckBox ( "<b style='color: #FFFFFF'>" + this.tr ( "Remember me ?" ) + "</b>" );
      lbl = chk.getChildControl ( "label" );
      lbl.setRich ( true );
      this.add ( chk, { top: 130, left: 10 } );
      this._remember = chk;

      var strForgot = "<center><i style='color: white'>" + this.tr ( "Forgot Password ?" ) + "</i></center>";
      var btnForgot = new qx.ui.basic.Atom ( strForgot );
      btnForgot.set ( { cursor: 'pointer' } );
      lbl = btnForgot.getChildControl ( "label" );
      lbl.setRich ( true );
      lbl.setAllowGrowY ( true );
      btnForgot.addListener( "mouseover", function ( )  {
        btnForgot.setLabel ( "<u style='color: white'>" + strForgot + "</u>" );
      },this );
      btnForgot.addListener( "mouseout", function ( )  {
        btnForgot.setLabel ( strForgot );
      },this );
      btnForgot.addListener( "click", function ( )  {
        this.forgot ( );
      },this );
      this.add ( btnForgot, { top: 130, right: 10 } );

      var width = parseInt ( ( this.getWidth ( ) - 30 ) / 2, 10 );
      var btnLogin = this._createBtn ( this.tr ( "Log in" ), "#AAAAFF70", width, function ( ) {
         this.login ( );
      }, this );
      this.add ( btnLogin, { bottom: 10, left: 10 } );
      var btnSignup = this._createBtn ( this.tr ( "Sign Up" ), "#AAFFAA70", width, function ( ) {
         this.signup ( );
      }, this );
      this.add ( btnSignup, { bottom: 10, right: 10 } );
} );

Line 1: We define the class name following the qooxdoo naming convention.
Line 3: Instead of creating a Window with a title etc we simply create a Container
Line 4 – 14: The constructor takes care of the initialization. I want to point out qx.bom.Document.getHeight ( ) which will give you the size of the browser window.
Line 11 – 13: Getting the applications root window and adding the LoginWindow to the root.
Line 19 – 21: The semi transparent background. If we would set the opacity of the actual LoginWindow we would also create the buttons and the text with the set opacity which is not wanted.
Line 46 – 50: For the checkbox we want to change the text color to white. In order to achieve this we have to get the actual Label object, and then use plain HTML to change the color.
Line 52 – 67: Here we build a widget which behaves like a HTML link. There are many ways this could be done, so consider this one of the solutions. Also please note that I am using a Atom instead of a Button object here.
Line 69 – 77: Finally we create the Log In and Sign up buttons. This is done using a function which takes in the required attributes and a callback function.

Lets inspect how we create the buttons and their custom looks.

    _createBtn : function ( txt, clr, width, cb, ctx )  {
      var btn = new qx.ui.form.Button ( "<b style='color: white'>" + txt + "</b>" );
      btn.set ( { width: width, cursor: 'pointer' } );
      lbl = btn.getChildControl ( "label" );
      lbl.setRich ( true );
      btn.addListenerOnce ( "appear", function ( )  {
        this.setBGColor ( btn, "#AAAAAA00", "#AAAAAA00" );
      },this );
      btn.addListener ( "mouseover", function ( )  {
        this.setBGColor ( btn, clr, clr );
      },this );
      btn.addListener ( "mouseout", function ( )  {
        this.setBGColor ( btn, "#AAAAAA00", "#AAAAAA00" );
      },this );
      btn.addListener ( "execute", function ( e )  {
         cb.call ( this );
      }, ctx );
      return btn;
    setBGColor : function ( btn, clr1, clr2 ) {
       var elem = btn.getContentElement ( );
       var dom  = elem.getDomElement ( );
       var img  = "linear-gradient(" + clr1 + " 35%, " + clr2 + " 100%)";
       if ( dom.style.setProperty )
            dom.style.setProperty ( "background-image", img, null );
            dom.style.setAttribute ( "backgroundImage", img );

Line 2 – 5 : The first few lines of the _createBtn function we create a button and then change the text color to white.
Line 6 – 8 : The first time the button is rendered we will go ahead and change the background color. If we call the function before the button is visible, then the DOM element would be null.
Line 9 – 14 : These two callback function handle the case when the mouse gets into and out of the widget. In this case we are going to change the background color of the button.
Line 15 – 17 : We handle the execute event and are going to call the registered function back. Please take note of the way I implemented the callback, which will retain the context ( object ) inside the function. In object oriented programming you want to always use and handle the data inside an object so it is important to remain in the appropriate context.
Line 20 – 28 : In order to set the buttons background color we have to modify the DOM element directly. Please remember that this is not the way I would change the style of a button if I wanted to use it throughout the rest of the application. This is a quick way to get to change the gradient background color.

… and action.

The following functions handle the button action.

    forgot : function ( )  {
      var forgot = new ao.apps.users.SoftwareSamurai.ForgotWindow ( );
      forgot.show  ( );
      this.destroy ( );
    login : function ( )  {
      var name = this._name.getValue ( );
      var pass = this._pass.getValue ( );
      var remember = this._remember.getValue ( );
      if ( name.length < 1 || pass.length < 1 )  {
        alert ( this.tr ( "Please provie username and password" ) );

      var str = "type=login";
      str += "&username="+name;
      str += "&password="+pass;
      str += "&remember="+remember;
      var app = qx.core.Init.getApplication ( );
      app.rpc ( str, function ( success ) {
        var win = null;
        if ( success )  {
          win = new ao.apps.users.SoftwareSamurai.ApplicationWindow ( );
          win.show ( );
          this.destroy ( );
        else  {
          alert ( this.tr ( "Could not log in." ) );
      }, this );
    signup : function ( )  {
      var signup = new ao.apps.users.SoftwareSamurai.SignupWindow ( );
      signup.show  ( );
      this.destroy ( );

Line 1 – 5 : The forgot function will create and display the ForgotWindow form and destroy the Login form.
Line 6 – 13 : we retrieve the values the user entered and do some basic sanity checking.
Line 15 – 18 : Here we build the query string to send to the back-end.
Line 19 – 20 : We get the application object to invoke the rpc function.
Line 21 – 29 : Once the server returns we check if we were successful in logging in. If yes, then we hide the Login form and start the main ApplicationWindow.
Line 32 – 36 : The signup function will create and display the SignupWindow form and destroy the Login form.

Let’s wrap it up

Congratulations, you have done it. You are now able to build your own complex web applications on a shared hosting service, such as Dreamhost with as little as $8,- per month.



Live sample

Create a stunning login form using QooxDoo and PHP

Note: try user “test” and password “abc123” or create a new user.

I also created a video on creating a stunning login form using QooxDoo, which you can find here …

Please follow this link here … to play with the code.

Create a 3D game in 5 minutes using AFrame

One day I asked myself the question : “Is it is possible to make a game in WebGL using the A-Frame framework in 5 minutes or less ?”.

Thinking through this a bit I concluded, that one of the things which take up the most time when you make a game is to create the models, textures, graphics, sound, and of course the game play.

If you take this out of the equation, and you have the data and game play already set, then it should be possible to create a game in a very short time period indeed. However would it be possible to do it in 5 minutes or less ?

Let’s find out.


The first thing to decide when you make a game is the actual game play. I like arcade games which do not require an extensive AI to handle the enemy’s movement. A simple random number generator, in general, and some physics is sufficient for those types of games.

So this time I want to create a game which is a basic shooter however wrapped around a globe. I do not intend in using a real physics engine, such as CANNON.js, as that would be overkill.

The user can change the rotation of his vessel around the globe and he can also adjust the tilt to hit ( or miss ) his target.

This is a simple enough game and can be extended ad infinity with better aliens, moving targets, better sound, graphics, etc however the 5 minute deadline is keeping us within a reasonable limit.

Model, Graphics and audio:

Since these things are unrelated to the actual coding I decided to take these out of the equation and instead use models, graphics, and audio I collected online.

Thankfully there are plenty of online stores for any type of 3D model available. I found this spaceship from Liz Reddington and the UFO by Eric Smith

Spaceship from http://poly.google.com

UFO model

The same is true for the texture I use for earth. you can find these easily online searching for texture earth. I picked one without clouds because I wanted to have a separate layer for clouds.

Earth textureCloud texture


I found some ambient background music through FreeMusicArchive from Lobo Loco  Background Audio

And then finally I found two more audio samples for shooting and for exploding through https://www.freesoundeffects.com.

Welcome scene

I chose to create a welcome scene because I had the template for it around from my previous video on 360° image web app and it also allowed me to place a Object inside the AFrame scene. It is always a nice touch when you make a game.

Using “ctrl” + “Alt” + “i”, I had to tweak the position and rotation of the camera, and the model to make this scene picture perfect. Although it took some time, it gave me the opportunity to use the graphical inspector and to figure out how to debug a 3D scene.

<!DOCTYPE html>
    <meta charset="utf-8">
    <title>360&deg; Game</title>
    <meta name="description" content="360&deg; Slideshow">
    <!-- https://github.com/aframevr/aframe/raw/master/dist/aframe-v0.7.1.min.js -->
    <script src="aframe-v0.7.1.min.js"></script>
    <!-- http://cdn.rawgit.com/donmccurdy/aframe-extras/v3.13.1/dist/aframe-extras.min.js -->
    <script src="drag-look-controls.min.js"></script>
        init : function ( ) {
          this.el.addEventListener ( 'click', function (evt) {
          } );
      } );
  <a-scene cursor="rayOrigin:mouse">
      <img id="pano7" src="pano7.jpg">
      <a-asset-item id="ship-obj" src="spaceship/Lo_poly_Spaceship_01_by_Liz_Reddington.obj"></a-asset-item>
      <a-asset-item id="ship-mtl" src="spaceship/Lo_poly_Spaceship_01_by_Liz_Reddington.mtl"></a-asset-item>
    <a-sky id="pano" src="pano7.jpg" rotation="0 0 0"></a-sky>
    <a-entity position="-370.687 222.94 -529.306" rotation="-10 220 0" >
      <a-camera drag-look-controls>
        <a-cursor id="cursor"></a-cursor>
    <a-box start_game="" visible="false" width="200" height="100" depth="300" position="-0.35 1.5 -1.0"></a-box>
    <a-entity obj-model="obj: #ship-obj; mtl: #ship-mtl" position="-0.35 1.5 -1.0" scale="0.5 0.5 0.5" ></a-entity>

Which will result in
Welcome page

Make a Game

Now with the above I have completed the pre-requisite and am now starting the clock. The main scene is a spinning globe with a starry background. I had a lot of the scaffolding from the initial sample available and all I had to do is to create an additional sphere which I gave the cloud layer which had a slightly increased size as compared to earths radius.
The Animation – tags will take care of the rotation for the earth-texture and cloud-texture so I don’t have to worry about that part anymore.

<!DOCTYPE html>
    <meta charset="utf-8">
    <title>360&deg; Game</title>
    <meta name="description" content="360&deg; Slideshow">
    <!-- https://aframe.io/releases/0.7.1/aframe.min.js -->
    <script src="aframe-v0.7.1.min.js"></script>
    <!-- http://cdn.rawgit.com/donmccurdy/aframe-extras/v3.13.1/dist/aframe-extras.min.js -->
    <script src="aframe-extras.js"></script>
  <a-scene cursor="rayOrigin:mouse" physics="debug: true">
      <img id="earth" src="earth-diffuse.jpg">
      <img id="clouds" src="clouds_8k.png" >

    <a-sky id="pano" src="background.png" rotation="0 0 0" radius="10000"></a-sky>
    <a-entity position="0 0 20" rotation="0 0 0" >
      <a-camera look-controls="enabled:false"></a-camera>

    <!-- EARTH and CLOUDS -->
    <a-sphere wireframe="false" src="#earth" position="0 0 0" radius="8">
      <a-animation attribute="rotation" dur="300000" fill="forwards" to="0 360 0" easing="linear" repeat="indefinite"></a-animation>
    <a-sphere src="#clouds" position="0 0 0" radius="8.2" material="opacity: 0.8; transparent: true">
      <a-animation attribute="rotation" dur="200000" fill="forwards" to="0 360 0" easing="linear" repeat="indefinite"></a-animation>

Main Scene

Asset collection

You cannot make a game without a few assets in place. I needed the spaceship, the ufo, the audio, as well as the bullet in the scene. So I added those in to the assets and the scene.

      <audio id="background-audio" src="Lobo_Loco_-_07_-_Funnny_Socks_ID_698.mp3"></audio>
      <audio id="shoot-audio"      src="shoot.mp3"></audio>
      <audio id="hit-audio"        src="hit.mp3"></audio>
      <img id="pano7"  src="pano7.jpg">
      <a-asset-item id="ship-obj"  src="spaceship/Lo_poly_Spaceship_01_by_Liz_Reddington.obj"></a-asset-item>
      <a-asset-item id="ship-mtl"  src="spaceship/Lo_poly_Spaceship_01_by_Liz_Reddington.mtl"></a-asset-item>
      <a-asset-item id="ufo-obj"   src="ufo/model.obj"></a-asset-item>
      <a-asset-item id="ufo-mtl"   src="ufo/materials.mtl"></a-asset-item>

      <img id="earth" src="earth-diffuse.jpg">
      <img id="clouds" src="clouds_8k.png" >
   <!-- SPACESHIP -->
    <a-entity drag-rotate-component rotation="0 45 0" >
      <a-entity id="ship" obj-model="obj: #ship-obj; mtl: #ship-mtl" position="-11 0 0" scale="0.005 0.005 0.005" rotation="300 90 0" ></a-entity>
    <!-- UFO -->
    <a-entity id="ufo-pos" rotation="0 130 0" >
      <a-entity class="ufo" id="ufo" obj-model="obj: #ufo-obj; mtl: #ufo-mtl" position="-9 0 0" scale="1.0 1.0 1.0" rotation="0 0 0" ></a-entity>

    <!-- BULLET -->
    <a-entity id="bullet" rotation="0 45 0" >
      <a-sphere id="beam" position="-11 0 0" radius="0.2" material="color: #F84" sphere-collider="objects: .ufo; radius: 0.5"></a-sphere>
      <a-animation id="anim" begin="startAnimation" end="endAnimation" attribute="rotation" dur="3000" fill="forwards" from="0 0 0" to="0 0 -360" easing="linear" repeat="indefinite"></a-animation>
Main scene including objects

This seems like a lot of code but it is mostly cut-and-paste and some size adjustments. This turned out to be straight forward as I have learned how to use the visual inspector previously.

All actual objects are encapsulated in another, outer a-entity. The reason for this is that we want to place the objects outside of earths radius by adjusting the position and then use the rotation on the outer entity for the game play.

The drag-rotate-component part on the Spaceship allows us to use the mouse to move the spaceship rotation around the globe as well as the tilt of the spaceship.

When you make a game you have to be able to detect when things collide. In order to have A-Frame detect the collision of two objects, I utilized the Sphere collider which was as simple as adding sphere-collider=”objects: .ufo; radius: 0.5″ to the bullet – object.

The coding part

With the scene complete and all objects in place I could go ahead and customize the ‘drag-rotate-component’ component.
Here again we have a lot of cut-and-paste code which takes care of binding the mouse events to their callback functions and initializing the objects as well as starting the background audio.

Please note the way the collision is detected for the sphere collider this.ufo.addEventListener ( ‘hit’, this.OnCollission.bind ( this ) );.

Finally, We are randomly placing the UFO into the scene in the last line of the initialization function. However we have to make sure the user can see it ( so it can’t be placed behind earth ).

<script type="text/javascript">
      schema : { speed : {default:1}},
      init : function(){
        this.ifMouseDown = false;
        this.x_cord = 0;
        this.y_cord = 0;
        this.distance= 11.0;
        this.rotX = -1.49;
        this.rotY =  0.0;
        document.addEventListener('mousedown',this.OnDocumentMouseDown.bind( this ) );
        document.addEventListener('mouseup',  this.OnDocumentMouseUp.bind  ( this ) );
        document.addEventListener('mousemove',this.OnDocumentMouseMove.bind( this ) );
        document.addEventListener('keyup',    this.OnDocumentKeyDown.bind  ( this ) );
        this.beam   = document.getElementById ( "beam" );
        this.bullet = document.getElementById ( "bullet" );
        this.anim   = document.getElementById ( "anim" );
        this.ship   = document.getElementById ( "ship" );
        this.ufo    = document.getElementById ( "ufo" );
        this.ufoPos = document.getElementById ( "ufo-pos" );
        this.ufo.addEventListener ( 'hit', this.OnCollission.bind ( this ) );
        var audio = document.getElementById ( "background-audio" );
        audio.play ( );
        audio.volume = 0.4;
        audio.loop   = true;
        this.setUfoPosition ( );
     setUfoPosition : function ( )  {
        var rotY = Math.random ( ) * 180;     // betweeen 0 and 180
        var dist = Math.random ( ) * 2 + 8.5; // between -8.5 and -10.5
        console.log ( "DIST: " + dist + " WHERE : " + rotY );
        this.ufoPos.setAttribute ( "rotation", "0 "+rotY+" 0" );
        this.ufo.setAttribute ( "position", -dist + " 0 0" );
    } );

Call me maybe

Lets take care of the callback functions we defined here first which build the core of the game.

      OnCollission  : function ( e )  {
        e.preventDefault ( );
        this.stopShot ( );
        this.ufoExplode ( );
      OnDocumentKeyDown : function ( e )  {
        if ( e.keyCode === 32 ) { // spacebar
          e.preventDefault ( );
          this.shoot ( );
      OnDocumentMouseDown : function ( event )  {
        this.ifMouseDown = true;
        this.x_cord = event.clientX;
        this.y_cord = event.clientY;
      OnDocumentMouseUp : function ( )  {
        this.ifMouseDown = false;
      OnDocumentMouseMove : function ( event )  {
        if ( this.ifMouseDown )  {
          var temp_x = event.clientX-this.x_cord;
          var temp_y = event.clientY-this.y_cord;
          if ( Math.abs ( temp_y ) < Math.abs ( temp_x ) )  {
            var  rotY = temp_x*this.data.speed/300;
            this.rotY = this.el.object3D.rotateY ( rotY ).rotation.y;
            this.bullet.object3D.rotateY( rotY );
          else {
            var  rotX = temp_y*this.data.speed/500;
            if ( rotX > 0 && this.rotX === -1.0 )
            if ( rotX < 0 && this.rotX === -1.5 )
            this.rotX = this.ship.object3D.rotateX ( rotX ).rotation.x;
            if ( this.rotX > -1.0 )
                 this.rotX = -1.0;
            if ( this.rotX < -1.5 )
                 this.rotX = -1.5;
          this.x_cord = event.clientX;
          this.y_cord = event.clientY;

The main function here if the OnDocumentMouseMove as expected since we have to calculate the new location and angle of the spaceship. It took some experimenting to get the values for the tilt right but overall this was not too painful.

just shoot me now

Okay so here is the interesting part. We want to make a game where we can shoot at something. For that we will have to implement a few functions now as well.
The lockAndLoad function will place the bullet back in the spaceships core.
animate will request the next animation frame until we have either detected a collision ( see OnCollission ) or until the bullet falls into earth.
shoot and stopShot should be self explanatory.

      lockAndLoad : function ( )  {
        var  rotY = this.rotY * 180.0/Math.PI;
        this.beam.object3D.position.x = -11.0;
        this.bullet.object3D.rotation.z = 0.0;
        this.bullet.object3D.rotation.order = "ZYX";
        this.anim.setAttribute ( "from", "0 "+rotY+" 0" ); 
        this.anim.setAttribute ( "to", "0 "+rotY+" -360" );
        this.anim.data.from="0 "+rotY+" 0";
        this.anim.data.to="0 "+rotY+" -360";
      shoot : function ( )  {
        document.getElementById ( "shoot-audio" ).play ( );
        this.lockAndLoad ( );
        this.beam.emit ( "startAnimation" );
        this.animate ( );
      stopShot : function ( )  {
        this.beam.emit ( "endAnimation" );
        this.lockAndLoad ( );
        // Stop animation
        this.beam.object3D.position.x = 0.0;
      animate : function ( )  {
        var self = this;
        if ( this.beam.object3D.position.x > -7.5 )  {
          this.stopShot ( );
        // this.rotX defines the speed of the descend
        // this.rotX in range of [-1.5 .. -1.0]
        var delta = (this.rotX+1.5) * 2; // Normalized value [0 .. 1]
        delta = 0.01 + delta* 0.1;

        this.beam.object3D.position.x += delta;
        function handle ( )  {
           self.animate ( );
        window.requestAnimationFrame( handle );

The only remaining function is to handle the case when the UFO is hit.

      ufoExplode : function ( )  {
        document.getElementById ( "hit-audio" ).play ( );
        this.setUfoPosition   ( );

and you are done

Cool so if you point your browser to your shiny new game you can actually spend some relaxing time with this.

You can also use this code and expand on it and make a game which has multi-player capabilities, using E.g. WebRTC for browser-to-browser communication. Then one player could navigate the UFO, the other could navigate the spaceship. Or you can add in an explosion or you can change the camera view or you can ….


Find the video for this tutorial below

3D Game in action

I also created a video building this 3D game, which you can find here …

Please follow this link here … to play with the code.

The possibilities are endless …

Since we have mastered 3D and the basics for gameplay it is now up to your imagination to get all out …

Possibilities are endless

360 degree image web app

In the last post we created a 360 degree image viewer in plain old javascript. we used the A-Frame framework and integrated the prev and next controls into the view itself.
This is a perfect solution if you want to display a series of 360 degree images ad you can hard-code the image locations in code. You can also modify the previous code and dynamically load images using PHP, NodeJS or any other server back-end.

In this, the second part, of episode 5 we are actually going ahead and replace the in-frame controls with some overlay controls using the QooxDoo javascript libraries. Below is a screenshot of the final app.

360 degree Viewer App
360 degree Viewer App

Where do we get started :

I want to take you through the process of creating a new application from scratch. This process is true for smaller projects as well as for larger ones. It is valid for JavaScript, C++, Python, go and about any language you are using.

This process also holds true if you are working on larger commercial applications except that instead of writing actual code, the steps described here are documented in a design document, requirements-document, or captured in a use-case document.

I firmly believe that anyone can program and anyone can learn how to do it by simply following this pattern. One point to remember is to keep it simple, clean and small. the number of lines of code in your program should be a reflection of the functionality.
A lot of the code you will see in this post is the result of “I want X to happen with control Y“. In fact the first 200 lines of code build and animate the UI.

Lets go then…

When I started I knew that I wanted to create an app which has

  • Forward and backward controls
  • Can display images on a 2D canvas as well as in 360 degree
  • I wanted to be able to view thumbnails of all ‘selected’ images

How do you design a UI with almost no existing requirements ?

First I looked at the navigation buttons. I simply looked for prev and next button images. I decided to go with arrows as I could not find anything else I liked and simplicity is king. However I wanted to have a nicer interface than the ImageViewer.

Image Viewer
Image Viewer

So instead of using a button object, I created a qx.ui.basic.Image object and added listener for the mouseenter, mouseout, and click events. Though now I needed a cold, and a hot image to indicate that there is something the user can interact with. Hmmm and come to think of it, the simple black and transparent arrow will not work on a black background. So back to the Gimp. Below is one of the eight buttons I created this way.

Button Design
Button Design

The javascript code to handle image buttons looks as follows:

     _createImgButton : function ( cold, hot, attr )  {
      var btn = new qx.ui.basic.Image ( this._path+cold+".png" );
      btn.set ( { scale: true, width: 40, height: 40 } );
      btn.hot = hot; btn.cold = cold;
      btn.addListener  ( "mouseover", function ( e )  {
         this._audio.play ( );
         btn.setSource ( this._path+btn.hot+".png" );
      }, this );
      btn.addListener  ( "mouseout", function ( e )  {
         btn.setSource ( this._path+btn.cold+".png" );
      }, this );
      this.add ( btn, attr );
      return btn;

With this fragment in place we can now go ahead and build the interface.
A few notes are in order here though.

  1. The 360 Viewer is handled in an iframe to avoid global namespace collusion in the main application.
  2. Switching between a 2D image display and the 360 Viewer is a simple game of hide and show
  3. We use the touchstart rather then the click event because the A-Frame library does not forward click events anymore.
  4. Take a closer look at back, scroll, and lyt. Those will handle the preview strip at the bottom
  5. Finally the this._info button will show / hide the topRightMenu.
    _build2DWindow : function ( )  {
      var scroll = new qx.ui.container.Scroll ( );
      scroll.setBackgroundColor ( "#ddf" );

      var inner = new qx.ui.container.Composite ( new qx.ui.layout.Canvas ( ) );
      scroll.add ( inner, { left: 0,  top: 0 } );
      this._scroll2D = scroll;

      this._preview2D = new qx.ui.basic.Image ( null );
      this._preview2D.setScale ( true );
      inner.add ( this._preview2D, { left: 5, right: 5, top: 5, bottom: 5 } );
      this.add  ( scroll, { left: 0, right: 0, top: 0, bottom: 0 } );
      scroll.hide ( );
    _buildWindow : function ( )  {
      this._build2DWindow ( );

      var iframe = new qx.ui.embed.Iframe ( this._path+"360IFrame.html" );
      this.add ( iframe, { top: 0, bottom: 0, left: 0, right: 0 } );
      iframe.addListenerOnce ( "appear", function ( ) {
         this._iframe = iframe; 
      }, this );
      var back = new qx.ui.core.Widget ( );
      back.set ( { height: 120, backgroundColor: "#888888", opacity: 0.4 } );
      this.add ( back, { bottom: -110, left: 20, right: 20 } );
      this._back = back;
      var scroll = new qx.ui.container.Scroll ( );
      scroll.set ( { height: 120, contentPaddingLeft: 5, scrollbarX: "on" } );
      this.add ( scroll, { bottom: -120, left: 20, right: 20 } );
      this._scroll = scroll;
      var lyt = new qx.ui.layout.HBox ( 5 );
      lyt.set ( { alignY: "middle" } );
      this._previewStrip = new qx.ui.container.Composite ( lyt );
      scroll.add ( this._previewStrip );
      this._prev = this._createImgButton ( "prev", "prev-1", { left:  5,  top: "48%" } );
      this._next = this._createImgButton ( "next", "next-1", { right: 10, top: "48%" } );
      this._info = this._createImgButton ( "info_down", "info_down-1", { right: 10, top: 5 } );
      this._info.state = "up";
      this._zoomIn  = this._createImgButton ( "plus", "plus-1",  { right: 10, top:  -50 } );
      this._zoomOut = this._createImgButton ( "minus","minus-1", { right: 10, top:  -90 } );
      this._2d      = this._createImgButton ( "2d", "2d-1",      { right: 10, top: -135 } );
      this._360     = this._createImgButton ( "360-1", "360",    { right: 10, top: -180 } );
      this._zoomIn.addListener  ( "touchstart", this.zoomIn,    this );
      this._zoomOut.addListener ( "touchstart", this.zoomOut,   this );
      this._2d.addListener      ( "touchstart", this.switch2D,  this );
      this._360.addListener     ( "touchstart", this.switch360, this );
      this._2d.state  = "up";
      this._360.state = "down";
      this._back.addListener    ( "mouseover", this.mouseOver, this );
      this._back.addListener    ( "mouseout",  this.mouseOut,  this );
      this._previewStrip.addListener ( "mouseover", this.mouseOver, this );
      this._previewStrip.addListener ( "mouseout",  this.mouseOut,  this );
      this._prev.addListener ( "touchstart", function ( e )  {
         this._audio.play ( );
         if ( this._curr < 0 )
           this._curr =  this._data.length-1; 
         this.displayImage ( );
      }, this );
      this._next.addListener ( "touchstart", function ( e )  {  
         this._audio.play ( );  
         if ( this._curr >= this._data.length )
           this._curr = 0;
         this.displayImage ( );
      }, this );
      this._info.addListener ( "touchstart", function ( e )  {
         var btn = this._info;
         if ( btn.state === "up" )  {
           btn.state= "down";
           btn.cold = "info_up";
           btn.hot  = "info_up-1";
           this._topRightMenu ( true );
         else  {
           btn.state= "up";
           btn.cold = "info_down";
           btn.hot  = "info_down-1";
           this._topRightMenu ( false );
      }, this );

Congratulations, at this point you have all the controls in place. However a modern interface requires some bling.
Thankfully QooxDoo makes this easy as well.

Animating to see

Burried deep within the API documentation is a little nugget which allows you to animate items around without a lot of code.

    animate : function ( dir, itm, start, end, dur )  {
      var kf, dom = itm.getContentElement ( ).getDomElement ( );
      if ( ! dom )
      if ( dir === "up" )  {
        kf = { 0: { top: start+"px" },
             100: { top:   end+"px" } };
        dom.style.top = kf[100].top;
        itm.setLayoutProperties ( { top: end } );
      if ( dir === "down" )  {
        var parentHeight = dom.parentElement.offsetHeight;
        var height = dom.offsetHeight;
        kf = { 0: { top: (parentHeight -start -height)+"px" },
             100: { top: (parentHeight -  end -height)+"px" } };
        dom.style.top = kf[100].top;
        itm.setLayoutProperties ( { bottom: end } );
      if ( dir === "left" )  {
        kf = { 0: { left: start+"px" },
             100: { left:   end+"px" } };
        dom.style.left = kf[100].left;
        itm.setLayoutProperties ( { left: end } );
      if ( dir === "right" )  {
        var parentWidth = dom.parentElement.offsetWidth;
        var width = dom.offsetWidth;
        kf = { 0: { left: (parentWidth -start -width)+"px" },
             100: { left: (parentWidth -  end -width)+"px" } };
        dom.style.left = kf[100].left;
        itm.setLayoutProperties ( { right: end } );

      var anim = { duration: 600, timing: "ease-out", keep: 100, keyFrames : kf };
      qx.bom.element.Animation.animate ( dom, anim );
    _showControls : function ( show )  {
      if ( show === true )  {
        this.animate ( "down", this._back,   -110, 5, 600 );
        this.animate ( "down", this._scroll, -120, 5, 600 );
        this.animate ( "left", this._prev,   5,  -45, 600 );
        this.animate ( "right",this._next,   5,  -45, 600 );
      else {
        this.animate ( "down", this._back,   5, -110, 600 );
        this.animate ( "down", this._scroll, 5, -120, 600 );
        this.animate ( "left", this._prev,    -45, 5, 600 );
        this.animate ( "right",this._next,    -45, 5, 600 );
      this._isOpen = show;
    _topRightMenu : function ( expand )  {
      if ( expand )   {
        this.animate ( "up", this._zoomIn,  -50, 50, 600 );
        this.animate ( "up", this._zoomOut, -90, 90, 600 );
        this.animate ( "up", this._2d,     -135,135, 600 );
        this.animate ( "up", this._360,    -180,180, 600 );
      else {
        this.animate ( "up", this._zoomIn,  50, -50, 600 );
        this.animate ( "up", this._zoomOut, 90, -90, 600 );
        this.animate ( "up", this._2d,     135,-135, 600 );
        this.animate ( "up", this._360,    180,-180, 600 );

Here is what the code does.
this._back and this._scroll are the controls for the image strip at the bottom. All other controls are image buttons. The core of the animate function is the qx.bom.element.Animation.animate ( dom, anim ); call, which takes the dom element and the animation attributes in form of the anim – object.

The most important information which we have to take care of are the key frames. those keyframes define the attribute values for the range of 0% ( beginning of the animation ) and 100% ( end of animation ). The system will take care of the intermediate positions.

Note: since we directly modify the dom element we will also have to tell the QooxDoo framework of the ‘layout change’ That is the reason we itm.setLayoutProperties ( { right: end } );. Without these calls QooxDoos’ layout engine would re-position the dom elements to its original position at the next refresh.

More JavaScript, I WANT MORE !!!!

  • mouseOver, and mouseOut is triggered by the preview strip at the bottom. It is like the taskbar which will auto-hide in windows.
  • switch2D, and switch360 trigger the hide and show spiel when we swich the view. Really nothing to it but powerful nevertheless.
  • _toggleBtn switches the hot, and cold state of a button. The icons have been named in a way where a “-1” appended to the name indicates an active position E.g. prev.png and prev-1.png
    _toggleBtn : function ( btn, state, icon )  {
      if ( state === true )  {
        btn.state = "down";
        btn.hot   = icon;
        btn.cold  = icon+"-1";
      else  {
        btn.state = "up";
        btn.hot   = icon+"-1";
        btn.cold  = icon;
      btn.setSource ( this._path+btn.cold+".png" );
    switch2D : function ( )  {
      this._toggleBtn ( this._2d,  true,  "2d" );
      this._toggleBtn ( this._360,false, "360" );
      this._iframe.hide   ( );
      this._scroll2D.show ( );
      this._mode2D = true;
      this.displayImage ( );
    switch360 : function ( )  {
      this._toggleBtn ( this._2d, false,  "2d" );
      this._toggleBtn ( this._360, true, "360" );
      this._iframe.show   ( );
      this._scroll2D.hide ( );
      this._mode2D = false;
      this.displayImage ( );
    mouseOver : function ( ) {
      if ( this._timer )  {
           this._timer.stop ( );
           this._timer = null;
      if ( this._isOpen === true )
       this._showControls ( true );
    mouseOut : function ( ) {
      if ( this._timer !== null )
      this._timer = qx.event.Timer.once ( function ( ) {
         // After 5 seconds ... hide
         this._isOpen = false;
         this._timer  = null;
         this._showControls ( false );
      }, this, 5000 );

200 Lines Of Code Great you are sticking with me here. I know it looks like a lot of code but see what we did thus far was simply to create a bunch of controls and took care of the way they are being displayed and animated.

At this point there are only two missing pieces to complete this application.

  1. Loading the image data and displaying it in the preview-strip.
  2. Displaying the active image in the main area

Faking it

In order to continue the client side code we will first use a fake response from a backend. Stricktly speaking we only need the image, and possibly a smaller version of it in form of a thumbnail. The code below will do just fine for now.

    loadImages : function ( )  {
      // Testing functionality
      var arr = [];
      for ( var t=0; t<200; t++ )  {
        var i = t*2;
        arr.push ( { name: "pano"+i,  src: "pano1.jpg", thumb: null, text: "Space Earth" } );
        arr.push ( { name: "pano"+i+1,src: "pano2.jpg", thumb: null, text: "Mountain Water" } );
      this._data = arr;
      var idx, cnt = 0;
      for ( idx in arr )  {
        this._addImage ( arr[idx], cnt++ );

Once we have the backend in place we will replace the above with :

    loadImages : function ( )  {
      // E.g. this._origSource = "AstraNOS:/Desktop/Courses/Episode 5/prev.png";
      var parms = [ this._origSource ];
      if ( ! this._rpc )
        this._rpc = new qx.io.remote.Rpc ( "services.php", "apps.system.360Viewer.360Viewer" );
      this._rpc.setTimeout ( 10000 ); // 10 seconds wait
      this._rpc.setCrossDomain ( false );
      var self = this;
      this._rpc.callAsync ( function ( arr, exception )  {
        if ( exception === null )  {
          self._data = arr;
          var idx, cnt = 0;
          for ( idx in arr )  { 
            self._addImage ( arr[idx], cnt++ );
          self._curr = 0;
          self.displayImage ( );
        else  {
          alert ( "Something went wrong : " + exception );
      }, "getImageList", parms );

So there are a few things in the above function we should talk about
Line 02 : this._origSource is the expected input for the backend call. AstraNOS indicates that it is ‘locally’ stored and not on E.g. DropBox. Then we want to point to a image from the system. It is set when the user opens the application.
Line 03 : parms contains an array of attributes which is passed into the function on the server side.
Lines 05 – 09 : Create the RPC object. I will write another blog post soon talking about the php backend. In short your root directory on the server contains services.php, which calls a php function in class “class_360Viewer” called “getImageList” with attributes “parms”.
Lines 10 – 21 : Here we handle the callback, receive the data from the server and add a single image per array entry to the preview-strip.

and here is the _addImage function.

    _addImage : function ( itm, idx )  {
      var thumb = itm.thumb ? itm.thumb : itm.src;
      var icon  = new qx.ui.basic.Image ( );
      icon._idx = idx;
      qx.io.ImageLoader.load ( thumb, function ( src )  {
        icon.setSource ( src );
        icon.set ( { padding: 0, scale: true, maxWidth: 200, maxHeight: 100, width: 200, height: 100, backgroundColor: "#FF3333" } );
        icon.addListener ( "mouseover", function ( e ) {
           this._audio.play ( );
           icon.set ( { padding: 4 } );
        }, this );
        icon.addListener ( "mouseout", function ( e ) {
           var pad = this._curr === icon._idx ? 2 : 0;
           icon.set ( { padding: pad } );
        }, this );
        icon.addListener ( "touchstart", function ( e ) {
           this._curr = icon._idx;
           this.displayImage ( );
        }, this );
        this._previewStrip.add ( icon );
      }, this );

Display the image already

We are almost complete. The following four functions handle the display of the image in both 2D, and 360 degree form.

  • getCurrentImage returns the current image either in thumbnail form or as teh full resolution image
  • displayImage displays the current image by first displaying the thumbnail, followed by the full sized image. This will improve usability as the thumbnail is already cached in the preview-strip.
  • _displayImage takes care of actually displaying the image in either 2D or 360 degree view based on the selected mode.
  • scaleImage this function is only required for the 2D view in order to properly fit it into the view-port of the window.
    getCurrentImage : function ( getThumb )  {
      if ( getThumb === true )
        return this._data[this._curr].thumb;
      return this._data[this._curr].src;
    displayImage : function ( )  {
      if ( ! this._iframe )
      var cnt = 0, kids = this._previewStrip.getChildren ( );
      for ( var idx in kids )  {
        kids[idx].set ( { padding: 0 } );
        if ( cnt++ === this._curr )
          kids[idx].set ( { padding: 2 } );
      var fname = this.getCurrentImage ( ).match(/[-_\w]+[.][\w]+$/i)[0];
      this.setCaption ( this._data[this._curr].name + " : " + fname );
      this._displayImage ( this.getCurrentImage ( true ) );
      this._displayImage ( this.getCurrentImage ( ) );
    _displayImage : function ( src )  {
      if ( this._mode2D === false )  {
        var  win = this._iframe.getWindow ( );
        if ( win )
          win.changeTo ( src );
      else  {
        qx.io.ImageLoader.load ( src, function ( src )  {
          var loaded= qx.io.ImageLoader.isLoaded ( src ); // Needed to make sure we have size info
          this._preview2D.setSource ( src );
          var s = qx.io.ImageLoader.getSize ( src );
          if ( typeof ( s.width ) !== "undefined" && typeof ( s.height ) !== "undefined" )
             this.scaleImage ( s.width, s.height );
        }, this );
    scaleImage : function ( iW, iH )  {
      var  x, y, w, h;
      if  ( this._scaleImage ) {
        var space = this._scroll2D.getInnerSize ( );
        w = space.width  - 10;
        h = space.height - 10;
        if ( iW/iH > w/h )  {
          var h1 = h;
          h = parseInt ( iH / iW * w, 10 );
          x = 5;
          y = parseInt ( ( h1 - h ) / 2, 10 ) + 0;
        else  {
          var w1 = w;
          w = parseInt ( iW / iH * h, 10 );
          y = 5;
          x = parseInt ( ( w1 - w ) / 2, 10 ) + 0;
      else  {
        w = iW; h = iH;
        x = 0;  y = 0;
      this._preview2D.setWidth  ( w );
      this._preview2D.setHeight ( h );
      this._preview2D.setLayoutProperties ( { top: y, left: x } );

construct “The end”

So here is the constructor then and the resize function

qx.Class.define ( "ao.apps.users.SoftwareSamurai.360Viewer",
  extend : qx.ui.window.Window,
  construct : function ( ) {
    this.base ( arguments );
    this.setLayout ( new qx.ui.layout.Canvas ( ) );
    this.setLayoutProperties ( { top: 10, left: 100 } );
    this.set  ( { contentPadding: 2, opacity: 1.0, width: 1400, height: 800 } );

    this._path   = ao.prefix+"apps/users/SoftwareSamurai/";
    this._data   = [];
    this._curr   = 0;
    this._timer  = null;
    this._mode2D = false;
    this._scaleImage = true; // 2D
    this._origSource = "";   // Handling autosatrt and loading of all images in this dir 

    // Buffering the putton images.
    this._imgBuff = [];
    this._audio  = new qx.bom.media.Audio ( this._path+"click.ogg" );
    this._iframe = null;
    this._isOpen = false;
    this.addListener  ( "resize", this._resize, this );
    this._buildWindow ( );
    if ( typeof ao !== "undefined" && typeof ao.desktop !== "undefined" )  {
      // Note ao.desktop is a class derived of qx.ui.desktop.Desktop ...
      ao.desktop.addWin ( this );
    else  {
      var app = qx.core.BaseInit.getApplication ( );
      var root = app.getRoot ( );
      root.add ( this );
      this.show ( );
    this.loadImages   ( );
  _resize : function ( )  {
    if ( this._mode2D === true )
      this.displayImage ( );

Phew, we went through 375 lines of code here but who is counting ?
At this point all that is left to do is to show the goods.

360° image Viewer:

I also created a video building this web app, which you can find here …

Please follow this link here … to play with the code.