Showing posts with label Manipulating data. Show all posts
Showing posts with label Manipulating data. Show all posts

Sunday, April 8, 2012

CRUD operations with WCF Ria Services, Upshot and Knockout

In my previous post, I showed you how to retrieve data with upshot using WCF Ria Services. Today I’m extending this example with simple CRUD operations, including validation. If you want to see this sample in action, I have added it to codeplex. You can find it here.

Server side

We will start at the server side, because this needs the least adjustments. Just like the Silverlight client, you can use the CUD operations by the naming convention (“InsertClassname”, “UpdateClassename” or “DeleteClassname”). Or by using the “insert”, “update” or “delete” attribute on the corresponding attributes. In my example I have done both. Once all this is done, the server side is configured.

[Insert]
public void InsertTodoItem(TodoItem todoItem)
{
    // Insert code
}
 
[Update]
public void UpdateTodoItem(TodoItem todoItem)
{
    // Update code
}
 
[Delete]
public void DeleteTodoItem(TodoItem todoItem)
{
    // Delete code
}

Client side


All changes made on the client side are by default immediately persisted to the server, however the upshot framework provides a way to buffer the changes. These changes will only be persisted to the server when the commitChanges method is called. If you want to undo the changes you made, you can call the revertChanges method. This way the situation will be the same as the last commit of the data. If you want to use the buffer functionality, you can do this by putting the bufferChanges property on true in the data source configuration object.



upshot.dataSources.GetTodoItems = upshot.RemoteDataSource({
    providerParameters: { 
        url: "/Ria4HTML-Services-TodoItemDomainService.svc",
        operationName: "GetTodoItems" 
    },
    provider: upshot.riaDataProvider,
    bufferChanges: true,
    entityType: "TodoItem:#Ria4html.Models",
    mapping: TodoItem
});

In the data source configuration object, we also need to provide an entityType and a mapping if we want create new entities on the client side. The mapping contains a JavaScript object that is represents the viewmodel for creating a new entity of the provided entityType. The entityType contains the classname + namespace of the objects on the server side. (Naming convention: <Classname>:#<namespace>)



function TodoItem(properties) {
    var self = this;
    properties = properties || {};
    self.TodoItemId = ko.observable(properties.TodoItemId || 0);
    self.Title = ko.observable(properties.Title);
    self.IsDone = ko.observable(properties.IsDone);
    upshot.addEntityProperties(self); // add properties managed by upshot
};



Todoitem is an example of a mapping object. The only thing we need to do to allow entity tracking, is adding the entity to upshot. This is done by calling the addEntityProperties method with the viewmodel as parameter. When this is done, the entity will be known in upshot and will be persisted to the server when saved.


Now we can start building up our viewmodel



function TodoItemViewModel(dataSource) {
    var self = this;
 
    self.dataSource = upshot.dataSources.GetTodoItems.refresh();
    self.localDataSource = upshot.LocalDataSource({ 
            source: self.dataSource
          , autoRefresh: true 
    });
    // Public data properties
    self.todoItems = self.dataSource.getEntities();
}

This was the situation we currently have when we just want to retrieve data. So lets start implementing our CUD operations.


Inserting Data


The first thing we will do if we want to start inserting data is adding a new instance of the mapping viewmodel to the TodoItemView model.



// Creates a new TodoItem and adds it to the viewmodel
self.newTodoItem = ko.observable(new TodoItem());
// Not possible on the localDataSource only on the remote
self.validationConfig = $.extend(
    {}
  , self.dataSource.getEntityValidationRules()
  , { submitHandler: handleSubmit}
);
 
var handleSubmit = function () {
    // Add new TodoItem to data source
    self.todoItems.unshift(self.newTodoItem()); 
    // Revert form to blank state
    self.newTodoItem(new TodoItem());     
}

Next, we add a validationConfig to the viewModel, this way we can enable client validation. The extend method of jQuery is used to merge multiple objects into the object provided in the first parameter. In this case we are merging the validation rules and an success handler. When using upshot, the rules of validation are added in the metadata and will be retrieve by calling the getEntityValidationRules method on the data source with the data.


If all validations are satisfied the submit handler will get called. In here we will add the new created object (values are filled up due the 2way binding of knockout). to the TodoItem collection of the viewmodel. Next we need to instantiate the newTodoItem property again with a new instance of TodoItem.


Now all the logic is implemented for inserting data, the only thing left to do is adding a view for inserting the data. Note that the autovalidate attribute has to be set on true for every field you want to validate. If you don’t enable this, you will have to call the validate method on each field manually.



<form data-bind="validate: validationConfig, with: newTodoItem">
    <fieldset>
        <table>
            <caption>TodoItem Information</caption>
            <tr>
                <th>Title</th>
                <td><input data-bind="value: Title, autovalidate: true"
                           name="Title" /></td>
            </tr>
            <tr>
                <th>Is done</th>
                <td><input data-bind="checked: IsDone, autovalidate: true"
                           name="IsDone" type="checkbox" /></td>
            </tr>
            <tr>
                <th>Action</th>
                <td><button class="addButton" type="submit">add</button></td>
            </tr>
        </table>
    </fieldset>
</form>

Important: If you want to use validation in combination with RIA services without manually providing the metadata, then you can only apply the binding when the data from the server is retrieved. If you don’t do this, no validation rules will get found when the validationConfiguration object is created. This way the validation rules will always be satisfied (since there are none) and the new entity will be added. So the solution is to provide a success callback on the refresh of the data source.



// necessary for validation
upshot.dataSources.GetTodoItems.refresh({}, function () {
    ko.applyBindings(new TodoItemViewModel());
});

UPDATING DATA


Since we use knockout.js for databinding all changes will be detected automatically and upshot will make sure the entitystate is adjusted as we start changing the entities. 


Removing data


Removing data is done by calling the deleteEntity method on the data source. For this we need to pass the entity we want to delete as parameter. On our view model we can provide a method for handling the delete of an entity



// Public operations
self.removeTodoItem = function (todoItem) {
    self.dataSource.deleteEntity(todoItem);
};

In our view we can then add a button that will handle the deletion of the item. By binding the removeTodoItem function on the parent, the current todoItem will be passed as parameter and will get deleted.



<ol data-bind="foreach: todoItems">
    <li>
        <strong data-bind="text: Title"></strong>
        <button class="removeButton" 
                data-bind="click: $parent.removeTodoItem">remove</button>
    </li>
</ol>



If you are working in buffermode, the entity will still appear in the list. This is because the entity is only deleted on the data source, and will only disappear when you save the changes. If you want to have it disappear immediately, you will have to provide a filter.


BufferMode


The last thing I want to add is how to manually commit or revert changes when the bufferMode is on. This is done by calling the commitChanges or revertChanges method on the data source. In the viewmodel we can provide to methods who enable this functionality



// Operations
self.saveAll = function () { self.dataSource.commitChanges() }
self.revertAll = function () { self.dataSource.revertChanges() }


In the view we can then add 2 buttons to save/revert our changes.



<button data-bind="click: saveAll">Save all</button>
<button data-bind="click: revertAll">Revert all</button>

Wednesday, November 9, 2011

Indexed DB: Manipulating data

The first thing we need to do when we want to read data, is to make sure we have data available in our database. So this post will handle inserting and updating data into the database.

For inserting data we have 2 possibilities: we have an add and a put method available. Both methods take the same parameters, but there is a slight difference between them. When we use the add method, we need to be sure that no other object with the same key is already added in the database. If it does, an constraint error (CONSTRAINT_ERR) will occur. So the add method will only be used when we want to add data that won’t be overwritten when it is already present with the same key in the database.

The put method we will usually use when we want to update data. If the data isn’t present yet with the same key, the data will be inserted. If you want some more information about the steps for storing a record into an object store, you can find this here on the W3C specs site.

Make sure when you are starting a new transaction to manipulate data, you will use a read_write transaction. Otherwise you will receive an READ_ONLY_ERR when calling the methods.

The put and the add method are both available in the IObjectStore interface. This means once we have created the transaction where we will work in, we need to retrieve the object store we want to work on. We do this by calling the ObjectStore method on the transaction. The only thing we need to pass is the name of the object store.
var txn = dbconnection.transaction([“ObjectStoreName”]
                                                 , IDBTransaction.READ_WRITE);
var objectStore = txn.objectStore(“ObjectStoreName”);
Once we have the object store object, we can start adding data to it. The result of the add operation is an IDBRequest object. This means the onsuccess will be called when the operation was successful and an onerror will be called when something went wrong.
When the onsuccess function gets called, the result of this action will be the key of the object in the object store
var addresult = objectStore.add(data);
addresult.onsuccess = function (event) {
      // Adding data is successful
      // Commit the transaction when using IE9
      var key;

      // IE 9 implementation
      if(event.result){
            key = event.result
      }
      // IE 10, Chrome and Firefox implementation
      else if (addresult.result){
            key = addresult.result;
      }
};
addresult.onerror = function (event) {
      // Handle the error
}
If we use a put for adding or changing data, we get the same structure only we will use the put method instead.
var putresult = objectStore.put(data);
putresult.onsuccess = function (event) {
      // Putting data is successful
      // Commit the transaction when using IE9
};
putresult.onerror = function (event) {
      // Handle the error
}
Now that we have added some data to our database, we can start retrieving it and present it to our users…