Simple Generic CRUD with AngularJS, Parse and Bootstrap

Envisage almost any data-driven user interface, and sooner or later you will probably come across a requirement like:

- present the user with a list (of users, addresses, products, etc.)

- allow the user to edit or delete an existing item in the list

- allow the user to create a new item and add it to the list

I decided to have a look at several web-based technologies to see how easy it might be to fulfil this requirement in as generic and extensible a way as possible, with the absolute minimal in the way of coding or configuration. I ended up opting for:

- angularJS for a data-bound UI

- Bootstrap for icons and element styling

- Parse for remote object definition, serialisation and persistence

My implementation involves:

- creating an angularJS Parse-based persistence service, which serialises the objects within a named Parse table

- creating a generic angularJS controller to present data supplied by the service and respond to user events

In order to use it, you just need to:

1. Subclass the service, specifying the name of the table you want bound to the UI

The following code creates an angular service bound to a Parse table called "Users":

angular.module('crudApp').service('UserPersistenceService', function () {

    return new PersistenceService("Users");

});

At the same time, you should create a table named "Users" within Parse, with columns for any information you want to bind. In this example, I created the fields "name", "bio" and "score." But I am electing just to bind the "name" and "bio" elements.

2. Subclass the controller, and add hooks to define application behaviour when items are added or edited

The controller exploits "backing fields" to store partially edited information, as well as supply default values when a new record is created. These operate as follows:

- initBackingFields(): called when a new record is created

- copyBackingFields(): called when you choose to edit a record

- commitBackingFields(): called when you have edited a record and elect to commit your changes 

So in the example above, we have just two editable fields, "name" and "bio", which we will back with two further fields, "newName" and "newBio", reflecting information that has been edited but not yet committed. The full implementation of the subclassed controller looks like this:

angular.module('crudApp').controller('UserListCtrl', ['$scope', '$controller', 'UserPersistenceService', function ($scope, $controller, persistenceService) {

    $controller('PersistenceController', {

        $scope: $scope,

        persistenceService: persistenceService,

        initBackingFields: function (item) {

            item.name = "";

            item.newName = item.name;

            item.bio = "";

        },

        copyBackingFields: function (item) {

            item.newName = item.attributes.name;

            item.newBio = item.attributes.bio;

        },

        commitBackingFields: function (item) {

            item.set("name", item.newName);

            item.set("bio", item.newBio);

        }

    });

} ]);

3. Bind the fields that interest you to the UI with appropriate directives 

Create an HTML page to display the items in the table in a list. The Persistence controller exposes three helper functions to facilitate record editing:

- toggleItemEdit(): switch a record in the UI between "editable" and "read only"

- deleteItem(): delete the currently selected item, and persist changes to the back end

- commitItemEdit(): update an existing or add a new record, and persist the changes

The resultant HTML page now looks like this:

   

   

   

   

   

    CRUD Test

   

   

       

Users

         

         

           

           

         

         

           

               

               

               

           

         

       

NameBio

                    {{item.attributes.name}}

                   

               

                    {{item.attributes.bio}}

                   

               

                 

                      Edit

                 

                 

                    Delete

                 

                 

                     

                        Update

                     

                     

                        Cancel

                     

                 

               

       

           

               

           

       

                   

                        New User

                   

               

       


     

Finally, here is the Javascript file that does all the work, "crudController.js" and the accompanying "styles.css", which adds some spacing to the table elements.

-- styles.css --

input

{

    width: 200px;

}

td

{

    width: 200px;

}

-- crudController.js --

var app = angular.module('crudApp', []);

function PersistenceService(parseTable) {

    var objParse = Parse.initialize("YOUR PARSE ADMIN KEY", "YOUR PARSE APP KEY");

    var items = Parse.Object.extend(parseTable);

    var queryItems = new Parse.Query(items);

    // Store the type of the persistence service created

    this.persistenceTable = parseTable;

    // Load a list of items from the back end

    this.load = function (itemsLoadedCallback) {

        queryItems.find(

        {

            success: function (results) {

                itemsLoadedCallback(results);

            },

            error: function (results) {

                alert("Failed to load items: " + results.message)

            }

        });

    }

    // Add a new or update an existing item

    this.update = function (item, itemUpdateCallback) {

        item.save(

        {

            success: function (results) {

                itemUpdateCallback(results);

            },

            error: function (results) {

                alert("Failed to update item: " + results.message)

            }

        });

    }

    // Delete an item

    this.remove = function (item, itemDeletedCallback) {

        item.destroy(

        {

            success: function (results) {

                itemDeletedCallback(results);

            },

            error: function (results) {

                alert("Failed to delete item: " + results.message)

            }

        });

    }

}

angular.module('crudApp').controller('PersistenceController', ['$scope', 'persistenceService', 'initBackingFields', 'copyBackingFields', 'commitBackingFields', function ($scope, persistenceService, initBackingFields, copyBackingFields, commitBackingFields) {

    // Callback to receive list of users, and bind them to the scope

    $scope.itemsLoaded = function (items) {

        // Set the isEditing and newName attributes on each user; these aren't persisted

        // but the controller uses them to facilitate editing

        for (i = 0; i < items.length; i++) {

            // We add these non-persisted fields to facilitate editing; "parse" won't serialise them

            copyBackingFields(items[i]);

            items[i].isEditing = false;

        }

        $scope.items = items;

        // Force scope update, to rebind updated item list to the view

        $scope.$apply();

    }

    // Called by the service when an item has been deleted, added or updated

    $scope.OnDatabaseChanged = function (items) {

        // Refresh the view

        persistenceService.load($scope.itemsLoaded);

    }

    $scope.toggleItemEdit = function (item) {

        item.isEditing = !item.isEditing;

    }

    $scope.commitItemEdit = function (item) {

        commitBackingFields(item)

        item.isEditing = false;

        // Commit the changes to the database

        persistenceService.update(item, $scope.OnDatabaseChanged);

    }

    $scope.addNewItem = function () {

        // This method doesn't commit the new user to the database; it just adds a row to the view,

        // and makes it editable

        var Item = Parse.Object.extend(persistenceService.persistenceTable);

        var item = new Item();

        item.isEditing = true;

        initBackingFields(item);

        $scope.items[$scope.items.length] = item;

    }

    $scope.deleteItem = function (item) {

        persistenceService.remove(item, $scope.OnDatabaseChanged);

    }

    // Start with an empty collection of items

    $scope.items = [];

    // Load the items from the service

    persistenceService.load($scope.itemsLoaded);

} ]);

This article is part of the GWB Archives. Original Author: Alex Hildyard

New on Geeks with Blogs