Click here to Skip to main content
Click here to Skip to main content

Raspberry Pi - [Episode 3] Get Coding With Coder

, 6 Dec 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Google Coder == "A simple way to make web stuff on Raspberry Pi.", well that is what they claim!
Raspberry Pi Logo

Introduction

In my first Raspberry Pi article, I gave a quick look at the board, and showed how easy it was to load up an OS and kick the board into life, connect to the internet and browse to The CodeProject.

In this article, I will take a look at the new Google Coder environment, to see what the chocolate factory have been up to.

Google describe it as "A simple way to make web stuff on Raspberry Pi.", the question must be, how simple?

Getting Started

First things first, you need to get hold of the necessary download to perform an install of the environment onto the SD Card for booting up the RasPi. Visit http://googlecreativelab.github.io/coder/ to grab the latest version. The version at the time of writing this was Version 0.4

The instructions on the website were a bit lacking. I ended up extracting the downloaded file and using the same approach as I did in my first RasPi article i.e. Win32DiskImager. Simply point it to the image file from the extracted zip file and set the SD Card as the target and let it do its thing.

Once Win32DiskImager had done its thing, pop the SD Card back into the RasPi, hook up the screen and keyboard/mouse etc and boot it up.

Success, after much screen churning, I was eventually presented with a login prompt. Now, again the info on Coder's homepage is a bit lacking, in the end I took a stab at the default password used for the Wheezy distribution (which this appears to be based on) namely username: pi and password: raspberry

Now I have logged in, I fire up a desktop session using command startx

I was using a wireless adapter and there is a simple GUI tool on the desktop for configuring a wireless adapter, once this was done I now had the RasPi on the network.

You may also want to set up Remote Desktop if you have a need. Again, I did this in the first article, but to save you looking you can fire up a terminal and use the command sudo apt-get install xrdp

Finally, back on my normal PC, I install the Bonjour Print Services as instructed on the Coder Homepage.

Note: An alternative to this is just to look for the IP Address that is being used by the RasPi. In the screenshot above you can see by wireless adapter has been assigned to 192.168.0.28, so I can simply navigate to http://192.168.0.28/.

At this point everything should be good to go and hit the Coder environment via the browser.

Coder In The Browser

Back on my normal PC, opening up a browser window and navigating to http://coder.local/ you are presented with a security warning (in Chrome at least) due to the site SSL certificate, but as this is your own RasPi it should be safe to "proceed anyway" so that's what I did.

At this stage you now get the start of the Coder environment. You are requested to set a password to protect your Coder.

Yes there are password rules as I found out!

NOTE: When you set the password here, it also changes the default login password for username: pi

Having set the password, you now login to your Coder environment to start coding.


Here we have it the Coder "Getting Started" page.

Settings

Clicking the "Gear" at the top right of the Coder environment allows you to set the name for your Coder, add your own name set the color and also access the Wifi settings and change the password.

It does say on the Coder website that if you forget your password, you can put the SD Card into another computer and create a text file name "reset.txt" in the coder_settings directory and this will allow you to then activate a password reset next time you reboot and connect to the Coder Console in the browser.

The Demo Apps

As you can see from the screenshot above, there are 3 demo apps already available to browse the source.

The Hello World is a very simple "Hello World" page. The Eyeball app is a simple animated eyeball that follows the mouse around the screen and blinks every so often. Now, the Space Rocks app, although it runs, the graphics didn't display correctly for me, so don't really know what was going on, all I could see was a lives counter, and when I pressed the arrow key, something must of moved because I died.

I thought this was a tad bizarre, so out of curiosity I fired up an IE10 session and pointed at the coder. This time I could happily see and play the Space Rocks app. It turns out to be an Asteroids clone.

Extremely disappointing that Google Chrome (29.0.1547.66) fails to play the game and IE10 succeeds. That will be Chrome 0 - 1 Internet Explorer. I thought that there is no way that Google team would have allowed this to be the case, so suspected something was up with my machine. Whether it was down to the machine being flat out folding on the CPU & GPU or just having a bad day. Having rebooted the machine and trying again, all was back in order and the Space Rocks now worked in Chrome and peace in the universe was restored.

If you at the top right of the screenshot above, you will notice there is a "CODER", "</>" and "HACK", these allow direct access back to the main Coder start page, the code behind for editing the application and a quick hack page,

The Editor Page

  • A - Returns back to the Coder environment start page
  • B - Click the app title to return to the running application
  • C - Switch between the various views of the code elements
  • D - Media manager, use this to upload artwork, audio etc.
  • E - Preview, switch the editor into a side by side preview of the code and application
  • F - Application settings, set the application title, colour, author.

The Coder Apps Environment

It is very much a modern web application environment using standard HTML, CSS and Javascript. For the server side there is also Node.js.

Given that this is basically a cut down linux box, I dare say you could if you wanted put anything you want onto it. Maybe we will put that to the test at a later date, concentrate on the basics first.

The code editor used by the Coder environment is built on Ace.js.

Node.js Modules

The node modules are located in the /home/coder/coder-dist/coder-base/node_modules and had to use the File Manager to poke around to see what is included.

The Node.js libraries that are provided are;

There is also a node package manager, NPM that can be used to extend the functionality of Node. This is performed from the command line, but is not scope for this article and you can do a bit of Googling if you really want to know more.

Shall We Build An App?

Of course we will, that is why we are here after all! How about something simple to start with. How about a simple Todo List application.

The code used in the various sections can be found in the following download and you can copy and paste this the content of each file into the relevant tab on the Coder editor;

The first thing we must do it create the application. From the Coder environment start page, click the green tile with the plus symbol. Next, set the colour and the title for the application and then click "Create". In this example I am creating "Daves Todo".

So what will the App have to be able to do. I think you can guess, but to summarise the functionality;

  • Display a list of items.
  • Add items to the list.
  • Delete items from the list.
  • Delete All items from the list in a single action.

Here is what we will end up with:

At the top there are some buttons to before the actions, the list of items todo complete with a link to remove a single item and the data entry form (which actually shows/hides and is not always visible). Quite a simple start, but everyone has to start somewhere!

At the bottom of the screenshot you can see Chrome's developer environment, this is a must when trying to debug what is going on and what is being passed around to and from the RasPi and Node.

HTML Markup

The code below highlights what is present on the HTML tab of the editor. The code in the HEAD element is the generic template created by Coder. I have only added the main body;

<!DOCTYPE html>
<html>
<head> 
    <title>Coder</title>
    <meta charset="utf-8">
    <!-- Standard Coder Includes -->
    <script>
        var appname = "{{app_name}}"; //app name (id) of this app
        var appurl = "{{&app_url}}";
        var staticurl = "{{&static_url}}"; //base path to your static files /static/apps/yourapp
    </script>
    <link href="/static/apps/coderlib/css/index.css" media="screen" rel="stylesheet" type="text/css"/>
    <script src="/static/common/js/jquery.min.js"></script>
    <script src="/static/common/ace-min/ace.js" type="text/javascript" charset="utf-8"></script>
    <script src="/static/apps/coderlib/js/index.js"></script>
    <script>
        Coder.addBasicNav();
    </script>
    <!-- End Coder Includes -->

    <!-- This app's includes -->
    <link href="{{&static_url}}/css/index.css" media="screen" rel="stylesheet" type="text/css"/>
    <script src="{{&static_url}}/js/index.js"></script>
    <!-- End apps includes -->
</head>
<body class="">
    <div class="pagecontent">
        <h1>Hi Dave, here are your things todo!</h1>
        <div id="refresh" class="button">Refresh</div><div id="addItem" class="button">Add Item</div><div id="deleteAll" class="button">Delete All</div>
        <div id="todoListContainer">
            <div id="refreshTitle"><i>Loading items...</i></div>
            <table id="itemList" class="CSSTableGenerator">
            <tr><th>Description</th><th>Priority</th></tr>
            </table>
            <div id="noItems">You have no items todo!</div>
        </div>
        <div id="todoForm">
            <form id="entryForm">
                <fieldset>
                <legend id="formTitle">Add a new item</legend>
                <input type="hidden" name="item" value=""/>
                <table>
                <tr><th>Item description</th><th>Priority</th></tr>
                <tr><td><input type="text" id="description" name="description" placeholder="Add a new item here!"/></td>
                    <td><input type="number" id="priority" name="priority" min="1" max="5" value="1"/></td>
                </tr>
                <tr><td><input type="submit" id="submit" title="Add Item" class="button"/></td>
                    <td><input type="button" id="cancel" title="Cancel" value="Cancel" class="button"/></td>
                </tr>
                </table>
                </fieldset>
            </form>
        </div>
    </div>
</body>
</html>

The CSS

Using a selection of web based generators (see the CSS references section at the end) I add the CSS markup for the styling. The only original CSS was the .pagecontent block.
.pagecontent {
    padding: 24px;
}

.button {
    -moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
    -webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
    box-shadow:inset 0px 1px 0px 0px #ffffff;
    background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ededed), color-stop(1, #dfdfdf) );
    background:-moz-linear-gradient( center top, #ededed 5%, #dfdfdf 100% );
    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#dfdfdf');
    background-color:#ededed;
    -webkit-border-top-left-radius:6px;
    -moz-border-radius-topleft:6px;
    border-top-left-radius:6px;
    -webkit-border-top-right-radius:6px;
    -moz-border-radius-topright:6px;
    border-top-right-radius:6px;
    -webkit-border-bottom-right-radius:6px;
    -moz-border-radius-bottomright:6px;
    border-bottom-right-radius:6px;
    -webkit-border-bottom-left-radius:6px;
    -moz-border-radius-bottomleft:6px;
    border-bottom-left-radius:6px;
    text-indent:0;
    border:1px solid #dcdcdc;
    display:inline-block;
    color:#777777;
    font-family:arial;
    font-size:15px;
    font-weight:bold;
    font-style:normal;
    height:50px;
    line-height:50px;
    width:100px;
    text-decoration:none;
    text-align:center;
    text-shadow:1px 1px 0px #ffffff;
}
.button:hover {
    background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #dfdfdf), color-stop(1, #ededed) );
    background:-moz-linear-gradient( center top, #dfdfdf 5%, #ededed 100% );
    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#dfdfdf', endColorstr='#ededed');
    background-color:#dfdfdf;
}.button:active {
    position:relative;
    top:1px;
}

label
{
width: 4em;
float: left;
text-align: right;
margin-right: 0.5em;
display: block
}

.submit input
{
margin-left: 4.5em;
}
input
{
color: #781351;
background: #fee3ad;
border: 1px solid #781351
}

.submit input
{
color: #000;
background: #ffa20f;
border: 2px outset #d7b9c9
}
fieldset
{
border: 1px solid #781351;
width: 20em
}

legend
{
color: #fff;
background: #ffa20c;
border: 1px solid #781351;
padding: 2px 6px
}

.CSSTableGenerator {
    margin:0px;padding:0px;
    width:100%;
    box-shadow: 10px 10px 5px #888888;
    border:1px solid #000000;

    -moz-border-radius-bottomleft:0px;
    -webkit-border-bottom-left-radius:0px;
    border-bottom-left-radius:0px;

    -moz-border-radius-bottomright:0px;
    -webkit-border-bottom-right-radius:0px;
    border-bottom-right-radius:0px;

    -moz-border-radius-topright:0px;
    -webkit-border-top-right-radius:0px;
    border-top-right-radius:0px;

    -moz-border-radius-topleft:0px;
    -webkit-border-top-left-radius:0px;
    border-top-left-radius:0px;
}.CSSTableGenerator table{
    width:100%;
    height:100%;
    margin:0px;padding:0px;
}.CSSTableGenerator tr:last-child td:last-child {
    -moz-border-radius-bottomright:0px;
    -webkit-border-bottom-right-radius:0px;
    border-bottom-right-radius:0px;
}
.CSSTableGenerator table tr:first-child td:first-child {
    -moz-border-radius-topleft:0px;
    -webkit-border-top-left-radius:0px;
    border-top-left-radius:0px;
}
.CSSTableGenerator table tr:first-child td:last-child {
    -moz-border-radius-topright:0px;
    -webkit-border-top-right-radius:0px;
    border-top-right-radius:0px;
}.CSSTableGenerator tr:last-child td:first-child{
    -moz-border-radius-bottomleft:0px;
    -webkit-border-bottom-left-radius:0px;
    border-bottom-left-radius:0px;
}.CSSTableGenerator tr:hover td{

}
.CSSTableGenerator tr:nth-child(odd){ background-color:#e5e5e5; }
.CSSTableGenerator tr:nth-child(even)    { background-color:#ffffff; }.CSSTableGenerator td{
    vertical-align:middle;


    border:1px solid #000000;
    border-width:0px 1px 1px 0px;
    text-align:left;
    padding:7px;
    font-size:10px;
    font-family:Arial;
    font-weight:normal;
    color:#000000;
}.CSSTableGenerator tr:last-child td{
    border-width:0px 1px 0px 0px;
}.CSSTableGenerator tr td:last-child{
    border-width:0px 0px 1px 0px;
}.CSSTableGenerator tr:last-child td:last-child{
    border-width:0px 0px 0px 0px;
}
.CSSTableGenerator tr:first-child td{
        background:-o-linear-gradient(bottom, #cccccc 5%, #b2b2b2 100%);    background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #cccccc), color-stop(1, #b2b2b2) );
    background:-moz-linear-gradient( center top, #cccccc 5%, #b2b2b2 100% );
    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#cccccc", endColorstr="#b2b2b2");  background: -o-linear-gradient(top,#cccccc,b2b2b2);

    background-color:#cccccc;
    border:0px solid #000000;
    text-align:center;
    border-width:0px 0px 1px 1px;
    font-size:14px;
    font-family:Arial;
    font-weight:bold;
    color:#000000;
}
.CSSTableGenerator tr:first-child:hover td{
    background:-o-linear-gradient(bottom, #cccccc 5%, #b2b2b2 100%);    background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #cccccc), color-stop(1, #b2b2b2) );
    background:-moz-linear-gradient( center top, #cccccc 5%, #b2b2b2 100% );
    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#cccccc", endColorstr="#b2b2b2");  background: -o-linear-gradient(top,#cccccc,b2b2b2);

    background-color:#cccccc;
}
.CSSTableGenerator tr:first-child td:first-child{
    border-width:0px 0px 1px 0px;
}
.CSSTableGenerator tr:first-child td:last-child{
    border-width:0px 0px 1px 1px;
}

The JavaScript

This is all new to the project. This isn't a lesson in JS, so I do not plan on detailing the specifics, but the general outline is as follows;

var items = [];

function item(description, priority) {
    this.Description = description;
    this.Priority = priority;
}

The items array holds a local copy of the items returned from the server. They are parsed out of JSON by the the loadItems method. The item function is a general ToDo item object that we use to create new items for pushing onto the items array.

$(document).ready( function() {
    
    //Disable jQuery ajax caching!
    $.ajaxSetup({
        cache: false
    });
    
    $('#todoForm').hide();
    
    //Attach the actions
    $("#refresh").click(function () {
        console.log("Refresh clicked.");
        loadItems(); 
    });
   
    $("#addItem").click(function () {
        console.log("Add Item clicked.");
        $('#formTitle').text("Add Todo Item");
        $('#todoForm').show(); 
    });
    

    $("#deleteAll").click(function () {
        console.log("Delete All clicked.");
        deleteAll();
        $('#todoForm').hide();
    });
    
    $("#entryForm").submit(function (event) {
        console.log("Form submitted.");
        event.preventDefault();
        
        $.ajax({
            type: "POST",
            url: "/app/daves_todo/addItem",
            data: $("#entryForm").serialize(),
            dataType: "string",
            success: function(){ 
                loadItems();
            }
        });
    
        $('#todoForm').hide();
        
        //Let us wait a wee bit to allow the raspi to catch up, it is a bit slow!
        //then we will load the items.
        setTimeout (function () { loadItems()},500);
    });
    
    $("#cancel").click(function () {
       $('#todoForm').hide(); 
    });
    
    //Load the items, after waiting for 500ms
    setTimeout(function () { loadItems()},500);
});

When the document is loaded, jQuery binds up the events to the relevant objects as well as assign some of the functions that the events will perform. Raspberry Pi is a bit slow, so I will use a cheeky timeout function to delay todo item loading following performing an action. Yes, there are better ways of doing this, but this is just one of those 'hacks' used for the demo.

Next up, the code used for loading the items from the server. There is a jQuery get JSON call which then parses out the items and pops them into the local array. From here we iterate the array and build the table to display the items and also setup the link to delete the individual items.

function loadItems() {
    //Retrieve a list of items from the server
    items = [];
    $("#noItems").hide();
    $("#refreshTitle").show();
    
    //Clear the table contents
    $("#itemList").html("");
    
    console.log("Begin ajax calls to server.");
    $.getJSON( "/app/daves_todo/getItems", function( data ) {
        $.each( data, function( key, val ) {
                        var newItem = new item(val.Description, val.Priority);
                        items.push(newItem);
                        });
 
        if (items.length === 0)
            {
                $("#noItems").show();
            }
        else
        {
            //Add the table header row
            $("#itemList").append("<tr><th>Description</th><th>Priority</th><th>Actions</th>");
            
            for (var index =0; index < items.length; index++)
            {
                var newRow = "<tr><td>" + items[index].Description + "</td><td>" + items[index].Priority + "</td><td><a href=/app/daves_todo/deleteItem?id=" + index.toString() + ">Remove</a></td></tr>";
                $("#itemList").append(newRow);
            }
        }
    })
    .fail(loadFail)
    .always(console.log("Completed ajax load method")); 
    
    $("#refreshTitle").hide();
}

function loadFail() {
    console.log("Error loading items from server.");
    alert("Error loading items from server.");   
} 

Lastly, there is the deleteAll method, this sends a jQuery POST to make the delete request, before using another little delay before reloading the table.

function deleteAll() {
 console.log("Begin ajax delete all call to server.");
    $.ajax({
            type: "POST",
            async: false,
            url: "/app/daves_todo/deleteAll",
            success: function(){
                //Yes, another delay to allow raspi to play catch up.
                setTimeout(function() { loadItems()}, 500);
            }
        });
}

The Node.js Backend JavaScript

exports.settings={};
//These are dynamically updated by the runtime
//settings.appname - the app id (folder) where your app is installed
//settings.viewpath - prefix to where your view html files are located
//settings.staticurl - base url path to static assets /static/apps/appname
//settings.appurl - base url path to this app /app/appname
//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
//settings.coder_owner - name of the user, Ie. "Suzie Q."
//settings.coder_color - hex css color given to this coder.

The settings section was already in place and no changes were made for this demo.

The next two sections define the "GET" and "POST" route handlers.

exports.get_routes = [
    { path:'/', handler:'index_handler' },
    { path:'/getItems', handler: 'getItems'},
    { path:'/deleteItem', handler: 'deleteItem'},
];

exports.post_routes = [
    { path:'/deleteAll', handler: 'deleteAllItems'},
    { path:'/addItem', handler: 'addItem'},
];

The next method is the default index page handler. I have not made changes to this and as you can see it renders up the default index template file.

exports.index_handler = function( req, res ) {
    var tmplvars = {};
    tmplvars['static_url'] = exports.settings.staticurl;
    tmplvars['app_name'] = exports.settings.appname;
    tmplvars['app_url'] = exports.settings.appurl;
    tmplvars['device_name'] = exports.settings.device_name;

    res.render( exports.settings.viewpath + '/index', tmplvars );
};

exports.on_destroy = function() {
};

The on_destroy function is another method that was pre-defined, this is used to do any clean up etc. if required.

The following code is used to create the item store. It is an in memory array. It is pre-populated with a new todo item when the application first runs. Similar to the client side javascript, there is a function used for creating the new todo objects for pushing onto the array.

var items = [];
var newItem = new item("Your first thing to do is try this!",1);
items.push(newItem);

function item(description, priority) {
    this.Description = description;
    this.Priority = priority;
}

The getItems method is called by the client and simply creates a JSON string of the items array. The addItem parses out the form data from the post, creates the new item and pushes it onto the array. The deleteItem picks out the index of the item to delete from the query string (remember we are using an index at each side so they should always align, then splices the array to delete the item. Finally, the deleteAllItems does what it says on the tin and simply creates a new empty array to replace all existing items.

exports.getItems = function(req, res) {
    res.writeHead(200, {"Content-Type": "application/json"});
    res.write(
        JSON.stringify(items)
    );
    res.end();
};

exports.addItem = function(req, res) {
    var newItem = new item(req.param('description'),req.param('priority'));
    items.push(newItem);

    res.writeHead(200, {"Content-Type": "application/text"});
    res.write("Item added.");
    res.end();
};

exports.deleteItem = function(req, res){
  //Delete a single item referenced by array index key
    var id = req.param('id');
    items.splice(id,1);

    res.redirect("/app/daves_todo/");
    res.end();
};

exports.deleteAllItems = function(req, res) {
    //Remove all items
    items = [];

    res.writeHead(200, {"Content-Type": "application/text"});
    res.write("Items deleted.");
    res.end();
};

Be-aware - Lots of improvements required!

There are a whole host of bad practices in this demo. There are things like forgery, playback, injection etc. all present which you would not want to have in production code.

Other things that would obviously need to be considered are things like persistent storage to a database. Automatic mechanisms to update the UI on datastore changes, removal of client side array etc.

Conclusions

There is a whole lot of documentation missing. Trying to work out what actually is present, what libraries are already loaded by default, how the whole multi-app environment within Coder was doing its thing etc.

Trying to get my head round Node.js for the first time wasn't easy. Well, not in this limited environment anyway. Had I started to look at maybe pushing server console messages etc. back to the client then things might have become easier, but that would kind of defeated the "out of the box" experience being presented.

As a basic get you going with HTML and client side things yes, it might be sufficient. However, I would say that I found the Raspberry Pi tended to die on occasions, needed a reboot to recover. The terminal window etc. would remain responsive, but the Coder environment via the browser was just a no-go. The old "save often" mantra does apply here.

There is no doubt that this has potential, hopefully as the development team rev up the environment it will become a more rounded experience.

Me on the other hand, I think I am broken for the time being, I spent days trying to parse out a jQuery JSON post to Node, but in the end gave up and reverted to traditional query string posts.

The Future?

Maybe I will get round to looking at integrating Mongodb with this, maybe I will look into Node in more detail, maybe I just need a sleep...

Hopefully more articles will start to appear as the more proficient RasPi users and Node experts throw in to the mix their experience. So, if you have a RasPi go and download Coder, take it for a spin and see what you make of it!

Most of all though, have fun and just keep going!

References

CSS References

To take the hassle out of crafting some of the CSS for the page, I made use of examples and generators already available on the net. Takes some of the pain out of demo making!

History

  • 6th December 2013 - Removed invalid references from code block that this website had added
  • 26th September 2013 - Added download to demo app section with the relevant code.
  • 26th September 2013 - Added amendment regarding connecting via IP Address instead of coder.local.
  • 25th September 2013 - Updated with narrative revisions, inline code markup.
  • 24th September 2013 - First Release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DaveAuld
Engineer
Scotland Scotland
I have been working in the Oil & Gas Industry for over 25 years now.
 
Core Discipline is Instrumentation and Control Systems.
 
Completed Bsc Honours Degree (B29 in Computing) with the Open University in 2012.
 
Currently, Offshore Installation Manager for the Beryl Bravo platform, which is located ~180 miles NE of Aberdeen, Scotland in the Northern North Sea.
Formely on the Forties Charlie platform, which is located ~110Miles NE of Aberdeen.
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionMy 5* PinmemberMas1131-Oct-13 3:14 
GeneralEpisode 2 - Where is it you may ask? [modified] PinprotectorDaveAuld26-Sep-13 20:47 
QuestionSlice of Awesome Pi! PinmemberH.Brydon26-Sep-13 19:10 
QuestionErrors! PinmemberMember 1030010726-Sep-13 8:56 
AnswerRe: Errors! PinprotectorDaveAuld26-Sep-13 10:47 
AnswerRe: Errors! PinprotectorDaveAuld26-Sep-13 11:09 
GeneralRe: Errors! PinmemberMember 1030010726-Sep-13 13:08 
GeneralRe: Errors! PinprotectorDaveAuld26-Sep-13 20:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 6 Dec 2013
Article Copyright 2013 by DaveAuld
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid