Files
JIMRI/web/operations_RFID_Trains.html
T
2026-06-17 14:00:51 +02:00

635 lines
34 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Operations Trains List for RFID</title>
<style>
</style>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/panel.css">
<link rel="stylesheet" href="/css/user.css">
<link rel="stylesheet" href="/css/tables.css">
<!-- include the jquery.jmri library and its dependencies -->
<script src="/js/jquery-2.2.4.min.js"></script>
<script src="/js/json2.js"></script>
<script src="/js/jquery.websocket.js"></script>
<script src="/js/logger.js"></script>
<script src="/js/jquery.jmri.js"></script>
<script src="/js/sorttable.js"></script>
<!--
Webpage which changes its appearance depending how it is called. With just the file name, it displays the currently built trains in Operations
, with an extension " [webpage].html?number=3 " for trainID 3, etc.. it shows a version of Conductor pages for the train in question.
Replicates the "Conductor" pages, in providing a list of wagons to Pickup, DropOff or Local Shunting. Main difference in "drop off"
which shows the current train composition and drop-off wagons tagged within that table.
Includes ID-Tag data in each table of wagons, which updates as wagons pass a tag reader. Intended for use with RFID tags.
Tables automatically hide if nothing within that table.
Works using the jQuery mechanisms into the JMRI webserver, and requires JMRI 5.7.6 or later.
Thanks to Steve Todd for technical guidance, and in adding a few features to JMRI 5.7.6 to make this possible.
author and file copyright Nigel Cliffe, June 2024
-->
<script type="text/javascript">
var jmri = null;
//get the URL details to set variables for behaviour of page
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const pagePath = window.location.pathname;
const arrayOfParams = Object.keys(params) ;
if (arrayOfParams.length) {
var conductorPage = true ;
var trainListPage = false ;
} else {
var trainListPage = true ;
var conductorPage = false ;
}
// initialise the variables used by the Javascript later on
var theTrainName ="" ;
var theTrainNumber = "";
var theCars = [] ; // all cars reserved for train (including cars collected at future stops)
var dropCars = [] ; // car names to be dropped at current stop
var pickupCars = [] ; // car names to be picked up at current stop
var inTrainCars = [] ; // cars in train at current stop, including those to be dropped off
var localCars = [] ; // cars to be moved about at local stop, not joining train
const tickCarHTML = "<input class='rs-check' type='checkbox' onchange = 'rsCheck()'>" ;
//append a new row to the trains table, or replace an existing row, based on name
function setRowAllTrains(name, data){
var trainstbody = $("table#trains tbody").html(); //get current table body
var tr = "";
var tds = "" ;
if (data.location != "") { // ie. only display running trains
tds = "<td><a href='" + pagePath + "?number=" + data.name +"'>" + data.userName + "</a></td><td>"
+ data.description + "</td><td>"
+ data.trainDepartsName + "</td><td>"
+ data.departureTime + "</td><td>"
+ data.status + "</td><td>"
+ data.location + "</td><td>"
+ data.trainTerminatesName + "</td><td>"
+ data.route +"</td>";
tr = "<tr data-name='" + data.name + "'>" + tds + "</tr>"; //build row with key
}
var row = $("table#trains tr[data-name='" + name + "']"); //look for row by key
if ($(row).length) {
// jmri.log("replacing a row with: " + name );
row.html(tds); //if found, replace cells
if (tds == "") { row.remove(); } // remove a now empty row
} else {
// jmri.log("inside Trains updater else statement " + tr );
$("table#trains tbody").html(trainstbody + tr); //if not found, append row to table body
}
};
// append new row, or replace existing row, for cars. Table used depends on status of car.
function setRow(name, data){
var carstbody = $("table#cars tbody").html(); //get current table body
var pickuptbody = $("table#pickupCars tbody").html(); //get pickupCars table body
var localtbody = $("table#localCars tbody").html(); //get localCars table body
var tr = "";
var pu_tr = "" ;
var local_tr = "" ;
var tickCar = "";
if(data.destination) {
var where = "" ;
if (data.whereLastSeen != null ) { where = data.whereLastSeen; };
var when = ""
if (data.whenLastSeen != null ) { when = data.whenLastSeen; };
if( inTrainCars.includes(name)) { // should be inTrainCars....
// jmri.log("inside Cars updater " +name );
if (dropCars.includes(name)) {
tickCar = tickCarHTML ;
// jmri.log("should mark for drop: " + name);
}
var tds = "<td>" + data.name + "</td><td>" + data.type + "</td><td>" + where + "</td><td>" + when + "</td><td>" //build cells
+ data.destination.userName + "</td><td>" + data.destination.track.userName + "</td><td>"
+ tickCar +"</td>";
var tr = "<tr data-name='" + data.name + "'>" + tds + "</tr>"; //build row with key
} else {
var tds = "";
}
if (pickupCars.includes(name)) {
// jmri.log("inside pickupCars updater: " + name );
tickCar = tickCarHTML ;
var pu_tds = "<td>" + data.name + "</td><td>" + data.type + "</td><td>" + where + "</td><td>" //build cells
+ when + "</td><td>" + data.destination.userName + "</td><td>"
+ data.destination.track.userName + "</td><td>" + data.location.userName + "</td><td>"
+ data.location.track.userName +"</td><td>" + tickCar +"</td>";
//..data.trainName + "</td><td>" + data.destination.userName + "</td>"; // NC - need something to check for not-null values in destination..
var pu_tr = "<tr data-name='" + data.name + "'>" + pu_tds + "</tr>"; //build row with key
// jmri.log(pu_tr);
} else {
var pu_tds="";
}
if (localCars.includes(name)) {
// jmri.log("inside pickupCars updater: " + name );
tickCar = tickCarHTML ;
var local_tds = "<td>" + data.name + "</td><td>" + data.type + "</td><td>" + where + "</td><td>" //build cells
+ when + "</td><td>" + data.destination.userName + "</td><td>"
+ data.destination.track.userName + "</td><td>" + data.location.userName + "</td><td>"
+ data.location.track.userName +"</td><td>" + tickCar +"</td>";
//..data.trainName + "</td><td>" + data.destination.userName + "</td>"; // NC - need something to check for not-null values in destination..
var local_tr = "<tr data-name='" + data.name + "'>" + local_tds + "</tr>"; //build row with key
// jmri.log(local_tr);
} else {
var local_tds="";
}
var row = $("table#cars tr[data-name='" + name + "']"); //look for row by key
if ($(row).length) {
// jmri.log("replacing a row with: " + name );
row.html(tds); //if found, replace cells
} else {
// jmri.log("inside Cars updater else statement " + tr );
$("table#cars tbody").html(carstbody + tr); //if not found, append row to table body
}
var puRow = $("table#pickupCars tr[data-name='" + name + "']"); //look for row by key
if ($(puRow).length) {
// jmri.log("inside puRow change updater" );
puRow.html(pu_tds); //if found, replace cells
} else {
// jmri.log("inside pickupCars updater else statement " + pu_tr );
$("table#pickupCars tbody").html(pickuptbody + pu_tr); //if not found, append row to table body
}
var localRow = $("table#localCars tr[data-name='" + name + "']"); //look for row by key
if ($(localRow).length) {
// jmri.log("inside puRow change updater" );
localRow.html(local_tds); //if found, replace cells
} else {
// jmri.log("inside pickupCars updater else statement " + pu_tr );
$("table#localCars tbody").html(localtbody + local_tr); //if not found, append row to table body
}
};
};
// check whether all the tick-boxes are ticked to enable/disable the move location button
function rsCheck() {
var disabled = false;
var arrayOfTicks = Array.from(document.getElementsByClassName("rs-check"));
if (arrayOfTicks.length) {
// enable the tick-All buttons
$("#check-all").prop("disabled", false) ;
$("#clear-all").prop("disabled", false) ;
arrayOfTicks.forEach(ele => {
if (!ele.checked) {
disabled = true ;
return false;
};
});
} else {
// There are no check boxes to tick, so disable the tick ALL buttons
$("#check-all").prop("disabled", true);
$("#clear-all").prop("disabled", true);
}
// jmri.log("Rs Check: " + disabled ) ;
$("#move-train").prop("disabled", disabled);
};
// tick all the checkboxes for cars, allowing the train to move location, rather than ticking each individually.
function tickAll(checkState) {
Array.from(document.getElementsByClassName("rs-check")).forEach(ele => {
ele.checked = checkState ;
});
$("#move-train").prop("disabled", !checkState);
$("#clear-all").prop("disabled", !checkState);
$("#check-all").prop("disabled", checkState);
};
// Move the train to a new location
function moveTrain() {
var nextStop = document.getElementById("move-train").getAttribute("data-location");
// jmri.log("would like to move train: " + theTrainNumber + " To: " + nextStop) ;
// To move: {"type":"train","method":"post","data":{"name":"12","location":"Lakeview"}}
// To terminate: {"type":"train","method":"post","data":{"name":"12","location":null}}
// jmri.socket._send( json string to send );
if (nextStop == "Terminate_Train") {
var stringToSend = '{"type":"train","method":"post","data":{"name":' + theTrainNumber + ',"location":null}}';
} else {
var stringToSend = '{"type":"train","method":"post","data":{"name":' + theTrainNumber + ',"location":"' + nextStop + '"}}';
}
// jmri.log(stringToSend) ;
jmri.socket._send(stringToSend) ;
}
// update the text at top of page when a train updates its location (or other status), including working out the next location
function setTitles(name, data) {
// set the page title stuff when the train updates
// jmri.log("setting titles for train: name='" + name + "', data=" + JSON.stringify(data).substr(0, 12080) + " (End of data).");
document.getElementById("train-name").innerText = data.userName ;
document.getElementById("train-status").innerText = data.status ;
// document.getElementById("train-comment").innerText is set lower down in the function, as it needs the locations array to be opened ;
// jmri.log ("Train is: " +theTrainName + " at Location: " + currentLocation + ) ;
if (!data.location) {
// No location means train no longer running, and tables not required.
document.getElementById("all_tables").hidden = true ; // hide tabels when no train
document.getElementById("train-progress").innerText = "not running/terminated" ;
} else {
// Train is running, populate the information and unhide the tables.
if (! document.getElementById("train-location")) { // if we'd previously removed some HTML, put it back again...
document.getElementById("train-progress").innerHTML = "Train currently at: <span class=\'nc-bold\' id=\'train-location\'>Subtitle me</span>, next stop is: <span class=\'nc-bold\' id=\'train-next-location\'></span>" ;
}
document.getElementById("train-location").innerText = data.location ;
var curLocation = data.locationId;
var tempLocations = data.locations ;
var nextOne = false;
var foundNextLocation = false ;
const keysArray = Object.keys(tempLocations);
const numOfLocations = keysArray.length;
var loopCounter = 0;
tempLocations.forEach(eloc => {
// jmri.log("locations: " + eloc.name + "( " + eloc.userName + " ) " + nextOne) ;
loopCounter ++ ;
if (nextOne ) {
document.getElementById("train-next-location").innerText = eloc.userName ;
document.getElementById("move-train").innerText = "Move to " + eloc.userName ;
document.getElementById("move-train").setAttribute("data-location", eloc.userName) ;
foundNextLocation = true ;
}
nextOne = false;
if (eloc.name == curLocation) {
nextOne = true;
document.getElementById("train-comment").innerText = eloc.comment ;
}
if (loopCounter == numOfLocations) {
if (!foundNextLocation) {
// we're at end of locations loop, so there isn't a "next place"
// jmri.log("train terminating") ;
document.getElementById("train-next-location").innerText = "Train Terminates Here" ;
document.getElementById("move-train").innerText = "Terminate Train" ;
document.getElementById("move-train").setAttribute("data-location", "Terminate_Train") ;
} else {
// jmri.log("found a location, train not terminating") ;
}
}
});
document.getElementById("all_tables").hidden = false ; // show tabels when train exists
}
}
// Sort Tables based on when last seen. Not really happy with this, but seems a limitation of the sortable tables library.
function resortTheTables() {
// cludge sorting method - we call the "sort by carName" (column 0) then "sort by whenLastSeen" to
// force the sort to happen after each call to this function
// First the main Cars table
var firstTH = document.getElementsByName("th-carsSystemName")[0];
sorttable.innerSortFunction.apply(firstTH, []);
var firstTH = document.getElementsByName("th-carsWhenLastSeen")[0];
sorttable.innerSortFunction.apply(firstTH, []);
// And then the Pickup table..
var firstTH = document.getElementsByName("th-pickUpSystemName")[0];
sorttable.innerSortFunction.apply(firstTH, []);
var firstTH = document.getElementsByName("th-pickUpWhenLastSeen")[0];
sorttable.innerSortFunction.apply(firstTH, []);
}
// After the document has loaded, call this to start doing things
$(document).ready(function () {
// Start by hiding elements not required depending on whether this is a "trainListPage" or a "conductorPage"
if (trainListPage) {
// ie. not a "conductorPage", so hide all the Conductor tables.
document.getElementById("train-summary").hidden = true ;
document.getElementById("inTrainCars-div").hidden = true ;
document.getElementById("cars").hidden = true ;
document.getElementById("pickupCars-div").hidden = true ;
document.getElementById("pickupCars").hidden = true ;
document.getElementById("localCars-div").hidden = true ;
document.getElementById("localCars").hidden = true ;
document.getElementById("conductor_buttons").hidden = true ;
} else { // this is a "conductorPage", so hide the trains overview elements
document.getElementById("trainsDiv").hidden = true ; // Hide the trains tables from the overview page
document.getElementById("trains").hidden = true ;
document.getElementById("pageTitleRow").innerHTML = "<h1 class=\'title text-capitalize\'>Train: <span id=\'train-name\'>Title Here: </span> </h1>" ;
}
// once the document is loaded, assign a $.JMRI object to
// the jmri variable and overload the function train(name, state, data)
jmri = $.JMRI({
// when the JMRI websocket has completed initialization, call this
hello: function (data) {
// jmri.log("in hello: data=" + JSON.stringify(data).substr(0, 80) + "..." );
jmri.getList("trains");
},
// when the JMRI object receives an array of trains, call this
trains: function (data) {
// jmri.log("in trains: data=" + JSON.stringify(data).substr(0, 80) + "...");
data.forEach(el => {
// jmri.log("found train name:" + theTrainName + " Requesting Listener for: " + el.type +" '" + el.data.name +"'") ;
// create listener for the specific train
jmri.getObject(el.type, el.data.name)
});
if (trainListPage) {
// Using Sortable Table JS file, which is a standard download
var newTableObject = document.getElementById("trains");
sorttable.makeSortable(newTableObject);
//empty the body of the table
$("table#trains tbody").html("");
}
if (conductorPage) {
$.each(params, function (index, value) { //compare against URL params
if(index == "trainName") {
// jmri.log("param element: " +index+"="+value )
theTrainName = value ;
}
if (index == "number") {
// jmri.log("param element is number: " +index+"="+value );
theTrainNumber = value ; // train identified by number rather than name..
}
});
// jmri.log("in trains: data=" + JSON.stringify(data).substr(0, 1080) + "...");
data.forEach(el => {
if(el.data.name == theTrainNumber) {
theTrainName = el.data.userName ;
}
if(el.data.userName == theTrainName) {
// jmri.log("found train name:" + theTrainName + " Requesting Listener for: " + el.type +" '" + el.data.name +"'") ;
// create listener for the specific train
jmri.getObject(el.type, el.data.name)
// var theCars = el.data.cars;
// jmri.log("found cars:" + JSON.stringify(theCars).substr(0, 180));
el.data.cars.forEach(mycar => { ;
// jmri.log("requesting listener for car name: " + mycar.name ) ;
// create a listener for each car in the train
jmri.getObject("car", mycar.name);
})
}
// jmri.log("Not actually Requesting update listener for " + el.type +" '" + el.data.name +"'");
//jmri.getObject(el.type, el.data.name);
});
// Using Sortable Table JS file, which is a standard download
var newTableObject = document.getElementById("cars");
sorttable.makeSortable(newTableObject);
//empty the body of the table
$("table#cars tbody").html("");
var newTableObject2 = document.getElementById("pickupCars");
sorttable.makeSortable(newTableObject2);
$("table#pickupCars tbody").html("");
var newTableObject3 = document.getElementById("localCars");
sorttable.makeSortable(newTableObject3);
$("table#localCars tbody").html("");
// and check the buttons are correctly set
// jmri.log("check the radio buttons");
tickAll(false) ;
}
},
// when the JMRI object receives a train update, call this
train: function (name, data) {
// jmri.log("in train: name='" + name + "', data=" + JSON.stringify(data).substr(0, 180) + " (End of data).");
if (trainListPage) {
setRowAllTrains(name, data); // add/remove to table for display
}
if (conductorPage) {
if (data.userName == theTrainName) { // only in the train we're interested in servicing...
setTitles(name, data) ;
theCars = [];
pickupCars = [] ;
dropCars = [];
inTrainCars = [] ;
localCars = [] ;
var currentLocation = data.location ;
var currentLocationId = data.locationId ;
// jmri.log("cars list: " +JSON.stringify(data.cars).substr(0, 180) );
// clear the table contents
$("table#cars tbody").html("");
$("table#pickupCars tbody").html("");
$("table#localCars tbody").html("");
data.cars.forEach(mycar => {
// if (mycar.location.userName == currentLocation) { // car is in current location
if (mycar.location.route == currentLocationId) { // car is in current locationId, which deals with multiple stops at same place
// jmri.log("in location car: " + mycar.name + " is at: " + mycar.location.userName + " Id: " + mycar.location.route );
if(mycar.location.track) { // car exists at current destination, either local shunt, or to join train
if (mycar.destination.route == currentLocationId) { // local shunting move, tag as such !
// jmri.log(mycar.name + " local shunting move only" ) ;
localCars.push(mycar.name) ;
} else {
// it's not a local move, its a pickup
pickupCars.push(mycar.name); // car is already here, so pick it up
}
} else {
inTrainCars.push(mycar.name) ; // ought to be inTrainCars
if(mycar.destination.userName == currentLocation) {
dropCars.push(mycar.name); // drop off car here !
}
}
} else {
// car is somewhere other than current location
// jmri.log("out of location car: " + mycar.name + " is at: " + mycar.location.userName);
}
theCars.push(mycar.name); // regardless of where car might be, add it to the total list "theCars"
setRow(mycar.name, mycar); // adds the "car" to relevant tables for display
})
resortTheTables() // resorts the tables after adding all the cars
// jmri.log("theCars: " + theCars + " dropCars: " + dropCars + " pickupCars: " +pickupCars + " inTrain: " + inTrainCars + " localCars: " + localCars );
if(inTrainCars.length == 0) {
// jmri.log(" should hide inTrainCars") ;
document.getElementById("inTrainCars-div").hidden = true ; // hide Div and Table when no wagons in the train
document.getElementById("cars").hidden = true ;
} else {
document.getElementById("inTrainCars-div").hidden = false ; // show Div and Table when there area wagons in the train
document.getElementById("cars").hidden = false ;
}
if(pickupCars.length == 0) {
// jmri.log(" should hide pickupCars") ;
document.getElementById("pickupCars-div").hidden = true ; // hide Div and Table when no wagons in the train
document.getElementById("pickupCars").hidden = true ;
} else {
document.getElementById("pickupCars-div").hidden = false ; // show Div and Table when there area wagons in the train
document.getElementById("pickupCars").hidden = false ;
}
if(localCars.length == 0) {
// jmri.log(" should hide localCars") ;
document.getElementById("localCars-div").hidden = true ; // hide Div and Table when no wagons in the train
document.getElementById("localCars").hidden = true ;
} else {
document.getElementById("localCars-div").hidden = false ; // show Div and Table when there area wagons in the train
document.getElementById("localCars").hidden = false ;
}
rsCheck();
}
}
},
cars: function (data) {
// jmri.log("in cars: data=" + JSON.stringify(data).substr(0, 180) + "...");
},
car: function (name, data) {
// jmri.log("in car: name='" + name + "', data=" + JSON.stringify(data).substr(0, 180) + " (End of data).");
// jmri.log("Pickups: " + pickupCars + " InTrain: " +inTrainCars + " Full Set: " + theCars );
setRow(name, data); // add or update the row
resortTheTables()
},
// all messages call console(), so use it for debugging
console: function (originalData) {
var data = JSON.parse(originalData);
// jmri.log("in console: data=" + JSON.stringify(data).substr(0, 80) + "...");
}
});
// trigger the initial connection to the JMRI server
jmri.connect();
});
</script>
</head>
<body>
<div >
<p>
Example page with alternative "Conductor" screens for "operations". Uses (what I believe to be) more easily understood Javascript to access the underlying data within OperationsPro. It is intended for use with stock fitted with ID-Tags, such as RFID, though will work without such tags. The screens show the last reported locations for stock as it passes tag readers, which will then re-sort the display of Cars to reflect the order of tag reading. A single "page" can display either the list of trains that are built, or the conductor screen for a given train.
</p>
</div>
<div class="jumbotron">
<div class="container">
<div class="row vertical-align">
<div id="pageTitleRow" class="col-md-6">
<h1 class="title text-capitalize">Trains built and ready: </h1>
</div>
<div id="filter-text" class="col-md-6">
<!-- filter text will go here -->
</div>
</div>
</div>
</div>
<div class="container">
<div id="train-summary">
<ul>
<li id="train-progress"> Train currently at: <span class="nc-bold" id="train-location">Subtitle</span>, next stop is: <span class="nc-bold" id="train-next-location"></span></li>
<li> Train Status: <span id="train-status"></span></li>
<li> Special notes at this location: <span id="train-comment"></span></li>
</ul>
</div>
<div id="all_tables">
<div id="pickupCars-div"> Cars to pickup at stop</div>
<table id="pickupCars" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th name="th-pickUpSystemName">System Name</th><th >Type</th>
<th >WhereLastSeen</th><th name="th-pickUpWhenLastSeen">WhenLastSeen</th>
<th id="th-destination" >Destination</th><th >Track</th>
<th >Current Loc</th><th >Current Track</th>
<th class="sorttable_nosort">Pick Up</th>
</tr>
</thead>
<tbody>
<td>S</td><td>U</td><td>V</td><td>C</td> <!-- this will be cleared and replaced -->
</tbody>
</table>
<div id="inTrainCars-div"> Cars to drop-off or remaining in Train </div>
<table id="cars" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th name="th-carsSystemName">System Name</th><th >Type</th>
<th >WhereLastSeen</th><th name="th-carsWhenLastSeen">WhenLastSeen</th>
<th id="th-destination" >Destination</th><th >Track</th>
<th >Drop Off</th>
</tr>
</thead>
<tbody>
<td>S</td><td>U</td><td>V</td><td>C</td> <!-- this will be cleared and replaced -->
</tbody>
</table>
<div id="localCars-div"> Local Car Moves </div>
<table id="localCars" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th name="th-LocalCarsSystemName">System Name</th><th >Type</th>
<th >WhereLastSeen</th><th name="th-carsWhenLastSeen">WhenLastSeen</th>
<th id="th-destination" >Destination</th><th >Track</th>
<th >Current Loc</th><th >Current Track</th>
<th class="sorttable_nosort">Drop Off</th>
</tr>
</thead>
<tbody>
<td>S</td><td>U</td><td>V</td><td>C</td> <!-- this will be cleared and replaced -->
</tbody>
</table>
<form class="form-inline" role="form" id="conductor_buttons">
<div class="btn-group" id="tick-all-buttons">
<button id="check-all" type="button" class="btn btn-default" onclick="tickAll(true)"><span class="glyphicon glyphicon-check"></span></button>
<button id="clear-all" type="button" class="btn btn-default" onclick="tickAll(false)"><span class="glyphicon glyphicon-unchecked"></span></button>
</div>
<button id="move-train" type="button" class="btn btn-primary" data-statuscode="20" data-location="norham" disabled="" onClick="moveTrain()">Move to (place)</button>
</form>
<div id="trainsDiv">
All Built Trains </div>
<table id="trains" class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th name="th-trainName">System Name</th>
<th >Description</th>
<th >Departs</th>
<th >Time</th>
<th >Status</th>
<th >Currently At</th>
<th >Terminates</th>
<th >Route</th>
</tr>
</thead>
<tbody>
<td>S</td><td>U</td><td>V</td><td>C</td> <!-- this will be cleared and replaced -->
</tbody>
</table>
</div><!-- id="all_tables" -->
<p></p>
</div><!-- /container -->
</body>
</html>