Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Create a CRUD web app using JQuery Mobile & PHP Ajax Calls

4.92/5 (8 votes)
21 Mar 2015CPOL24 min read 52.1K   2K  
Demonstrates creating, updating, deleting single file json records in a web server using jQuery Mobile and Php.

Introduction

Assumptions: You want to create a multi-user mobile web app that's going to be accessible anywhere on the globe. You want this app to be fast in terms of creating, updating and deleting records. You want all your records to be persisted on a server and easily accessible whenever anyone logs in with whatever device they are using. This app should run directly from the web, however can be easily ported to a hybrid app.

My previous article, spoke a lot about creating  crud web applications using JQuery Mobile. A lot has taken place eversince then and whilst I am yet to have a similar article using WebSQL and IndexedDB, I wanted to be able to create something that anyone could use, irrespective of device and the information will be available anywhere. Whilst this can be done using any backend database like MySQL, MongoDB etc, I opted for the single file records approach. 

What does this mean? This means that each record on the server is stored as a single json file.

When a user reads a record from the displayed record, php is used to read the file record from the server and then displayed. This is performed using an Ajax call. There are pros and cons in relation to this approach, however for small databases that you might want to create, its a perfect vehicle. However, the speed of your server and your client computer are of utmost importance in the performance of this approach.

Some silly assumptions: You are familiar with JQuery Mobile, you also know some JavaScript and are able to make Ajax calls, you know how to set up a web server and file permissions. You are also familiar with JSON.

Download MyProjects.zip 

You can unzip and open Folder As Site with MS WebMatrix::: or any other web server that has PHP installed. A live demo is available here: http://www.mbangas.com/jqmshow/myprojects/

Background

We are going a Project Tracker Mobile application with just two models, Projects and People. The People model will store details about the people resources in our projects and the Projects will store simple details of our projects. Because people should have controlled access to the application, we will add authentication for this app.

Lets call this app MyProjects.

Each project will have the following fields:

1. Project Name - unique, compulsory, text

2. Status - compulsory, dropdown list, this could be Pending, Approved, On Hold etc

3. Priority - compulsory, radio button, this could be low, medium, high

4. Due date - compulsoty, date picker

5. Percent Complete - slider to indicate percentage complete

5. Owner - dropdown list derived from People file

6. Notes - text area

This is translated into this UI here as depicted with Figure 1.

Figure 1: Project Screen

Image 1

Pressing Cancel will take a user to the Projects Listing Screen and Pressing Save, will save the project record as a single json file on the web server Project folder. The file name being the Project Name e.g. Write-CodeProject-Article.json as depicted in Figure 1.1. below

Figure 1.1. : Project JSON file record.

Image 2

Each person will have the following fields

1. Full Name - compulsory, text

2. Email Address - compulsory, email

3. Reports to - compulsory, text, the person this person will report to for governance purposes.

This is translated to be Figure 2 below.

Figure 2: People Screen

Image 3

and the Person record stored as the following JSON file record.

Figure 2.1 Person JSON File

Image 4

From the above image, one can see from the folder structure how the records are organized, and the various php files that make this possible. I will show how this is all glued together with the code below.

Each user will have the following fields

1. First Name - compulsory, text

2. Last Name - compulsory, text

3, Email Address - comulsory, email

4. Password - compulsory, unique, password

5. User Role - compulsory, dropdown for Administrator/User

6. Active - compulsory, checkbox

This is translated as Figure 3 below

Figure 3: User screen

Image 5

with the resulting JSON record being as per Figure 3.1 below

Figure 3.1: User json file record

Image 6

You will note that the password stored here is encrypted to hide it from the normal eye. My article here discussed how to encrypt and decrypt passwords using the Stanford Crypto Library.

For this approach, three php files have been created for each model.

1. Project - ajaxSaveProject.php, ajaxDeleteProject.php and ajaxGetProject.php

2. Person - ajaxSavePerson.php, ajaxDeletePerson.php and ajaxGetPerson.php

3. User - ajaxSaveUser.php, ajaxDeleteUser.php and ajaxGetPerson.php

Explaining the php files

ajaxSave...php - this file gets a passed json string and save it to the web server using the primary key of the file as a file name.

ajaxDelete...php - this file gets a passed primary key and deletes the json file from the server

ajaxGet...php - this loops through all available records in that folder and returns a \n delimited string of each of the files names. These are parsed to get the details of each file and the results stored as json objects.

NB: A folder is created on the folder for each model e.g. all users will be stored under a User folder on the server.

Using the code

In my article, Create CRUD web app using JQuery Mobile and LocalStorage I explained in detail how to create a crud web app and one can refer to that for more details. As CRUD methods are basically the same for each model, I will deeply delve on 1 model here, the Project Model, as both the User and Person model follows the same methodolody in Adding, Updating and Deleting Records.

My other article named, Enhancing MyFamily.Show JQuery Mobile Hybrid App, spoke in detail about side panels, adding background images to forms, d3 for tree drawing, creating tables and exporting to excel, etc and the one named Write Once, Run Everywhere: The Comic Books Collection Hybrid, spoke about creating tables (for reporting), relational comboboxes, export to excel etc, thus this article will not focus on that.

In finality, my article named, Adding Security to our NoteKeeper JQuery Mobile App, discussed how one can create login and user screens for their mobile app so that users can be authenticated.

The Logic behind MyProjects

MyProjects is a web app that will store and retrieve details of our projects. Users of MyProjects should be authenticated and MyProjects should be accessible from the internet/web. There is a governance structure of the project hierachy here and we need to also keep that in mind. Each project has an owner allocated to it.

As MyProjects will run from a webserver, PHP should be installed on the server and permissions should be set for the folder that will host MyProjects projects, users and people.

Starting MyProjects

An end user is provided with a link of the website where myprojects is hosted, for example, mine is here as a demonstration. As soon as the user opens the link, a welcome screen is shown. If a user does not have an account, they can Sign Up. As this is a live application, the process of user sign ups should be properly controlled but due to a demonstration here, a loose sign up process has been created.

Figure 4: Sign In

Image 7

The sign in screen above allows a user to sign in to use the application and create, update and delete project, persons, users details. As authorization has not been added here, the process of CRUD operations per model  should be tightly controlled for data centric secure web apps.

The Sign In screen is defined with the html below. All view definitions are stored within the index.html file as per attachment above.

HTML
<div data-theme="a" id="pgSignIn" data-role="page">
        <header id="pgSignInHdr" data-role="header" data-position="fixed">
            <h1>MyProjects</h1>
        </header>
        <div id="pgSignIncontent" data-role="content">
            <h3>Welcome</h3><form action="#" method="post" id="pgSignInForm" name="pgSignInForm">
                <p>Existing Users</p><div data-role="fieldcontain">
                    <label for="pgSignInEmail" id="lblpgSignInEmail">Email Address<span style='color:red;'>*</span></label>
                    <input required title="Enter email address here." type="email" name="pgSignInEmail" id="pgSignInEmail" placeholder="Enter email address here." autocomplete="off" data-clear-btn="true"></input>
                </div>
                <div data-role="fieldcontain">
                    <label for="pgSignInPassword" id="lblpgSignInPassword">Password<span style='color:red;'>*</span></label>
                    <input required autocomplete="off" title="Enter password here." type="password" name="pgSignInPassword" id="pgSignInPassword" placeholder="Enter password here." data-clear-btn="true"></input>
                </div>
                <div><button type="submit" id="pgSignInIn" class="ui-btn ui-corner-all ui-shadow ui-btn-b">Sign In</button>
                </div>
                <p>Don't have an account</p><div><button id="pgAddUserUp" class="ui-btn ui-corner-all ui-shadow">Sign Up</button>
                </div>
            </form>
        </div>
        <footer id="pgSignInFtr" data-role="footer" data-position="fixed">
            <h1>Powered by JQM.Show © Anele Mbanga 2015</h1>
        </footer>
    </div>

What really happens when one clicks Sign In? This reads a user record from the webserver.

The code behind this button works like this. The user email address and password are read from what has been input on the screen using JQuery syntax. Then a php call to ajaxGetUser is made to read a file from the web server that is made up of the email address, thus for me, this file will be anele@mbangas.com.json. If the file exists, the password entered is compared to the password that is stored on the file, if these match, the first check is fine. The second check is whether the account is active or not. If the account is not active the second check fails and the end user gets warned. If all goes well, the user is taken to the main springboard of the application.

The source code behind Sign In.

All the source code for the app is stored in the app.js file.

JavaScript
// bind the sign in click event
            $('#pgSignInIn').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                // verify the user details
                app.SignInUser(
                $('#pgSignInEmail').val().trim(),
                $('#pgSignInPassword').val().trim()
                );
            });

As you have noted above, clicking the button calls app.SignInUser with the email address and password as entered by the user. That source is here.

JavaScript
app.SignInUser = function (Email,Password) {
            // get users
            $('#pgSignIn').data('success', 'true');
            var uname = Email;
            Email = Email.replace(/ /g, '-');
            Email += '.json';
            var req = Ajax("ajaxGetUser.php?file=" + encodeURIComponent(Email));
            if (req.status == 200) {
                // parse string to json object
                var userRec = JSON.parse(req.responseText);
                // verify password and status of account
                var pwd = userRec.Password;
                // decript the password
                pwd = sjcl.decrypt('MashJQMShow', pwd);
                var atv = userRec.Active;
                if (Password != pwd) {
                    $('#pgSignIn').data('success', 'false');
                    uname = uname.replace(/-/g, ' ');
                    $('#alertboxheader h1').text('Password Error');
                    $('#alertboxtitle').text(uname);
                    $('#alertboxprompt').text('The password specified is incorrect!');
                    $('#alertboxok').data('topage', 'pgSignIn');
                    uname = uname.replace(/ /g, '-');
                    $('#alertboxok').data('id', uname);
                    $.mobile.changePage('#alertbox', {transition: 'pop'});
                }
                if (atv == false) {
                    $('#pgSignIn').data('success', 'false');
                    uname = uname.replace(/-/g, ' ');
                    $('#alertboxheader h1').text('Account Error');
                    $('#alertboxtitle').text(uname);
                    $('#alertboxprompt').text('This account is no longer active. Contact your System Administrator!');
                    $('#alertboxok').data('topage', 'pgSignIn');
                    uname = uname.replace(/ /g, '-');
                    $('#alertboxok').data('id', uname);
                    $.mobile.changePage('#alertbox', {transition: 'pop'});
                }
                } else {
                //user file is not found
                $('#pgSignIn').data('success', 'false');
                uname = uname.replace(/-/g, ' ');
                $('#alertboxheader h1').text('User Error');
                $('#alertboxtitle').text(uname);
                $('#alertboxprompt').text('This user is NOT registered in this App!');
                $('#alertboxok').data('topage', 'pgSignIn');
                uname = uname.replace(/ /g, '-');
                $('#alertboxok').data('id', uname);
                $.mobile.changePage('#alertbox', {transition: 'pop'});
            }
            //find if status is successful or not
            var succ = $('#pgSignIn').data('success');
            if (succ == 'true') {
                pgSignInClear();
                // show the page to display after sign in
                $.mobile.changePage('#pgMenu', {transition: pgtransition});
            }
        };

app.SignInUser calls the ajaxGetUser.php file which basically reads the user file using the email from the web server and returns a json string of the file. This is then parsed for easy reading into an {} object.

PHP
<?php
    //get the file contents from the server
    If (isset($_REQUEST['file'])) {
        $file = basename($_REQUEST['file']);
        echo file_get_contents('./User/'.$file);
        } Else {
        If (is_dir('./User') && $handle = opendir('./User/')) {
            While (False !== ($entry = readdir($handle))) {
                If (!is_dir($entry)) {
                    echo basename($entry)."\n";
                }
            }
            closedir($handle);
            } Else {
            header("HTTP/1.0 404 Not Found");
        }
    }

?>

The file variable is passed to the ajax call with..

JavaScript
var req = Ajax("ajaxGetUser.php?file=" + encodeURIComponent(Email));

as indicated in app.SignInUser method. All operations to read files from the webserver using a get method will follow the same approach. The main determinant is the folder the content is being read from. In this particular case its the User folder.

What happens when Sign Up is clicked? This facilitates the creation  of a record of a user on the webserver.

When a user clicks Sign Up, he/she is taken to the add user screen as depicted in Figure 3 above. When all the user details are entered, the user then clicks Save.

What happens when Save is clicked? A new user is being added to the webserver, creating a new record.

As soon as the user has entered their details and click Save, the following method is called to save the user details to the web server.

JavaScript
$('#pgAddUserSave').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                // save the User
                var UserRec;
                //get form contents into an object
                UserRec = pgAddUserGetRec();
                //save object to JSON
                app.addUser(UserRec);
            });

The details entered by the user are read from the screen and stored in UserRec, this object is then passed to app.addUser to save it to the server. Lets look at pgAddUserGetRec() that gets the entered screen details.

JavaScript
function pgAddUserGetRec() {
            //define the new record
            var UserRec
            UserRec = {};
            UserRec.FirstName = $('#pgAddUserFirstName').val().trim();
            UserRec.LastName = $('#pgAddUserLastName').val().trim();
            UserRec.Email = $('#pgAddUserEmail').val().trim();
            UserRec.Password = $('#pgAddUserPassword').val().trim();
            UserRec.Password = sjcl.encrypt('MashJQMShow', UserRec.Password);
            UserRec.ConfirmPassword = $('#pgAddUserConfirmPassword').val().trim();
            UserRec.ConfirmPassword = sjcl.encrypt('MashJQMShow', UserRec.ConfirmPassword);
            UserRec.UserRole = $('#pgAddUserUserRole').val().trim();
            UserRec.Active = $('#pgAddUserActive').prop('checked');
            return UserRec;
        }

The script above uses JQuery syntax to read the details from the screen and save them as an object. This object gets passed to app.addUser and this basically does this.

JavaScript
// add a new record to server storage.
        app.addUser = function (UserRec) {
            // define a record object to store the current details
            var Email = UserRec.Email;
            // cleanse the record key of spaces.
            Email = Email.replace(/ /g, '-');
            UserRec.Email = Email;
            //convert record to json to write to server
            var recordJSON = JSON.stringify(UserRec);
            // save the data to a server file, use the post method as it has 8MB minimum data limitation
            var req = Ajax("ajaxSaveUser.php", "POST" , recordJSON);
            if (req.status == 200) {
                //show a toast message that the record has been saved
                toastr.success('User record saved.', 'MyProjects');
                //find which page are we coming from, if from sign in go back to it
                var pgFrom = $('#pgAddUser').data('from');
                switch (pgFrom) {
                    case "pgSignIn":
                    $.mobile.changePage('#pgSignIn', {transition: pgtransition});
                    break;
                    default:
                    // clear the edit page form fields
                    pgAddUserClear();
                    //stay in the same page to add more records
                }
                } else {
                //show a toast message that the record has not been saved
                toastr.error('User record not saved. Please try again.', 'MyProjects');
            }
        };

The email address is the primary key for each user and it gets cleaned for empty spaces here. The passed user object read from the screen is then JSON stringified to convert it into a string. Then an ajax call to ajaxSaveUser.php is made posting the json string to it to save it into the web server. If the user is successfully saved, a toast is shown telling the user the record was saved. This is checked with the status message of 200 that gets returned by the ajax call. As the user in this case accessed this screen from the sign in screen, as soon as tehy save their details, they will be taken back to the Sign In page to sign in.

This Add User screen as depicted in Figure 3 has a panel and also a menu button on the header but these are hidden because the user in this instance is Signing Up.

The ajaxSaveUser.php file that saves the user details to the web server.

JavaScript
<?php
    // Get the data from the client.
    $record = file_get_contents('php://input');
    // convert file contents to json object
    $jsonrec = json_decode($record);
    // read the primary key
    $Email = $jsonrec->Email;
    //write the data out to a file on the server
    //make sure permissions are all OK!
    //create the parent folder
    if (!is_dir('./User/')) {
        mkdir('./User/');
    }
    //define the file
    $jsonFile = "User/" . $Email . ".json";
    $f = fopen($jsonFile, 'w') or die("Error: Can't open file. Got write permission?");
    fwrite($f, $record);
    fclose($f);
?>

 

Remember, a stringified json object is posted to the web server when this method is called. The contents of that string are read and saved as $record. This is then decoded with json_decode to make it an easily accessible array so that we can get the email address, which is the primary key to the user record. We then read the email address to a varible $email by executing ->Email in our decoded user record.

During the first run, the User directory might not exist, so we then check if that folder exists, if not, its created. We then define the complete file name for this record by defining the jsonFile as

JavaScript
$jsonFile = "User/" . $Email . ".json";

Remember, the email address read does not have a .json extension, thus we add it here. The file is then opened and the contents of our user record written as is (as a string) to the file.

If you have been following closely, I have already demonstrated CR of our CRUD application. We read a user details for sign in and also created a user with Sign Up already. This is the same approach that is followed throughout the application to CRUD operations that are herein. We will now demonstrate Updating Users and deleting users from the database so that the UD portion of our CRUD operation is completed. After that we will talk about MyProjects some more.

From the springboard that appears after a succesful sign in as depicted in Figure 5, select Users. This will list all available Users in MyProjects.

Figure 5: Springboard (creating similar dashboards was discussed in Enhancing MyFamily.Show article as referenced above)

Image 8

Figure 6: Users (Creating a similar listing was discussed in almost all the referenced articles)

Image 9

Currently there is just myself on the list of users captured. If you select a user from this list, the screen to update the user will be shown. This app uses different screens to add records and also update records. As much as this may seem duplication effort, I have found it easier to maintain my code with that approach.

Figure 7: Edit User

 Image 10

As you can see the footer navigation bar above, there are three buttons on it. 1. Cancel - to go back to the Users listing, 2. Update to save the user details to the web server and 3. Delete to delete the user details. This screen is resembles the Add User screen as depicted in Figure 3 with the exception of one added button, that is the Delete Button.

What happens when a user clicks the Update button? An existing user record is updated on the webserver.

Clicking Update on this screen will read the details from the user form and save these to a database. This behaves the same way as discussed in the Sign Up screen above. The same method is applied by due to the difference in form and page names, app.UpdateUser is called instead.

JavaScript
app.updateUser = function (UserRec) {
            // define a record object to store the current details
            var Email = UserRec.Email;
            // cleanse the record key of spaces.
            Email = Email.replace(/ /g, '-');
            UserRec.Email = Email;
            //convert record to json to write to server
            var recordJSON = JSON.stringify(UserRec);
            var req = Ajax("ajaxSaveUser.php", "POST" , recordJSON);
            if (req.status == 200) {
                //show a toast message that the record has been saved
                toastr.success('User record updated.', 'MyProjects');
                // clear the edit page form fields
                pgEditUserClear();
                // show the records listing page.
                $.mobile.changePage('#pgUser', {transition: pgtransition});
                } else {
                //show a toast message that the record has not been saved
                toastr.error('User record not updated. Please try again.', 'MyProjects');
            }
        };

The object passed to app.updateUser entails the records of the user as read from the User Edit screen depicted below.

JavaScript
//read contents of each form input
        function pgEditUserGetRec() {
            //define the new record
            var UserRec
            UserRec = {};
            UserRec.FirstName = $('#pgEditUserFirstName').val().trim();
            UserRec.LastName = $('#pgEditUserLastName').val().trim();
            UserRec.Email = $('#pgEditUserEmail').val().trim();
            UserRec.Password = $('#pgEditUserPassword').val().trim();
            UserRec.Password = sjcl.encrypt('MashJQMShow', UserRec.Password);
            UserRec.ConfirmPassword = $('#pgEditUserConfirmPassword').val().trim();
            UserRec.ConfirmPassword = sjcl.encrypt('MashJQMShow', UserRec.ConfirmPassword);
            UserRec.UserRole = $('#pgEditUserUserRole').val().trim();
            UserRec.Active = $('#pgEditUserActive').prop('checked');
            return UserRec;
        }

all this being called by the click event of the Update button for the user.

JavaScript
// Update click event on Edit Page
            $('#pgEditUserUpdate').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                // save the User
                var UserRecNew;
                //get contents of Edit page controls
                UserRecNew = pgEditUserGetRec();
                //save updated records to JSON
                app.updateUser(UserRecNew);
            });

From the above, when the update button is clicked, the user details are read from the screen and saved into a UserRecNew object by pgEditUserGetRec(). This object is then passed to app.updateUser method which calls the ajaxSaveUser.php script. 

This then concludes our CRU part of CRUD that we are talking about here. We read a user record with ajaxGetUser.php, created and updated a record with ajaxSaveUser.php and then we will talk about the Delete method.

The only way to delete a record with this approach is when you select the record from its listing and then clicking the Delete button from the edit screen. Figure 7 above is a typical example of this edit screen.

What happens when Delete is clicked? A record is deleted from the server.

When a user opts to delete a record, a prompt is provided to confirm whether the user wants to delete a record or not as demonstrated below. I have created another user here to demonstrate this.

Figure 8: Confirm Message Box

Image 11

If the end user is sure to delete the record, he/she should select Yes on this screen and the record will be removed from the webserver.

Creating prompts like this has been discussed in the previous articles, but anyway, lets refresh. The prompt that appears here is actually a page with a data role of dialog.

JavaScript
// delete button on Edit Page
            $('#pgEditUserDelete').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                //read the record key from form control
                var Email = $('#pgEditUserEmail').val().trim();
                //show a confirm message box
                Email = Email.replace(/-/g, ' ');
                $('#msgboxheader h1').text('Confirm Delete');
                $('#msgboxtitle').text(Email);
                $('#msgboxprompt').text('Are you sure that you want to delete this user? This action cannot be undone.');
                $('#msgboxyes').data('method', 'deleteUser');
                $('#msgboxno').data('method', 'editUser');
                Email = Email.replace(/ /g, '-');
                $('#msgboxyes').data('id', Email);
                $('#msgboxno').data('id', Email);
                $('#msgboxyes').data('topage', 'pgEditUser');
                $('#msgboxno').data('topage', 'pgEditUser');
                $.mobile.changePage('#msgbox', {transition: 'pop'});
            });

In this case, the email address of the user, as the user has been defined as a primary key for users is read and stored in a variable named Email. This is cleaned for any empty characters. The header of the message box is set with Confirm Delete, with a title being the email address we want to delete and a prompt of 'Are you sure you want to delete this user? This action cannot be undone.' shown to the user. Then the actions to be undertaken when a Yes / No button are selected by the user set.

When the user selects a Yes button, the app.deleteUser method will be executed.

JavaScript
$('#msgboxyes').data('method', 'deleteUser');

and when a No is selected, then app.editUser will be executed.

JavaScript
$('#msgboxno').data('method', 'editUser');

basically going back to the edit screen.

How do these methods deleteUser and editUser methods get called?

If you note from the code above, data properties have been set for the message box before it is shown, then it is shown with a pop with.

JavaScript
$.mobile.changePage('#msgbox', {transition: 'pop'});
$('#msgboxyes').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                var yesmethod = $('#msgboxyes').data('method');
                var yesid = $('#msgboxyes').data('id');
                app[yesmethod](yesid);
            });

Above is the code of the Yes button click. The method to be executed is read from the data-method attribute that has just been set for the button and then the method executed with...

JavaScript
app[yesmethod](yesid);

The same goes for the edit method.

JavaScript
$('#msgboxno').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                var nomethod = $('#msgboxno').data('method');
                var noid = $('#msgboxno').data('id');
                var toPage = $('#msgboxno').data('topage');
                // show the page to display after a record is deleted
                $.mobile.changePage('#' + toPage, {transition: pgtransition});
                app[nomethod](noid);
            });

When a user opts for a No, the method to execute is read from the data-method attribute that has been set before the confirm button is shown. The topage and id data attributes are also read and then the provided page is shown.

JavaScript
$.mobile.changePage('#' + toPage, {transition: pgtransition});
app[nomethod](noid);

This approach is followed because the app uses the same messagebox, alerts for all available models. When a user deletes a person from the People screen or deletes a project from the Projects screen, end users will see the same prompting screen but it will act differently according to what is passed to it for each model.

For user deletion, the method to be called will be deleteUser, this is depicted below:

JavaScript
//delete a record from JSON using record key
        app.deleteUser = function (Email) {
            Email = Email.replace(/ /g, '-');
            var req = Ajax("ajaxDeleteUser.php/?Email=" + Email);
            if (req.status == 200) {
                toastr.success('User record deleted.', 'MyProjects');
                } else {
                toastr.error('User record not deleted.', 'MyProjects');
            }
            // show the page to display after a record is deleted, this case listing page
            $.mobile.changePage('#pgUser', {transition: pgtransition});
        };

Delete user uses the email address to delete the users. An ajax call to delete a record is executed by..

JavaScript
var req = Ajax("ajaxDeleteUser.php/?Email=" + Email);

if the execution is succesful, the user json file record will be deleted from the webserver and a toast message shown to the user that the record was deleted. Our previous articles dealt with the toast scripts. As soon as the record is deleted/not deleted, the page is changed to User listing page, with the given transition.

PHP
<?php
    //delete the json record file from the server
    $Email = $_GET['Email'];
    unlink('./User/'.$Email.'.json');
?>

 

ajaxDeleteUser.php basically executes the unlink php method to delete the <user>.json file from the web server.

Designing the MyProjects web app...

For each project, an owner should be defined first. Thus, the first step in recording our projects, we need to add our people. In the springboard after logging in, select People, this will list all available captured people and also makes available a button at top right to named New to create a new person.

Figure 9: People Listing

Image 12

Selecting a person from the list opens up a Person Edit screen as shown in Figure 2 above. One can then update a persons details by selecting Save or can delete a users details by selecting Delete. An ajaxSavePerson and an ajaxDeletePerson will be executed in such cases.

JavaScript
//listview item click eventt.
            $(document).on('click', '#pgPersonList a', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                //get href of selected listview item and cleanse it
                var href = $(this)[0].href.match(/\?.*$/)[0];
                var FullName = href.replace(/^\?FullName=/,'');
                //change page to edit page.
                $.mobile.changePage('#pgEditPerson', {transition: pgtransition});
                //read record from JSON and update screen.
                app.editPerson(FullName);
            });

The primary key for the person's file is the FullName of the person and using that to edit a persons record is used. You might be wondering how all available people are loaded and listed in this listview when the records reside on the server. Let me explain. I will explain with the projects details going forward. If you are on the People screen, select the Menu button at the top left. A panel will appear and then select the Back button. This will take you to the springboard. From the springboard, select Projects, this will list all your projects as depicted in Figure 10 below.

Figure 10: Projects Listing

Image 13

If you dont have projects yet, select New and add a few projects. The same methods to add a user that was explain above applies.

Listing Projects: How is this done from the server?

As we have indicated, each record is stored as a single json file on the webserver for all records that we store, whether users, people or projects. To list records in a listview, a pagebeforechange method is executed,

JavaScript
$(document).on('pagebeforechange', function (e, data) {
                //get page to go to
                var toPage = data.toPage[0].id;
                switch (toPage) {
                case 'pgProject':
                    $('#pgRptProjectBack').data('from', 'pgProject');
                    // restart the storage check
                    app.checkForProjectStorage();
                    break;
                case 'pgReports':
                    $('#pgRptProjectBack').data('from', 'pgReports');
                    break;
                    case 'pgRptProject':
                    app.ProjectRpt();
                    break;
                case 'pgEditProject':
                    $('#pgRptProjectBack').data('from', 'pgEditProject');
                    // clear the add page form fields
                    pgEditProjectClear();
                    //load related select menus before the page shows
                    app.pgEditProjectLoadOwner();
                    break;
                case 'pgAddProject':
                    $('#pgRptProjectBack').data('from', 'pgAddProject');
                    // clear the add page form fields
                    pgAddProjectClear();
                    //load related select menus before the page shows
                    app.pgAddProjectLoadOwner();
                    break;
                default:
                }
            });

This method above gets executed before each page is shown and executes the respective code depending on which page is specified in the switch statement. Our project listing page is called pgProject.

When pgProject is detected as the next page that will be shown by MyProjects, this method, 

JavaScript
app.checkForProjectStorage();

is executed. What does this do anyway?

JavaScript
//display records if they exist or tell user no records exist.
        app.checkForProjectStorage = function () {
            //get records from JSON.
            var ProjectObj = app.getProject();
            // are there existing Project records?
            if (!$.isEmptyObject(ProjectObj)) {
                // yes there are. pass them off to be displayed
                app.displayProject(ProjectObj);
                } else {
                // nope, just show the placeholder
                $('#pgProjectList').html(ProjectHdr + noProject).listview('refresh');
            }
        };

app.checkForProjectStorage runs app.getProject to read all existing records from the server, if the are existing records, these are loaded to ths listview by app.displayProject. If there are no projects, the end user is told that there are no projects available.

Lets explore

JavaScript
//get records from JSON.
var ProjectObj = app.getProject();

Why are we calling app.getProject()? app.getProject method is used a lot within the app as you will note from the source code. From the look of things, its called 4 times already. You will note that the method to edit a project does not get the project json file record directly but reads all records first into an array and then get the record we are editing from that array. Wow, that's a mouthful already. Yes, that's a very long way of doing things, why read all records when you can just read the json project file you want? That was done on purpose for demonstration purposes only. On a live environment, doing such a read will pull a lot of server resources and might even crash your server. Instead when performing a read, read the json file record you want for an edit as that is quicker and not resource intensive.

JavaScript
//get all existing records from JSON
        app.getProject = function () {
            // get Project records
            var ProjectObj = {};
            var icnt, itot;
            //get the list of files under directory
            var req = Ajax("ajaxGetProject.php"); //NB//
            if (req.status == 200) {
                var recFiles = req.responseText;
                recFiles = recFiles.split('\n');
                itot = recFiles.length - 1;
                for (icnt = 0; icnt <= itot; icnt++) {
                    var recFile = recFiles[icnt];
                    if (recFile.length > 0) {
                        // read the file contents and display them
                        var req = Ajax("ajaxGetProject.php?file=" + encodeURIComponent(recFile)); //NB//
                        if (req.status == 200) {
                            // parse string to json object
                            var record = JSON.parse(req.responseText);
                            var ProjectName = record.ProjectName;
                            record.ProjectName = record.ProjectName.replace(/-/g, ' ');
                            ProjectObj[ProjectName] = record;
                        }
                    }
                }
                //sort the objects
                var keys = Object.keys(ProjectObj);
                keys.sort();
                var sortedObject = Object();
                var i;
                for (i in keys) {
                    key = keys[i];
                    sortedObject[key] = ProjectObj[key];
                }
                ProjectObj = sortedObject;
                return ProjectObj;
            }
        };

The app.getProject method executes ajaxGetProject.php to get all project.json file names from the server. Existing file names are returned as delimited \n string. Then we loop through all these file names and read each one of them and save details of each file into a json {} object. When all these records are read, these are then sorted by the primary key, this being the ProjectName. Note that ajaxGetProject.php is called twice within the app.getProject method. First no file name is parsed and then the second time when the file names are read, the file name is passed. See code lines with //NB// above.

ajaxGetProject.php

PHP
<?php
    //get the file contents from the server
    If (isset($_REQUEST['file'])) {
        $file = basename($_REQUEST['file']);
        echo file_get_contents('./Project/'.$file);
        } Else {
        If (is_dir('./Project') && $handle = opendir('./Project/')) {
            While (False !== ($entry = readdir($handle))) {
                If (!is_dir($entry)) {
                    echo basename($entry)."\n";
                }
            }
            closedir($handle);
            } Else {
            header("HTTP/1.0 404 Not Found");
        }
    }
?>

When called without the 'file' attribute specified, ajaxGetProject.php goes through the server Project folder and returns the basename for each file that exists delimited by CRLF i.e. \n

This is just the file names and when these are returned, they resulting response...

JavaScript
var recFiles = req.responseText;
recFiles = recFiles.split('\n');

is then converted into a string array by splitting it, then for each file read, then the contents of each file are read and stored into {}. Each object then will have each project attributes.

JavaScript
var req = Ajax("ajaxGetProject.php?file=" + encodeURIComponent(recFile)); //NB//
                        if (req.status == 200) {
                            // parse string to json object
                            var record = JSON.parse(req.responseText);
                            var ProjectName = record.ProjectName;
                            record.ProjectName = record.ProjectName.replace(/-/g, ' ');
                            ProjectObj[ProjectName] = record;
                        }

Then when all the project details are read, app.displayProject is called passing the returned project objects.

JavaScript
//display records in listview during runtime.
        app.displayProject = function (ProjectObj) {
            // create an empty string to contain html
            var html = '';
            // make sure your iterators are properly scoped
            var n;
            // loop over records and create a new list item for each
            //append the html to store the listitems.
            for (n in ProjectObj) {
                //get the record details
                var ProjectRec = ProjectObj[n];
                // clean the primary key
                ProjectRec.ProjectName = ProjectRec.ProjectName.replace(/-/g, ' ');
                //define a new line from what we have defined
                var nItem = ProjectLi;
                //update the href to the key
                n = n.replace(/-/g, ' ');
                nItem = nItem.replace(/Z2/g,n);
                //update the title to display, this might be multi fields
                var nTitle = '';
                //assign cleaned title
                nTitle = n.replace(/-/g, ' ');
                //replace the title;
                nItem = nItem.replace(/Z1/g,nTitle);
                //there is a count bubble, update list item
                var nCountBubble = '';
                nCountBubble += ProjectRec.PercentComplete;
                //replace the countbubble
                nItem = nItem.replace(/COUNTBUBBLE/g,nCountBubble);
                //there is a description, update the list item
                var nDescription = '';
                nDescription += ProjectRec.Status;
                nDescription += ', ';
                nDescription += ProjectRec.Priority;
                //replace the description;
                nItem = nItem.replace(/DESCRIPTION/g,nDescription);
                html += nItem;
            }
            //update the listview with the newly defined html structure.
            $('#pgProjectList').html(ProjectHdr + html).listview('refresh');
        };

For each project, we get the project name, this will be shown in the title of the listview, the countbubble shows the percentage complete of each project and the description shows the status and priority. This is done via a loop through each project {} element that exists from everything read from the server.

This is all done before the project listing is shown.

Projects Listing Html Definition:

HTML
<div data-theme="a" id="pgProject" data-role="page">
                            <div data-position="left" data-display="reveal" data-position-fixed="true" id="pgProjectPnl" data-role="panel">
                                <ul data-role="listview" id="pgProjectPnlLV">
                                    <li ><a data-transition="slide" href="#pgAddProject">New</a></li>
                                    <li ><a data-transition="slide" href="#pgRptProject">Report</a></li>
                                    <li ><a data-transition="slide" href="#pgMenu">Back</a></li>
                                </ul>
                            </div>
                            
                            <header id="pgProjectHdr" data-role="header" data-position="fixed">
                                <h1>MyProjects</h1>
                                <a data-role="button" id="pgProjectMenu" href="#pgProjectPnl" data-icon="bars" data-transition="slide" class="ui-btn-left">Menu</a>
                                <a data-role="button" id="pgProjectNew" data-icon="plus" data-theme="b" class="ui-btn-right">New</a>
                            </header>
                            <div id="pgProjectcontent" data-role="content">
                                <h3>Projects</h3><ul data-role="listview" data-inset="true" id="pgProjectList" data-filter="true" data-filter-placeholder="Search Projects" data-filter-reveal="false">
                                    <li data-role="list-divider">Your Projects</li>
                                    <li id="noProject">You have no projects</li>
                                    
                                </ul>
                            </div>
                            
                        </div>

In its simplest definition, the screen to list projects is basically empty as its details are updated during runtime based on captured projects. This screen has a slide panel to enable access to project reports, go back to the projects listing screen and add a new project.

Clicking New will take one to add a new project as depicted in Figure 1 above.

Project HTML Definition.

As you saw in Figure 1, the project screen is just a simple screen to with basic controls to add a project details as per specifications given. This is defined by this html code here.

HTML
<div data-theme="a" id="pgAddProject" data-role="page">
                            <div data-position="left" data-display="reveal" data-position-fixed="true" id="pgAddProjectPnl" data-role="panel">
                                <ul data-role="listview" id="pgAddProjectPnlLV">
                                    <li ><a data-transition="slide" href="#pgAddProject">New</a></li>
                                    <li ><a data-transition="slide" href="#pgRptProject">Report</a></li>
                                    <li ><a data-transition="slide" href="#pgProject">Back</a></li>
                                </ul>
                            </div>
                            
                            <header id="pgAddProjectHdr" data-role="header" data-position="fixed">
                                <h1>MyProjects</h1>
                                <a data-role="button" id="pgAddProjectMenu" href="#pgAddProjectPnl" data-icon="bars" class="ui-btn-left">Menu</a>
                            </header>
                            <div id="pgAddProjectcontent" data-role="content">
                                <h3>Add Project</h3><form action="#" method="post" id="pgAddProjectForm" name="pgAddProjectForm">
                                    <div data-role="fieldcontain">
                                        <label for="pgAddProjectProjectName" id="lblpgAddProjectProjectName">Project Name<span style='color:red;'>*</span></label>
                                        <input required title="Enter project name here." type="text" name="pgAddProjectProjectName" id="pgAddProjectProjectName" placeholder="Enter project name here." autocomplete="off" data-clear-btn="true"></input>
                                    </div>
                                    <div data-role="fieldcontain">
                                        <fieldset id="fspgAddProjectStatus" data-role="controlgroup" data-type="horizontal" data-mini="true">
                                            <legend>Status<span style='color:red;'>*</span></legend>
                                            <input type="radio" name="pgAddProjectStatus" id="pgAddProjectStatusApproved" autocomplete="off" value="Approved"></input>
                                            <label for="pgAddProjectStatusApproved" id="lblpgAddProjectStatusApproved">Approved</label>
                                            <input type="radio" name="pgAddProjectStatus" id="pgAddProjectStatusPending" autocomplete="off" value="Pending"></input>
                                            <label for="pgAddProjectStatusPending" id="lblpgAddProjectStatusPending">Pending</label>
                                            <input type="radio" name="pgAddProjectStatus" id="pgAddProjectStatusStarted" autocomplete="off" value="Started"></input>
                                            <label for="pgAddProjectStatusStarted" id="lblpgAddProjectStatusStarted">Started</label>
                                            <input type="radio" name="pgAddProjectStatus" id="pgAddProjectStatusCancelled" autocomplete="off" value="Cancelled"></input>
                                            <label for="pgAddProjectStatusCancelled" id="lblpgAddProjectStatusCancelled">Cancelled</label>
                                            <input type="radio" name="pgAddProjectStatus" id="pgAddProjectStatusOn hold" autocomplete="off" value="On hold"></input>
                                            <label for="pgAddProjectStatusOn hold" id="lblpgAddProjectStatusOn hold">On hold</label>
                                        </fieldset>
                                    </div>
                                    <div data-role="fieldcontain">
                                        <fieldset id="fspgAddProjectPriority" data-role="controlgroup" data-type="horizontal" data-mini="true">
                                            <legend>Priority<span style='color:red;'>*</span></legend>
                                            <input type="radio" name="pgAddProjectPriority" id="pgAddProjectPriorityLow" autocomplete="off" value="Low"></input>
                                            <label for="pgAddProjectPriorityLow" id="lblpgAddProjectPriorityLow">Low</label>
                                            <input type="radio" name="pgAddProjectPriority" id="pgAddProjectPriorityMedium" autocomplete="off" value="Medium"></input>
                                            <label for="pgAddProjectPriorityMedium" id="lblpgAddProjectPriorityMedium">Medium</label>
                                            <input type="radio" name="pgAddProjectPriority" id="pgAddProjectPriorityHigh" autocomplete="off" value="High"></input>
                                            <label for="pgAddProjectPriorityHigh" id="lblpgAddProjectPriorityHigh">High</label>
                                        </fieldset>
                                    </div>
                                    <div data-role="fieldcontain">
                                        <label for="pgAddProjectDueDate" id="lblpgAddProjectDueDate">Due Date<span style='color:red;'>*</span></label>
                                        <input required data-options='{"mode":"flipbox","dateFormat":"%Y-%m-%d","overrideDateFormat":"%Y-%m-%d"}' title="Enter due date here." type="text" name="pgAddProjectDueDate" id="pgAddProjectDueDate" placeholder="Enter due date here." autocomplete="off" data-role="datebox"></input>
                                    </div>
                                    <div data-role="fieldcontain">
                                        <label for="pgAddProjectPercentComplete" id="lblpgAddProjectPercentComplete">Percent Complete<span style='color:red;'>*</span></label>
                                        <input required min="0" max="100" title="" type="range" name="pgAddProjectPercentComplete" id="pgAddProjectPercentComplete" autocomplete="off"></input>
                                    </div>
                                    <div dir="ltr" data-role="fieldcontain">
                                        <label for="pgAddProjectOwner" id="lblpgAddProjectOwner">Owner<span style='color:red;'>*</span></label>
                                        <select name="pgAddProjectOwner" id="pgAddProjectOwner" dir="ltr" class="required">
                                            <option value="null" data-placeholder="true">Select Owner</option>
                                            <option ></option>
                                        </select>
                                    </div>
                                    <div data-role="fieldcontain">
                                        <label for="pgAddProjectNotes" id="lblpgAddProjectNotes">Notes<span style='color:red;'>*</span></label>
                                        <textarea name="pgAddProjectNotes" id="pgAddProjectNotes" placeholder="Enter notes here." class="required"></textarea>
                                    </div>
                                </form>
                            </div>
                            
                            <footer id="Ftr" data-role="footer" data-position="fixed">
                                <div data-role="navbar">
                                    <ul>
                                        <li><a id="pgAddProjectBack" data-icon="carat-l">Cancel</a>
                                        </li>
                                        <li><a type="submit" id="pgAddProjectSave" data-icon="action">Save</a>
                                        </li>
                                    </ul>
                                </div>
                            </footer></div>

This screen also have a panel to help with navigation around the projects screen. The page name for Adding Projects is 

HTML
pgAddProject

What happens when a user clicks Save on the Project Screen?

When a user clicks Save, just like when we were discussion adding users above, the project details are read and saved into an object and this object is saved to the web server.

JavaScript
// Save click event on Add page
            $('#pgAddProjectSave').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                // save the Project
                var ProjectRec;
                //get form contents into an object
                ProjectRec = pgAddProjectGetRec();
                //save object to JSON
                app.addProject(ProjectRec);
            });

function pgAddProjectGetRec() {
            //define the new record
            var ProjectRec
            ProjectRec = {};
            ProjectRec.ProjectName = $('#pgAddProjectProjectName').val().trim();
            ProjectRec.Status = $('input:radio[name=pgAddProjectStatus]:checked').val();
            ProjectRec.Priority = $('input:radio[name=pgAddProjectPriority]:checked').val();
            ProjectRec.DueDate = $('#pgAddProjectDueDate').val().trim();
            ProjectRec.PercentComplete = $('#pgAddProjectPercentComplete').val().trim();
            ProjectRec.Owner = $('#pgAddProjectOwner').val().trim();
            ProjectRec.Notes = $('#pgAddProjectNotes').val().trim();
            return ProjectRec;
        }

app.addProject then saves the record to the server by call ajaxSaveProject.php (saving records was discussed in detail above)

JavaScript
// add a new record to server storage.
        app.addProject = function (ProjectRec) {
            // define a record object to store the current details
            var ProjectName = ProjectRec.ProjectName;
            // cleanse the record key of spaces.
            ProjectName = ProjectName.replace(/ /g, '-');
            ProjectRec.ProjectName = ProjectName;
            //convert record to json to write to server
            var recordJSON = JSON.stringify(ProjectRec);
            // save the data to a server file, use the post method as it has 8MB minimum data limitation
            var req = Ajax("ajaxSaveProject.php", "POST" , recordJSON);
            if (req.status == 200) {
                //show a toast message that the record has been saved
                toastr.success('Project record saved.', 'MyProjects');
                //find which page are we coming from, if from sign in go back to it
                var pgFrom = $('#pgAddProject').data('from');
                switch (pgFrom) {
                    case "pgSignIn":
                    $.mobile.changePage('#pgSignIn', {transition: pgtransition});
                    break;
                    default:
                    // clear the edit page form fields
                    pgAddProjectClear();
                    //stay in the same page to add more records
                }
                } else {
                //show a toast message that the record has not been saved
                toastr.error('Project record not saved. Please try again.', 'MyProjects');
            }
        };

To update project details, an end user should locate the project of interest from project listing and then select it, this will open the Edit Project Screen as depicted in Figure 1. The end user can then update its details and click Save or perhaps click Delete if they want to delete the project.

What happens when a user clicks Delete on Edit Project?

JavaScript
// delete button on Edit Page
            $('#pgEditProjectDelete').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                //read the record key from form control
                var ProjectName = $('#pgEditProjectProjectName').val().trim();
                //show a confirm message box
                ProjectName = ProjectName.replace(/-/g, ' ');
                $('#msgboxheader h1').text('Confirm Delete');
                $('#msgboxtitle').text(ProjectName);
                $('#msgboxprompt').text('Are you sure that you want to delete this project? This action cannot be undone.');
                $('#msgboxyes').data('method', 'deleteProject');
                $('#msgboxno').data('method', 'editProject');
                ProjectName = ProjectName.replace(/ /g, '-');
                $('#msgboxyes').data('id', ProjectName);
                $('#msgboxno').data('id', ProjectName);
                $('#msgboxyes').data('topage', 'pgEditProject');
                $('#msgboxno').data('topage', 'pgEditProject');
                $.mobile.changePage('#msgbox', {transition: 'pop'});
            });

As you will note, this acts the same was as Delete user above but in this case the app.deleteProject method is called to delete a project.

JavaScript
//delete a record from JSON using record key
        app.deleteProject = function (ProjectName) {
            ProjectName = ProjectName.replace(/ /g, '-');
            var req = Ajax("ajaxDeleteProject.php/?ProjectName=" + ProjectName);
            if (req.status == 200) {
                toastr.success('Project record deleted.', 'MyProjects');
                } else {
                toastr.error('Project record not deleted.', 'MyProjects');
            }
            // show the page to display after a record is deleted, this case listing page
            $.mobile.changePage('#pgProject', {transition: pgtransition});
        };

Because the project name is the primary key for projects, this is what gets passed to ajaxDeleteProject.php, which is represented by...

PHP
<?php

    //delete the json record file from the server
    $ProjectName = $_GET['ProjectName'];
    unlink('./Project/'.$ProjectName.'.json');
?>

 

which basically justs deletes the project file from the web server, all from clicking the Yes button from this prompt.

Figure 11: Confirm Project Delete

Image 14

For our projects, there is a reporting / governing structure as defined by the people who we capture. You will recall that in the People screen, one has to indicate who a person reports to.

Figure 12: Project Governance Tree

Image 15

This reporting structure can then be demonstrated with the following d3 tree chart that can also be exported to a picture when a user clicks Export (top right). Creating such a tree has been greatly discussed in the Enhancing MyFamily.Show article I wrote. This is accessible from selecting Menu from People screen and selecting Relationships > ReportsTo, as depicted below.

Figure 13: People Listing Side Menu

Image 16

One is also able to generate excel reports from captured information for users, people and projects. From each listing selecting Menu provides access to the slide menu. Below is a report of the Projects captured as an example

Figure 14: Projects Report (clicking Export to Excel will generate a report as depicted in Figure 15)

Image 17

Figure 15: Excel Report

Image 18

That's all folks!!!, thanks for reading this article.

Points of Interest

The Ajax call to process the php files is inside the remoteserver.js file that needs to be referenced within the header section when defining your html file.

JavaScript
function Ajax(URL, method, data, callback) {
    if (typeof(method) !== 'object') {
        var settings = new Object;
        if(!method || method === null || typeof(method) === 'undefined') method = "GET";
        settings.type = method.toUpperCase()
        if(!data || data === null || typeof(data) === 'undefined') data = "";
        settings.data = data;
        if (!callback) {
            settings.async = false;
            } else {
            settings.success = callback;
        settings.fail = callback}
    }
    return $.ajax(URL, settings);
}

For reading the web server files, we specify the URL and perform resulting functions based on the return req.status. For writing, we define the method as POST and the data passed being the stringified json text.

We have also added iscroll.js here for all the listview controls. Where forms should scroll, similar methods can be applied to the code to make an element scroll.

JavaScript
var pgUserListScroller = new IScroll('#pgUserList', {mouseWheel:true, scrollbars:true, bounce:true, zoom:false});

Whilst with this approach it seems easy to develop mobile web apps that can be accessible from any device with data that will sit on a server and be accessible to everyone having access to the server, the speed of the server and how your app is designed will affect the performance. It is thus imperative that your code is optimized as much as possible, especially for read and writes to the server. 

The challenge here is still generating queries based on the saved information as each record is stored as a single json file on the server. For very small database applications this works perfectly where there is not going to be queries that need to be executed. This approach also ensures that each user can edit/update/delete on record at a time without having to keep all records loaded on the edit screens.

This kind of access means that as soon as a record is updated by another user, it will be available as soon as when saved. There has been no attempt made to lock the records on edit though.

I will however make an attempt to show a progress bar as in some systems, reading all records from the server might have some delays.

License

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