html5 local storage [Tutorial]



The World's Most Misunderstood Programming Language
__darknite

PostMon May 07, 2012 3:09 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



HTML5 Offline and Online Web SQL tutorial.

One of the extremely cool feature of HTML5 is that of “local storage”. Local storage allows data to be saved on the clients computer. In HTML5 there are two options for local storage: “localStorage” and “WebSQL”.

In this tutorial we are going to focus on WebSQL. localStorage is only a simple Key,Value or KV store. This is perfect for some lightweight data needs, however for more complex data needs webSQL is the perfect fit. WebSQL is a full blown sqlite implementation that runs in the browser!

Web applications are excellent for many reasons, platform independence, centralised versioning, zero installation and deployment, etc. However they do suffer from one major problem: Connectivity.

How do you function when you have no internet connectivity? If a web application is designed to work “offline” then it is possible to resolve the issue of connectivity. Please note, when we say offline we are not talking about zero internet connectivity. Instead when we talk about offline we mean intermittent connection failure.

Tutorial Overview:

* Part 1: Offline and Online architecture explained
* Part 2: Front end UI and HTML
* Part 3: Web SQL and Ajax Functions
* Part 4: Local SQL functions
* Part 5: Buffer Logic and Buffer Manager
* Part 6: Network PHP Load and Configuration Function
* Part 7: Network PHP SQL Data access
* Part 8: Network SQL Functions
__darknite

PostMon May 07, 2012 3:09 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 1: Offline and Online architecture explained

Image

The offline mode architecture design is depicted in the above diagram. The client interacts with the web application using the browser.

All network based actions are taken via AJAX calls to the network. During network failure, the application handles the request by “buffering” it. This is done by storing this data in “Local Storage” using WebSQL.

The results that are then displayed are also stored, this part is crucial to the design. This is known as the “Current State”. The Current State data is always stored locally.

While the user is interacting with the web application, a secondary background action occurs. At a set interval, a background AJAX action is taken to take one item off the buffer and added to the network. If this action is successful then the network returns the buffer ID (the local buffer record). The local buffer record is then deleted. If the action fails then it can be ignored and no local buffer record are deleted.

When a user starts the application, we must first check to see if the local buffer is empty. If it is empty this means that the network is up to date. The current data from the network should be “pulled down”. On the other hand if the buffer is not empty, then it means that the network is not yet up to date. In this case, the “local Current State Data” should be loaded.

In this manner, the application can work seamlessly with or without network connectivity.
__darknite

PostMon May 07, 2012 3:10 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 2: Front end UI and HTML

The front end UI is fairly straightforward and not the focus of this tutorial, below is a screenshot of the final UI.

Image

It consists of three buttons, and a “debug” window. The debug window allows us to understand the application activity.

The HTML code is attached below:

<!DOCTYPE html>
<html lang="en">
   <head>
   <!--
      HTML 5, Offline and Online WebSQL Application Tutorial
      Copyright (C)2012  The IT Ninja www.theitninja.co.uk

      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
   -->
      <title>Offline App</title>
      <script language="javascript" src="offline.js"></script>
      
      
      
      
      <style type="text/css">
      body
      {
         width:100%;
      }
.bt1 {
   -moz-box-shadow:inset 0px 1px 0px 0px #caefab;
   -webkit-box-shadow:inset 0px 1px 0px 0px #caefab;
   box-shadow:inset 0px 1px 0px 0px #caefab;
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #77d42a), color-stop(1, #5cb811) );
   background:-moz-linear-gradient( center top, #77d42a 5%, #5cb811 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#77d42a', endColorstr='#5cb811');
   background-color:#77d42a;
   -moz-border-radius:6px;
   -webkit-border-radius:6px;
   border-radius:6px;
   border:1px solid #268a16;
   display:inline-block;
   color:#306108;
   font-family:arial;
   font-size:15px;
   font-weight:bold;
   padding:6px 24px;
   text-decoration:none;
   text-shadow:1px 1px 0px #aade7c;
}.bt1:hover {
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #5cb811), color-stop(1, #77d42a) );
   background:-moz-linear-gradient( center top, #5cb811 5%, #77d42a 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5cb811', endColorstr='#77d42a');
   background-color:#5cb811;
}.bt1:active {
   position:relative;
   top:1px;
}
/* This imageless css button was generated by CSSButtonGenerator.com */
.bt2 {
   -moz-box-shadow:inset 0px 1px 0px 0px #bbdaf7;
   -webkit-box-shadow:inset 0px 1px 0px 0px #bbdaf7;
   box-shadow:inset 0px 1px 0px 0px #bbdaf7;
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #378de5) );
   background:-moz-linear-gradient( center top, #79bbff 5%, #378de5 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#378de5');
   background-color:#79bbff;
   -moz-border-radius:6px;
   -webkit-border-radius:6px;
   border-radius:6px;
   border:1px solid #84bbf3;
   display:inline-block;
   color:#ffffff;
   font-family:arial;
   font-size:15px;
   font-weight:bold;
   padding:6px 24px;
   text-decoration:none;
   text-shadow:1px 1px 0px #528ecc;
}.bt2:hover {
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #378de5), color-stop(1, #79bbff) );
   background:-moz-linear-gradient( center top, #378de5 5%, #79bbff 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#378de5', endColorstr='#79bbff');
   background-color:#378de5;
}.bt2:active {
   position:relative;
   top:1px;
}
.bt3 {
   -moz-box-shadow:inset 0px 1px 0px 0px #e6cafc;
   -webkit-box-shadow:inset 0px 1px 0px 0px #e6cafc;
   box-shadow:inset 0px 1px 0px 0px #e6cafc;
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #c579ff), color-stop(1, #a341ee) );
   background:-moz-linear-gradient( center top, #c579ff 5%, #a341ee 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#c579ff', endColorstr='#a341ee');
   background-color:#c579ff;
   -moz-border-radius:6px;
   -webkit-border-radius:6px;
   border-radius:6px;
   border:1px solid #a946f5;
   display:inline-block;
   color:#ffffff;
   font-family:arial;
   font-size:15px;
   font-weight:bold;
   padding:6px 24px;
   text-decoration:none;
   text-shadow:1px 1px 0px #8628ce;
}.bt3:hover {
   background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #a341ee), color-stop(1, #c579ff) );
   background:-moz-linear-gradient( center top, #a341ee 5%, #c579ff 100% );
   filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#a341ee', endColorstr='#c579ff');
   background-color:#a341ee;
}.bt3:active {
   position:relative;
   top:1px;
}
#Main
{
   text-align:center;
}
#MainInner
{
   margin:0 auto;
   width:960px;
   text-align:left;
}
.Totals
{
   text-align:center;
   font-size:175%;
   font-weight:bolder;
}
.debug
{
   font-size:70%;
   color:ddd;
   height:400px;
   overflow:scroll;
   border:solid 1px #d2d2d2;
}
h1
{
   font-size:28px;
   font-family:arial;
}
</style>
   </head>
   <body>
   
   <div id="Main">
      <div id="MainInner">
   
      <h1>HTML 5, Offline and Online WebSQL Application Tutorial</h1>
      <small>&copy2012 The IT Ninja | www.theitninja.co.uk</small>
      <br/>
      <br/>
      <br/>
      
   
      <table>
         <tr>
            <td class="Totals">
               <span id="Total_1"></span>
            </td>
            <td class="Totals">
               <span id="Total_2"></span>
            </td>
            <td class="Totals">
               <span id="Total_3"></span>
            </td>
         </tr>
         <tr>
            <td>
               <a href="javascript:ButtonClicked(1);" class="bt1">click me!</a>
            </td>
            <td>
               <a href="javascript:ButtonClicked(2);" class="bt2">click me!</a>
            </td>
            <td>
               <a href="javascript:ButtonClicked(3);" class="bt3">click me!</a>
            </td>
         </tr>
      </table>
      
      <br/>
      <br/>
      <br/>
      
      <div class="debug">
         Debug Info:
         <br/>
         Local->User Id:<span id="UserId">777</span><br/>
         <span id="debug"></span>
      </div>
   </div>
   </body>
</html>
__darknite

PostMon May 07, 2012 3:10 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 3: Web SQL and Ajax Functions

For this application we will be relying on two key technologies:

* WebSQL
* AJAX

We can wrap up these two key ingredients into Javascript functions, these will be very important as these will be used throughout the rest of the application.

Web SQL Function:

The Web SQL function takes 4 parameters, these are:

* sql: the sql query to be executed.
* data: the set of parameters associated with the above sql query, passed as an array.
* fn_callback: a function callback when the sql is executed.
* fn_Error: a function callback when the sql fails.

//=======================================================
// WebSql
//=======================================================
function runSql(sql, data, callback_fn, error_fn)
{
   var db = openDatabase("DataBuffer", "1.0", "Buffer Data For Bad Connections", 5 * 1024 * 1024);
   if(db!==null){db.transaction(function(tx){tx.executeSql(sql,data,callback_fn,error_fn);});}
}
function OnError(tx, e){alert("Local WebSQL Error -> "+e.message);}


Ajax Function:

The Ajax function takes 4 parameters. these are:

* action: this is a string that represent the function call on the server end.
* data: json data that is associated with the network function call.
* fn_callback: a function callback when the network responds.
* fn_Error: a function callback when the network fails.

//=======================================================
// Ajax Call
//=======================================================
function AjaxCall(action, data, fn_callback, fn_Error)
{
   var xmlhttp = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");   
    xmlhttp.onreadystatechange=function()
    {
      if (xmlhttp.readyState==4 && xmlhttp.status==200)
      {
         fn_callback(xmlhttp.responseText);
      }
      if(xmlhttp.readyState==4 && xmlhttp.status !==200)
      {      
         if(fn_Error!==null)
         {
            fn_Error(data);
         }
      }
    }
    xmlhttp.open("GET",GetDataUrl(action, data),true);
    xmlhttp.send();
}
function GetDataUrl(action, data)
{
    var seed = new Date().getTime();
    return "/buffer/jxNetwork.php?action="+action+"&data="+encodeURIComponent(data)+"&seed="+seed;
}


A finally some miscellaneous utility functions:

//=======================================================
// debug
//=======================================================
function d(x){j('debug').innerHTML +=x+"<br/>";}
function j(x){return document.getElementById(x);}
__darknite

PostMon May 07, 2012 3:10 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 4: Local SQL functions

All of our local database (Web SQL) functions are listed below:

* CreateBufferTable: this is part of the application initialisation, if this table does not exist then creates it.

The buffer table holds data for failed network transactions.

* CreateStateTable: this is part of the application initialisation, if this table does not exist then creates it.

The state table is crucial, it holds the current “state” of the web application, it is triggered when the screen is updated. This data is only “read” when the application is started up and the buffer is NOT empty.

* AddBufferItem: This adds the data to the local Buffer.

* DeleteBufferItem: Self explanatory
* GetBufferItem: Gets the oldest buffer item (to be pushed to the network)
* GetBufferCount: Self explanatory
* UpdateCurrentState: When page render is updated, the current “state” is saved
* GetCurrentState: This reads the current state.
* DeleteAll: Debug purpose, deletes all local data.

//=======================================================
// database functions
//=======================================================
function CreateBufferTable()
{
   sql = "create table if not exists"
       +" buffer(buffer_Id Integer Primary Key Asc, UserId Integer, ButtonId Integer, TotalCount Integer, DateStamp datetime)";
      
   runSql(sql, [], function(tx, e){d("Local->Buffer Table Added/Detected")}, OnError);
}
function CreateStateTable()
{
   sql = "create table if not exists"
       +" state(state_Id Integer Primary Key Asc, button1 int, button2  int, button3 int)";
      
   runSql(sql, [], function(tx, e){d("Local->State Table Added/Detected")}, OnError);
}
function AddBufferItem(UserId, ButtonId, TotalCount)
{   
   var DateStamp = new Date();
   sql = "insert into buffer (UserId, ButtonId, TotalCount, DateStamp) values (?,?,?,?)";
   runSql(sql, [UserId, ButtonId,TotalCount, DateStamp],null, OnError);
}
function DeleteBufferItem(id)
{
   sql = "delete from buffer where buffer_Id = ?";
   runSql(sql, [id], null, OnError);
}
function GetBufferItem()
{
   sql = "select Buffer_Id, UserId, ButtonId, TotalCount from buffer order by DateStamp asc limit 1";
   runSql(sql,[], GotBufferItem,OnError);
}
function GetBufferCount()
{
   sql = "select count(Buffer_Id) as Total from buffer";
   runSql(sql,[], TotalBufferCount,OnError);
}
function UpdateCurrentState(button1, button2, button3)
{
   sql = "delete from state";      
   runSql(sql, [], function(tx, e){d("Local->Current State Data flushed")}, OnError);
   sql = "insert into state (button1, button2, button3) values (?,?,?)";      
   runSql(sql, [button1, button2, button3], function(tx, e){d("Local->Current State Data Updated")}, OnError);
}
function GetCurrentState()
{
   sql = "select button1, button2, button3 from state limit 1";      
   runSql(sql, [], GotCurrentState, OnError);
}
function DeleteAll()
{
   sql = "delete from buffer";      
   runSql(sql, [], function(tx, e){d("Local->All Local Buffer Data Deleted")}, OnError);
   sql = "delete from state";      
   runSql(sql, [], function(tx, e){d("Local->All Local State Deleted")}, OnError);
}
__darknite

PostMon May 07, 2012 3:11 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 5: Buffer Logic and Buffer Manager

Buffer Logic:
The entry point of the javascript code is “InitBuffer()”. This creates both the Buffer and State tables (if they do not exist).

Then the buffer is checked, if the buffer is not empty, the data is loaded from the State table. If the buffer is empty then the data is loaded from the network.

After this the buffer Manager is called, this simply runs in a loop in the background to clear the buffer.

Buffer logic

//=======================================================
// Buffer Logic
//=======================================================
InitBuffer();

function InitBuffer()
{
   CreateBufferTable();
   CreateStateTable();
   GetBufferCount();
   //DeleteAll();
}
function TotalBufferCount(tx, rs)
{
   if(rs.rows.length > 0 && rs.rows.item(0).Total > 0)
   {
      //because buffer is not empty, network is out of sync, load data from local state data
      GetCurrentState();      
   }
   else
   {
      //because buffer is empty, network is up to date, load data from network
      AjaxCall("GetLatest", "",LoadDataFromNetwork, d);
   }
}
function GotCurrentState(tx, rs)
{
   //loading from local store:
   var state=null;
   if(rs.rows.length > 0)
   {
      state = {c1 : rs.rows.item(0).button1
            ,c2 : rs.rows.item(0).button2
            ,c3 : rs.rows.item(0).button3
            }
      j("Total_1").innerHTML = state['c1'];
      j("Total_2").innerHTML = state['c2'];
      j("Total_3").innerHTML = state['c3'];
      d('Local->Data Loaded From Local State');
   }
   
   BufferManager();
}
function LoadDataFromNetwork(data)
{
   var x = JSON.parse(data);
   
   if(x!==null)
   {
      j("Total_1").innerHTML = x["button1"];
      j("Total_2").innerHTML = x["button2"];
      j("Total_3").innerHTML = x["button3"];
      d('Local->Data Loaded From Network');
   }
   BufferManager();
}


Buffer Manager

//=======================================================
// Buffer Manager
//=======================================================
function BufferManager()
{
   GetBufferItem();
   setTimeout('BufferManager()',1000);
}
function GotBufferItem(tx, rs)
{
   var data =null;
   if(rs.rows.length > 0)
   {
      data = { bufferId : rs.rows.item(0).buffer_Id
            ,userId : rs.rows.item(0).UserId
            ,buttonId : rs.rows.item(0).ButtonId
            ,totalCount : rs.rows.item(0).TotalCount
            }
   }
   
   if(data !==null)
   {
      d('Local->Submitting Buffer Item to Network:'+data['bufferId']);
      AjaxCall("BufferAddItem", JSON.stringify(data)
            ,function(x)
            {
               var data = JSON.parse(x);
               if(data !==null)
               {
                  DeleteBufferItem(data['bufferId']);
                  d('Local->Buffer Item Deleted:'+data['bufferId']);   
               }
            }
            ,function(x){d('Local->Failed to submit buffer item to network');});
   
   }
}


A closer look at the “ButtonClicked()” function:

This function is triggered when one of the buttons are clicked. It simply takes the current state (total number of times this button has been clicked), and increments it.

An Ajax request is made to the network to update this data, on fail it is buffered. the result is then updated on the screen and the “Current State Data” is also updated.

function ButtonClicked(id)
{
   var total = parseInt(j('Total_'+id).innerHTML);
   if(!isNaN(total))
   {
      total+=1;
   }
   else
   {
      total=0;
   }
   
   var json = { userId: parseInt(j('UserId').innerHTML)
            ,buttonId: id
            ,totalCount: total
            };
            
   //Update Network --> on failure add to buffer, finally save current state               
   AjaxCall('AddItem',JSON.stringify(json)
         ,d
         ,function(x)
         {
            var data = JSON.parse(x);
            AddBufferItem(data['userId'],data['buttonId'],data['totalCount']);
            d('Local->Network Fail, data added to local');
         });
   //refresh display
   j('Total_'+id).innerHTML = total;
   //save current state
   UpdateCurrentState(j('Total_1').innerHTML,j('Total_2').innerHTML,j('Total_3').innerHTML);
}
__darknite

PostMon May 07, 2012 3:11 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 6: Network PHP Load and Configuration Function

We need to define some database configuration for use in our project. The values below will need to be set to whatever your database settings are:

//==================================
// config
//==================================
define("db_user", "buffer_user");
define("db_pass", "password");
define("db_name", "buffer_demo");
define("db_server", "127.0.0.1");


This section is server side, all calls are made to the jxNetwork.php script. The overall theory for this part is simple. All ajax calls made to this file contain two main querystring parameters:

* action: a string value that names a function to execute.
* data: json data that is url-encoded.

The pattern is fairly straightforward, the ajax is always going to call a function and possibly have some data that function should execute. Based on this rather than have a long winded mess of spaghetti code that entails lots of “switch statements” or “if then statements”. We can replace it with a single function, that will check to see if the named function exists, and if so call it and pass any data should it exists.

//==================================
// main function
//==================================
Page_Load();
function Page_Load()
{   
   if(isset($_GET['action']) && function_exists($_GET['action']))
   {
      $json ="";
      if(isset($_GET['data'])){$json = json_decode(urldecode($_GET['data']));}
      call_user_func($_GET['action'], $json);
   }
}
__darknite

PostMon May 07, 2012 3:11 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 7: Network PHP SQL Data access

To avoid ugly and messy spaghetti code we can create two simple “data access” functions. These will help remove duplicate code and help us write cleaner code, when calling the database.

There are two main functions:

* runSQL: this takes one parameter “query”. Which is the sql query to be executed. The returned value can be iterated using “mysql_fetch_array” function.

* runSQLParms: the exact same as above, however for queries that have parameters involved, this is a cleaner way of executing them. An array of parameters along with the sql query can be passed. The function escapes the parameters and then creates merges the parameters with the query to create the final sql query which is then executed.

 
//==================================
// Data Access
//==================================
function runSQL($Query)
{
   //connect to server, then test for failure
   if(!($conn = mysql_connect(db_server,db_user,db_pass)))
   {
      print("Failed to connect to database!<br>\n");
      exit();
   }            
   //select database, then test for failure
   if(!($dbResult = mysql_query("USE ".db_name, $conn)))
   {
      print("Can't use the database.<br>\n");
      exit();
   }
            
   $rs = mysql_query($Query) or die(mysql_error());
   mysql_close($conn);
   return $rs;
}
function runSQLParms($Query,$Params)
{
   //connect to server, then test for failure
   if(!($conn = mysql_connect(db_server,db_user,db_pass)))
   {
      print("Failed to connect to database!<br>\n");
      exit();
   }            
   //select database, then test for failure
   if(!($dbResult = mysql_query("USE ".db_name, $conn)))
   {
      print("Can't use the database.<br>\n");
      exit();
   }   
   foreach($Params as $key => $value)
   {
      $Query = str_replace($key,mysql_real_escape_string($value,$conn),$Query);
   }
   //echo $Query;
   $rs = mysql_query($Query) or die(mysql_error());
   mysql_close();
   return $rs;
}
__darknite

PostMon May 07, 2012 3:12 pm


User avatar
Rank: Junior

Posts: 88
Joined: Wed Feb 22, 2012 12:07 pm



Part 8: Network SQL Functions

This is the final part of this tutorial. The “loader” function simply call these functions and pass any data as a parameter (if it exists).

The functions use the “Data Access” functions as described in part 7. The functions are self explanatory:

//==================================
// main database functions
//==================================
function CreateTable()
{
   $sql = "create table if not exists
         buffer
         (
            buffer_id int NOT NULL AUTO_INCREMENT,
            PRIMARY KEY(buffer_id),
            UserId Int,
            ButtonId Int,
            TotalCount Int,
            DateStamp datetime
         )";
   runSQL($sql);
}
function DeleteAll()
{
   $sql = "delete from buffer";
   runSQL($sql);
   header('Content-type: text/plain');
   echo "All items deleted!";
}
function BufferAddItem($json)
{
   $sql = "insert into buffer (UserId, ButtonId, TotalCount, DateStamp)
                  values (@userId, @buttonId, @totalCount, now())";
   
   $p["@userId"] = $json->{'userId'};
   $p["@buttonId"] = $json->{'buttonId'};
   $p["@totalCount"] = $json->{'totalCount'};
   
   runSQLParms($sql, $p);
   
   //return back the bufferId so that it can be deleted locally
   $res["bufferId"] = $json->{'bufferId'};
   header('Content-type: application/json');
   echo json_encode($res);
}
function AddItem($json)
{
   $sql = "insert into buffer (UserId, ButtonId, TotalCount, DateStamp)
                  values (@userId, @buttonId, @totalCount, now())";
   
   $p["@userId"] = $json->{'userId'};
   $p["@buttonId"] = $json->{'buttonId'};
   $p["@totalCount"] = $json->{'totalCount'};
   
   runSQLParms($sql, $p);
   echo "Network->Item Addded!".$json->{'totalCount'};
}
function GetLatest()
{
   $sql =  "select  (SELECT totalCount FROM buffer where buttonId = 1 order by DateStamp desc limit 1) as c1
               ,(SELECT totalCount FROM buffer where buttonId = 2 order by DateStamp desc limit 1) as c2
               ,(SELECT totalCount FROM buffer where buttonId = 3 order by DateStamp desc limit 1) as c3";
         
   $rs = runSQL($sql);
   
   $json;      
   while($row = mysql_fetch_array($rs))
   {
      $json["button1"] = (int)$row['c1'];
      $json["button2"] = (int)$row['c2'];
      $json["button3"] = (int)$row['c3'];
   }
   header('Content-type: application/json');
   echo json_encode($json);
}


That’s it! although this tutorial is targeted for advanced programmers, it hopefully should help others also.

As usual all the material for this project is available in the members section:

Project Files for Download
9 posts • Page 1 of 1

Who is online

Users browsing this forum: No registered users and 1 guest