Forum Moderators: open

Message Too Old, No Replies

Need Help with airport-style kisok code

code for airport-style kiosk display

         

agilent

9:55 pm on Jun 28, 2010 (gmt 0)

10+ Year Member



Hello-
I have an application in mind for the display of tabular data in a web browser set up as a kiosk. The data is only refreshed once an hour, but as the data set is lengthy, the display should rotate through three or four pages and then repeat, without user interaction. Think of an "arrival and departures" type display at the airport.

To explore the idea, I coded up a static page that is representative of the html and javascript that I think my server should deliver. It contains a string array with 20 or 30 lines of raw data, and a function with a simple mechanism that adjusts start and end indexers to pull off a block of lines out of the array, (depending upon which "page" of the table we are displaying.) Further, the function contains the necessary document.writes to actually build the tables via HTML, and I used CSS to set fonts colors and all that stuff.

Finally, I added logic in a script to call the display function, each time incrementing a global variable to tell it what table "page" to display in sequence.

The my problem is that I don't see a sequence of pages displayed. Rather, the system will just hang until the script finishes what it's doing and exits or is terminated, and then the browser displays whatever accumulated during the run. When I finally do get output, it's exaclty what I want, and I get no errors, but again, the trouble is I can't figure out how to fix it so that I actually see the progression of pages.

How do you force the screen to update and be "live" as the script is running? Is my approach fundamentally inconsistent with the way Javascript/HTML operates, and if so, does anyone have any examples of source code that I could look at which would implement the "airport kiosk" style display?

BTW- My fallback position would be to have the server deliver all the pages, but this strikes me as an incredible waste of bandwidth, as the nature of the data is such that it won't change much in the course of an hour. It's clunky to have to keep hitting the server just to get the pages to rotate on the client end.

Many thanks in advance,
@agilent

subexpression

4:51 am on Jun 29, 2010 (gmt 0)

10+ Year Member



agilent,

What server-side language and database are you using?

As for fetching data, I would use an XMLHttpRequest (AJAX) object to check your data source for updates once an hour using setTimeout().

Create a method for checking the last update against the next update's timestamp.
Use AJAX to query your server and find a newer timestamp.

If it's been an hour, and there's no update found, set the milliseconds to 60*1000, so it pings the server every minute.
If there is an update, then initiate the update with another AJAX request once again and refresh your table array.
Reset the milliseconds to 60*60*1000, so it goes back to pinging the server every hour.

Then, use a separate setTimeout() to iterate through your tables using CSS display property...the current table is set to display = 'block', while the others are set to display = 'none';

Depending on how you have your data organized will determine how to parse it and refresh your presentation layer.

If you could give details about your server configuration, server-side language, and data source, it would be helpful.

agilent

4:06 pm on Jun 29, 2010 (gmt 0)

10+ Year Member



@subexpression-
Thanks for taking the time to look at my question and to respond.

Let me answer your question directly by saying that the server is an Ubuntu 8.04 box, the database engine is MySql, and the server-side language is PHP5. I would hasten to add, however, that at the moment, getting live data is not really my problem. My problem has to do with the display of data once I've gotten it.

That said, let me rephrase the objective. Suppose I have a string array with three elements: "Huey," "Dewey," and "Louie." When you load the page, it should clear, and display "Huey" for 30 seconds. Then, without user input or interaction, the screen should clear itself and the second array element "Dewey" should appear. Again, after 30 seconds, the screen should clear and "Louie" should appear.

After a final 30 second delay, the screen should clear and the screen display should return to "Huey" once more. This display of array elements should loop indefinitely.

What I have discovered about Javascript is that while I have created the code and logic to do what I've just described, it doesn't work as expected. The screen is never updated until the script terminates. This means you never see anything until the script is terminated, and then you only see what was last done to the screen by the code.

Care to take a stab at a sample block of code for the huey-dewey-and-louie problem?

Thanks,
agilent

subexpression

5:25 pm on Jun 29, 2010 (gmt 0)

10+ Year Member



agilent,

OK - I put together a "kiosk" object which does what you described above:
Notice the self.status.thirty...it's set to 3 seconds so you can see the refresh quickly. You can change it to 30*1000 to get 30 seconds of delay.

Also, below the self.content, I included a data object for fetching fresh data from the server and it uses the synchronous "ajax" object below it. I know, it's not ajax if it's synchronous. SJAX?
That's where self.status.last, self.status.next, self.status.sixty, and self.status.hour come into play.
But, out of the box, the kiosk.data.load() is not connected...so you can figure that out at a later date and sort out how you want to check the timestamp for fresh data.

It should work pretty well for you - it works great on my machine.
Let me know if you have any issues. Feel free to PM me through StickyMail.

- subexpression

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>

<head>

<style type="text/css">
#container {
background:#fefefe;
margin:20px;
border:1px solid #aaa;
padding:10px;
width:400px;
height:200px;
font-family:Arial;
font-size:18px;
font-weight:normal;
color:#777;
}
</style>

<script type="text/javascript">
var kiosk = new function(){
var self = this;
// UPDATE STATUS
self.status = {
'last': '',
'next': '',
'thirty': 3*1000,
'sixty': 60*1000,
'hour': 60*60*1000
};
// PRESENTATION LAYER
self.content = {
'data':[],
'sequence': '',
'container': '',
'load': function(){
this.data = ['Huey','Dewey','Louie'];
this.sequence = this.data.length - 1;
this.container = document.getElementById('container');
this.update();
},
'update': function(){
this.container.innerHTML = this.data[this.index()];
setTimeout(function(){self.content.update()},self.status.thirty);
},
'index': function(){
((this.data.length - 1) == this.sequence) ? this.sequence = 0 : this.sequence++;
return this.sequence;
}
};
// DATA ARRAY UPDATE
self.data = {
't': '',
'load': function(){
this.t = self.status.hour;
},
'check': function(){
setTimeout(
function(){
self.status.next = self.ajax.getdata('timestamp.php','&lastupdate=' + self.status.last);
if(self.status.next == self.status.last){
self.data.t = self.status.minute;
this.check();
}
else {
self.data.update();
}
},self.data.t);
},
'update': function(){
self.content.data = self.ajax.getdata('update.php');
self.content.load();
this.t = self.status.hour;
this.check();
}
};
// AJAX
self.ajax = {
'request': false,
'newrequest': function(){
this.request = false;
try {this.request = new XMLHttpRequest()}
catch(error1){
try {this.request = new ActiveXObject('Msxml2.XMLHTTP')}
catch(error2){
try {this.request = new ActiveXObject('Microsoft.XMLHTTP')}
catch(error3){this.request = false}
}
}
},
// SYNCHRONOUS DATA RESPONSE
'getdata': function(url,args){
args = (typeof args == 'undefined') ? '' : args;
url += '?rand=' + Math.floor(Math.random()*1000) + args;
this.newrequest();
if(this.request != null){
this.request.open('GET',url,false);
this.request.send(null);
return this.request.responseText;
}
}
};
}
window.onload = function(){
kiosk.content.load();
}
</script>

</head>

<body>

<div id="container"></div>

</body>

</html>

agilent

6:32 pm on Jun 29, 2010 (gmt 0)

10+ Year Member



@subexpression-

Thank you so much. I will test drive and study this code shortly. I may have questions ;)

While I have done a fair amount of programming over the last 20 years, including C, Fortran, Pascal, Basic, and assembler for a half-dozen micros, I am a complete newbie where Javascript, HTML, and web programming is concerned. There are obviously some fundamental differences between these and the more linear programming I am used to. I have enjoyed very much tinkering with all of this, and guidance from experts like yourself really helps bootstrap the learning process. Thanks again.

subexpression

7:05 pm on Jun 29, 2010 (gmt 0)

10+ Year Member



agilent,

good to hear! You might benefit from copying the code and restoring the nesting of code blocks { } with TAB characters.
Then, you'll be able to understand it a bit better.
That's one of the drawbacks of this forum...whitespace is removed from posts without pre /pre tags

good luck,

- s

Fotiman

7:45 pm on Jun 29, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I tend to post my code examples in:
[quote][pre][code]
[/code][/pre][/quote]

It keeps the formatting a little better.

subexpression

7:52 pm on Jun 29, 2010 (gmt 0)

10+ Year Member


Thanks Fotiman - I'll remember that.

I tried [quote][code][pre][/pre][/code][/quote] and it didn't work so well.

- s

agilent

10:03 pm on Jun 29, 2010 (gmt 0)

10+ Year Member



@subexpression-

Wow... I'm afraid that the way this code functions is less apparent to me than I had thought it would be.

I have a *lot* of questions, but rather than flood you with them, I think a few words as to your reasoning will probably answer a great many of those questions. Could you give me the "nickel tour" and offer some brief block-by-block commentary? Hopefully, the questions that remain will be both few and more meaningful.

Again, thanks for your time.
@agilent

subexpression

12:03 am on Jun 30, 2010 (gmt 0)

10+ Year Member



agilent,

Alright! Yeah, it can be cryptic at first glance, but I'm certain you'll understand what's happening after a bit of closer examination.

The parent object "kiosk" is declared and initialized without a constructor, but rather a generic function(){}, and encapsulates all other objects, methods and properties:

<script type="text/javascript">
var kiosk = new function(){
var self = this;
};
</script>

The "self" variable is used to resolve scope issues associated with Javascript's keyword "this".
The "this" keyword, in traditional Javascript, refers to the "owner" of the object, method, event or property. When using "this" in inner object code blocks, "this" no longer refers to the parent object, but is encapsulated within the scope of the inner object or method. So, to get around this, I first assign "this" to the variable "self", so the parent object can be referenced anywhere. self.method() is the same as this.method() or formally kiosk.method().

When using event-driven methods, such as setTimeout(), the formal self.object.method() is required to again resolve scope issues.

The first object, "status", is more or less an associative array for timeout milliseconds and timestamp status. Javascript doesn't quite have associative arrays, but JSON (Javascript Object Notation) takes care of that. There are other, less-efficient ways to create hash tables, but I prefer JSON. The benefit is that an index isn't required like with associative arrays in classic programming.

The dot "." operator accesses the the value by it's respective key: self.status.last, and externally, you can call kiosk.status.last to access the value. Each key & value pair is separated by a comma. Notice that 'last':'' key has an empty string for a value. That's because it will be initialized later in the "data" object.

// UPDATE STATUS
self.status = {
'last': '',
'next': '',
'thirty': 3*1000,
'sixty': 60*1000,
'hour': 60*60*1000
};


The next part is the actual "Huey, Dewey, and Louie" rotator...the presentation layer which interacts with the user interface, refreshing the container div's innerHTML property with array data every 30 seconds.

The first key/value pair, 'data', is initialized with an empty array []
The second, 'sequence', is initialized with an empty string ''
The third, 'container', is also initialized with an empty string ''
The fourth, 'load', is where this script initializes everything and begins the methods which utilize setTimeout().

'load': function(){} first loads in your data array...which has 3 hard-coded strings, Huey, Dewey, and Louie.
Once you can resolve how you're going to access data, then you can set up the self.ajax.get() method to grab fresh data and populate the 'this.data' array during load time. But for this example, it's hard coded.

The 'sequence' keeps track of where you're at in the array as each 30 second interval passes. In the 'load' method, sequence is initialized to this.data.length - 1 ...which is 3 - 2 for the zero-based index of the data array.

this.container is initialized with Javascript's built-in method for referencing HTML elements by id, document.getElementById();

getElementById() requires a string argument and must be the id of an existing element in the document. The id attribute of the <div> element is how we reference the div itself.

So, now that this.container is initialized, the <div> element can be reference, altered, appended, and even deleted through Javascript. Our purpose is the inject data into it using the .innerHTML property of the div.

This happens in the 'update' method called at the end of the 'load' method.
this.data[this.index()] determines where in the array we are at...if it's at the last index, it starts over at 0. Otherwise, it increments by 1.
This occurs in the this.index() method ternary condition, then returns the current value in this.sequence, accesses that array index, and assigns the value to the innerHTML property of this.container.
Then, <div id="container"></div> becomes <div id="container">Huey</div> and so on.

The next line in 'update' calls itself again in 30 seconds using the setTimeout() method.

// PRESENTATION LAYER
self.content = {
'data':[],
'sequence': '',
'container': '',
'load': function(){
this.data = ['Huey','Dewey','Louie'];
this.sequence = this.data.length - 1;
this.container = document.getElementById('container');
this.update();
},
'update': function(){
this.container.innerHTML = this.data[this.index()];
setTimeout(function(){self.content.update()},self.status.thirty);
},
'index': function(){
((this.data.length - 1) == this.sequence) ? this.sequence = 0 : this.sequence++;
return this.sequence;
}
};


That pretty much handles the presentation layer.

Next, you'll need to decide how you want to handle data on your server.
I would suggest creating a PHP script which detects if fresh content is available.
Using MySQL, you could set up a table which has a few simple fields for updates: last and next
When an update is available, this table's last & next values are updated as well as the array data you seek to refresh.

The next object, 'data', handles updates and has its own timer on another thread.
I use timers for asynchronous processes and create multi-threaded apps in Javascript, preventing those "Unresponsive Script" nags. Asynchronous Javascript and XML (AJAX) also behaves in this manner.
't': '' is initialized several times depending on how long it has been since the last update.
In the 'load' method, this.t is initialized to 60 minutes.

The 'check' method creates an AJAX request for timestamp.php after 60 minutes, a page you would probably query MySQL for the last timestamp. If the timestamp is not newer than the last update, then self.data.t is set to 60 seconds, and this.check() is called again until an update is found.

Once it is found, self.data.update() is called and another AJAX request is created to refresh the original data array...self.content.data
Then, self.content.load() is reinitialized to load in the new array
self.data.t is reset to 60 minutes awaiting the next update in an hour or so.
Lastly, this.check() is called to start the whole process over again.


// DATA ARRAY UPDATE
self.data = {
't': '',
'load': function(){
this.t = self.status.hour;
},
'check': function(){
setTimeout(
function(){
self.status.next = self.ajax.getdata('timestamp.php','&lastupdate=' + self.status.last);
if(self.status.next == self.status.last){
self.data.t = self.status.minute;
this.check();
}
else {
self.data.update();
}
},self.data.t);
},
'update': function(){
self.content.data = self.ajax.getdata('update.php');
self.content.load();
this.t = self.status.hour;
this.check();
}
};

The AJAX request and get methods are pretty much standard hack you can find anywhere.
I organize mine with JSON because I feel it's easier to maintain.
I use synchronous calls often, in order to preserve responseText and return it before the readyState changes and the value evaporates.

// AJAX
self.ajax = {
'request': false,
'newrequest': function(){
this.request = false;
try {this.request = new XMLHttpRequest()}
catch(error1){
try {this.request = new ActiveXObject('Msxml2.XMLHTTP')}
catch(error2){
try {this.request = new ActiveXObject('Microsoft.XMLHTTP')}
catch(error3){this.request = false}
}
}
},
// SYNCHRONOUS DATA RESPONSE
'getdata': function(url,args){
args = (typeof args == 'undefined') ? '' : args;
url += '?rand=' + Math.floor(Math.random()*1000) + args;
this.newrequest();
if(this.request != null){
this.request.open('GET',url,false);
this.request.send(null);
return this.request.responseText;
}
}
};
}


Finally, window.onload = function(){} is called when the browser completes loading all HTML elements.
If the document wasn't finished loading and I attempted to access the id="content", I would get an error stating that the element I'm looking for is undefined. The kiosk.content.load() method is executed automatically once everything is loaded and ready.


window.onload = function(){
kiosk.content.load();
}

I hope that helps and doesn't obfuscate things for you! I know my explanations can be a bit wordy at times, but
I assume whoever reads this might not have any knowledge of some of these things. Someone might have an epiphany by reading a seemingly insignificant piece of info.

- s

agilent

4:37 pm on Jun 30, 2010 (gmt 0)

10+ Year Member



@subexpression

Outstanding walk-through! I actually appreciate the more detailed explanations because they elevate what would simply have been "an answer" to something more like a mini-tutorial.

My sense of all of this is that this type of programming makes heavy use of abstraction. Instead of referencing specific things, we reference things which contain things, which in turn contain other things. I can see where this would be of benefit in terms of reusability (but it does make understanding what's going on more difficult). The other impression I get is that program execution is less like the progression of a sequence of falling dominoes, and more like the cascade of a flame passing through a matchbook that has been lit on fire. The program loops, but at first glance, there is no obvious looping construct. In general, have to think OOP is a real bear if you are coding some kind of time-determinate functionality, say, in an embedded processor.

For the moment, I'm going to excuse myself from understanding the data retrieval portion, and focus on presentation with hard-coded data. I plan to use your example as a reference, and see if I can't rework my existing code to function as yours does. I'll let you know how I make out.

Speaking of data, in the case of this type of application, is there any reason not to use hard-coded data in the final product? I mean, since PHP on the server is what's generating the page, and PHP has access to MySQL, I can embed and "hard code" whatever data I wish when I deliver the page to the client. The only downside to refreshing the entire page with embedded data, as opposed to not refreshing the entire page but merely requesting more data, is that resending the javascript is redundant... and strictly speaking, wasteful of bandwidth.... Your thoughts?

agilent

subexpression

4:58 pm on Jun 30, 2010 (gmt 0)

10+ Year Member



agilent,

I like your analogy to the matchbox...with timer threads, many things can be simultaneously "lit on fire" so to speak, and remain independent. Yet, when a condition is met in one of the threads, it can interact, alter, or stop the other threads.

Yeah, OOP is hard to wrap your head around. It's like vital organs - you can't survive if one is missing - they're all mutually self supporting. But then, it's more like real-world objects. Maybe it's just my own coding style, but I always try to make Swiss Army Knife objects which do everything in one neat package.

I agree - there's really nothing wrong with hard-coded data in the final product. A page refresh is no big deal, if the customer doesn't care :)

And, bandwidth is becoming less and less a problem as internet speeds increase for the basic home user.

agilent

7:24 pm on Jul 6, 2010 (gmt 0)

10+ Year Member



@subexpression

I have been working on the kiosk code using your example as a template, and am seeing signs that this all will work. I have, however, run into some kind of problem that is unexpected.

In the original static javascript page I wrote, I was able to make the script invoke CSS styles to format rows and cells on the basis of status information. My old code didn't have the revolving-page behavior, but it was able to colorize information according to content. All the HTML tag stuff was written with document.writes.

Using my known-good tag generation code, I ported it to the skeleton you provided. In essence, all of the stuff that would have gone to document.write is now concatenated inside of "this.container.innerHTML."

The funny thing is that it no longer looks the same. I'm using the same style declarations, but now I can't seem to colorize rows. The header area is proportioned differently. I'm viewing this with Mozilla, but a Google search suggests that there are known problems with trying to build a table using innerHTML... at least with IE.

I have examples of how things should look, as generated by my old code, and I have my evolving code based on the skeleton you provided, both of which may be a little lengthy for inclusion here... but I can post them if you you wish.

Any ideas/suggestions on how to proceed?
agilent