Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7018 Articles
article-image-applying-linq-entities-wcf-service
Packt
05 Feb 2013
15 min read
Save for later

Applying LINQ to Entities to a WCF Service

Packt
05 Feb 2013
15 min read
(For more resources related to this topic, see here.) Creating the LINQNorthwind solution The first thing we need to do is create a test solution. In this article, we will start from the data access layer. Perform the following steps: Start Visual Studio. Create a new class library project LINQNorthwindDAL with solution name LINQNorthwind (make sure the Create directory for the solution is checked to specify the solution name). Delete the Class1.cs file. Add a new class ProductDAO to the project. Change the new class ProductDAO to be public. Now you should have a new solution with the empty data access layer class. Next, we will add a model to this layer and create the business logic layer and the service interface layer. Modeling the Northwind database In the previous section, we created the LINQNorthwind solution. Next, we will apply LINQ to Entities to this new solution. For the data access layer, we will use LINQ to Entities instead of the raw ADO.NET data adapters. As you will see in the next section, we will use one LINQ statement to retrieve product information from the database and the update LINQ statements will handle the concurrency control for us easily and reliably. As you may recall, to use LINQ to Entities in the data access layer of our WCF service, we first need to add an entity data model to the project. In the Solution Explorer, right-click on the project item LINQNorthwindDAL, select menu options Add | New Item..., and then choose Visual C# Items | ADO.NET Entity Data Model as Template and enter Northwind.edmx as the name. Select Generate from database, choose the existing Northwind connection, and add the Products table to the model. Click on the Finish button to add the model to the project. The new column RowVersion should be in the Product entity . If it is not there, add it to the database table with a type of Timestamp and refresh the entity data model from the database In the EMD designer, select the RowVersion property of the Product entity and change its Concurrency Mode from None to Fixed. Note that its StoreGeneratedPattern should remain as Computed. This will generate a file called Northwind.Context.cs, which contains the Db context for the Northwind database. Another file called Product.cs is also generated, which contains the Product entity class. You need to save the data model in order to see these two files in the Solution Explorer. In Visual Studio Solution Explorer, the Northwind.Context.cs file is under the template file Northwind.Context.tt and Product.cs is under Northwind.tt. However, in Windows Explorer, they are two separate files from the template files. Creating the business domain object project During Implementing a WCF Service in the Real World, we create a business domain object (BDO) project to hold the intermediate data between the data access objects and the service interface objects. In this section, we will also add such a project to the solution for the same purpose. In the Solution Explorer, right-click on the LINQNorthwind solution. Select Add | New Project... to add a new class library project named LINQNorthwindBDO. Delete the Class1.cs file. Add a new class file ProductBDO.cs. Change the new class ProductBDO to be public. Add the following properties to this class: ProductID ProductName QuantityPerUnit UnitPrice Discontinued UnitsInStock UnitsOnOrder ReorderLevel RowVersion The following is the code list of the ProductBDO class: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LINQNorthwindBDO { public class ProductBDO { public int ProductID { get; set; } public string ProductName { get; set; } public string QuantityPerUnit { get; set; } public decimal UnitPrice { get; set; } public int UnitsInStock { get; set; } public int ReorderLevel { get; set; } public int UnitsOnOrder { get; set; } public bool Discontinued { get; set; } public byte[] RowVersion { get; set; } } } As noted earlier, in this article we will use BDO to hold the intermediate data between the data access objects and the data contract objects. Besides this approach, there are some other ways to pass data back and forth between the data access layer and the service interface layer, and two of them are listed as follows: The first one is to expose the Entity Framework context objects from the data access layer up to the service interface layer. In this way, both the service interface layer and the business logic layer—we will implement them soon in following sections—can interact directly with the Entity Framework. This approach is not recommended as it goes against the best practice of service layering. Another approach is to use self-tracking entities. Self-tracking entities are entities that know how to do their own change tracking regardless of which tier those changes are made on. You can expose self-tracking entities from the data access layer to the business logic layer, then to the service interface layer, and even share the entities with the clients. Because self-tracking entities are independent of entity context, you don't need to expose the entity context objects. The problem of this approach is, you have to share the binary files with all the clients, thus it is the least interoperable approach for a WCF service. Now this approach is not recommended by Microsoft, so in this book we will not discuss it. Using LINQ to Entities in the data access layer Next we will modify the data access layer to use LINQ to Entities to retrieve and update products. We will first create GetProduct to retrieve a product from the database and then create UpdateProduct to update a product in the database. Adding a reference to the BDO project Now we have the BDO project in the solution, we need to modify the data access layer project to reference it. In the Solution Explorer, right-click on the LINQNorthwindDAL project. Select Add Reference.... Select the LINQNorthwindBDO project from the Projects tab under Solution. Click on the OK button to add the reference to the project. Creating GetProduct in the data access layer We can now create the GetProduct method in the data access layer class ProductDAO, to use LINQ to Entities to retrieve a product from the database. We will first create an entity DbContext object and then use LINQ to Entities to get the product from the DbContext object. The product we get from DbContext will be a conceptual entity model object. However, we don't want to pass this product object back to the upper-level layer because we don't want to tightly couple the business logic layer with the data access layer. Therefore, we will convert this entity model product object to a ProductBDO object and then pass this ProductBDO object back to the upper-level layers. To create the new method, first add the following using statement to the ProductBDO class: using LINQNorthwindBDO; Then add the following method to the ProductBDO class: public ProductBDO GetProduct(int id) { ProductBDO productBDO = null; using (var NWEntities = new NorthwindEntities()) { Product product = (from p in NWEntities.Products where p.ProductID == id select p).FirstOrDefault(); if (product != null) productBDO = new ProductBDO() { ProductID = product.ProductID, ProductName = product.ProductName, QuantityPerUnit = product.QuantityPerUnit, UnitPrice = (decimal)product.UnitPrice, UnitsInStock = (int)product.UnitsInStock, ReorderLevel = (int)product.ReorderLevel, UnitsOnOrder = (int)product.UnitsOnOrder, Discontinued = product.Discontinued, RowVersion = product.RowVersion }; } return productBDO; } Within the GetProduct method, we had to create an ADO.NET connection, create an ADO. NET command object with that connection, specify the command text, connect to the Northwind database, and send the SQL statement to the database for execution. After the result was returned from the database, we had to loop through the DataReader and cast the columns to our entity object one by one. With LINQ to Entities, we only construct one LINQ to Entities statement and everything else is handled by LINQ to Entities. Not only do we need to write less code, but now the statement is also strongly typed. We won't have a runtime error such as invalid query syntax or invalid column name. Also, a SQL Injection attack is no longer an issue, as LINQ to Entities will also take care of this when translating LINQ expressions to the underlying SQL statements. Creating UpdateProduct in the data access layer In the previous section, we created the GetProduct method in the data access layer, using LINQ to Entities instead of ADO.NET. Now in this section, we will create the UpdateProduct method, using LINQ to Entities instead of ADO.NET. Let's create the UpdateProduct method in the data access layer class ProductBDO, as follows: public bool UpdateProduct( ref ProductBDO productBDO, ref string message) { message = "product updated successfully"; bool ret = true; using (var NWEntities = new NorthwindEntities()) { var productID = productBDO.ProductID; Product productInDB = (from p in NWEntities.Products where p.ProductID == productID select p).FirstOrDefault(); // check product if (productInDB == null) { throw new Exception("No product with ID " + productBDO.ProductID); } NWEntities.Products.Remove(productInDB); // update product productInDB.ProductName = productBDO.ProductName; productInDB.QuantityPerUnit = productBDO.QuantityPerUnit; productInDB.UnitPrice = productBDO.UnitPrice; productInDB.Discontinued = productBDO.Discontinued; productInDB.RowVersion = productBDO.RowVersion; NWEntities.Products.Attach(productInDB); NWEntities.Entry(productInDB).State = System.Data.EntityState.Modified; int num = NWEntities.SaveChanges(); productBDO.RowVersion = productInDB.RowVersion; if (num != 1) { ret = false; message = "no product is updated"; } } return ret; } Within this method, we first get the product from database, making sure the product ID is a valid value in the database. Then, we apply the changes from the passed-in object to the object we have just retrieved from the database, and submit the changes back to the database. Let's go through a few notes about this method: You have to save productID in a new variable and then use it in the LINQ query. Otherwise, you will get an error saying Cannot use ref or out parameter 'productBDO' inside an anonymous method, lambda expression, or query expression. If Remove and Attach are not called, RowVersion from database (not from the client) will be used when submitting to database, even though you have updated its value before submitting to the database. An update will always succeed, but without concurrency control. If Remove is not called and you call the Attach method, you will get an error saying The object cannot be attached because it is already in the object context. If the object state is not set to be Modified, Entity Framework will not honor your changes to the entity object and you will not be able to save any change to the database. Creating the business logic layer Now let's create the business logic layer. Right click on the solution item and select Add | New Project.... Add a class library project with the name LINQNorthwindLogic. Add a project reference to LINQNorthwindDAL and LINQNorthwindBDO to this new project. Delete the Class1.cs file. Add a new class file ProductLogic.cs. Change the new class ProductLogic to be public. Add the following two using statements to the ProductLogic.cs class file: using LINQNorthwindDAL; using LINQNorthwindBDO; Add the following class member variable to the ProductLogic class: ProductDAO productDAO = new ProductDAO(); Add the following new method GetProduct to the ProductLogic class: public ProductBDO GetProduct(int id) { return productDAO.GetProduct(id); } Add the following new method UpdateProduct to the ProductLogic class: public bool UpdateProduct( ref ProductBDO productBDO, ref string message) { var productInDB = GetProduct(productBDO.ProductID); // invalid product to update if (productInDB == null) { message = "cannot get product for this ID"; return false; } // a product cannot be discontinued // if there are non-fulfilled orders if (productBDO.Discontinued == true && productInDB.UnitsOnOrder > 0) { message = "cannot discontinue this product"; return false; } else { return productDAO.UpdateProduct(ref productBDO, ref message); } } Build the solution. We now have only one more step to go, that is, adding the service interface layer. Creating the service interface layer The last step is to create the service interface layer. Right-click on the solution item and select Add | New Project.... Add a WCF service library project with the name of LINQNorthwindService. Add a project reference to LINQNorthwindLogic and LINQNorthwindBDO to this new service interface project. Change the service interface file IService1.cs, as follows: Change its filename from IService1.cs to IProductService.cs. Change the interface name from IService1 to IProductService, if it is not done for you. Remove the original two service operations and add the following two new operations: [OperationContract] [FaultContract(typeof(ProductFault))] Product GetProduct(int id); [OperationContract] [FaultContract(typeof(ProductFault))] bool UpdateProduct(ref Product product, ref string message); Remove the original CompositeType and add the following data contract classes: [DataContract] public class Product { [DataMember] public int ProductID { get; set; } [DataMember] public string ProductName { get; set; } [DataMember] public string QuantityPerUnit { get; set; } [DataMember] public decimal UnitPrice { get; set; } [DataMember] public bool Discontinued { get; set; } [DataMember] public byte[] RowVersion { get; set; } } [DataContract] public class ProductFault { public ProductFault(string msg) { FaultMessage = msg; } [DataMember] public string FaultMessage; } The following is the content of the IProductService.cs file: using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace LINQNorthwindService { [ServiceContract] public interface IProductService { [OperationContract] [FaultContract(typeof(ProductFault))] Product GetProduct(int id); [OperationContract] [FaultContract(typeof(ProductFault))] bool UpdateProduct(ref Product product, ref string message); } [DataContract] public class Product { [DataMember] public int ProductID { get; set; } [DataMember] public string ProductName { get; set; } [DataMember] public string QuantityPerUnit { get; set; } [DataMember] public decimal UnitPrice { get; set; } [DataMember] public bool Discontinued { get; set; } [DataMember] public byte[] RowVersion { get; set; } } [DataContract] public class ProductFault { public ProductFault(string msg) { FaultMessage = msg; } [DataMember] public string FaultMessage; } } Change the service implementation file Service1.cs, as follows: Change its filename from Service1.cs to ProductService.cs. Change its class name from Service1 to ProductService, if it is not done for you. Add the following two using statements to the ProductService.cs file: using LINQNorthwindLogic; using LINQNorthwindBDO; Add the following class member variable: ProductLogic productLogic = new ProductLogic(); Remove the original two methods and add following two methods: public Product GetProduct(int id) { ProductBDO productBDO = null; try { productBDO = productLogic.GetProduct(id); } catch (Exception e) { string msg = e.Message; string reason = "GetProduct Exception"; throw new FaultException<ProductFault> (new ProductFault(msg), reason); } if (productBDO == null) { string msg = string.Format("No product found for id {0}", id); string reason = "GetProduct Empty Product"; throw new FaultException<ProductFault> (new ProductFault(msg), reason); } Product product = new Product(); TranslateProductBDOToProductDTO(productBDO, product); return product; } public bool UpdateProduct(ref Product product, ref string message) { bool result = true; // first check to see if it is a valid price if (product.UnitPrice <= 0) { message = "Price cannot be <= 0"; result = false; } // ProductName can't be empty else if (string.IsNullOrEmpty(product.ProductName)) { message = "Product name cannot be empty"; result = false; } // QuantityPerUnit can't be empty else if (string.IsNullOrEmpty(product.QuantityPerUnit)) { message = "Quantity cannot be empty"; result = false; } else { try { var productBDO = new ProductBDO(); TranslateProductDTOToProductBDO(product, productBDO); result = productLogic.UpdateProduct( ref productBDO, ref message); product.RowVersion = productBDO.RowVersion; } catch (Exception e) { string msg = e.Message; throw new FaultException<ProductFault> (new ProductFault(msg), msg); } } return result; } Because we have to convert between the data contract objects and the business domain objects, we need to add the following two methods: private void TranslateProductBDOToProductDTO( ProductBDO productBDO, Product product) { product.ProductID = productBDO.ProductID; product.ProductName = productBDO.ProductName; product.QuantityPerUnit = productBDO.QuantityPerUnit; product.UnitPrice = productBDO.UnitPrice; product.Discontinued = productBDO.Discontinued; product.RowVersion = productBDO.RowVersion; } private void TranslateProductDTOToProductBDO( Product product, ProductBDO productBDO) { productBDO.ProductID = product.ProductID; productBDO.ProductName = product.ProductName; productBDO.QuantityPerUnit = product.QuantityPerUnit; productBDO.UnitPrice = product.UnitPrice; productBDO.Discontinued = product.Discontinued; productBDO.RowVersion = product.RowVersion; } Change the config file App.config, as follows: Change Service1 to ProductService. Remove the word Design_Time_Addresses. Change the port to 8080. Now, BaseAddress should be as follows: http://localhost:8080/LINQNorthwindService/ProductService/ Copy the connection string from the App.config file in the LINQNorthwindDAL project to the following App.config file: <connectionStrings> <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind. csdl|res://*/Northwind.ssdl|res://*/Northwind. msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=Northwind;integrated security=True;Multipl eActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings> You should leave the original connection string untouched in the App.config file in the data access layer project. This connection string is used by the Entity Model Designer at design time. It is not used at all during runtime, but if you remove it, whenever you open the entity model designer in Visual Studio, you will be prompted to specify a connection to your database. Now build the solution and there should be no errors. Testing the service with the WCF Test Client Now we can run the program to test the GetProduct and UpdateProduct operations with the WCF Test Client. You may need to run Visual Studio as administrator to start the WCF Test Client. First set LINQNorthwindService as the startup project and then press Ctrl + F5 to start the WCF Test Client. Double-click on the GetProduct operation, enter a valid product ID, and click on the Invoke button. The detailed product information should be retrieved and displayed on the screen, as shown in the following screenshot: Now double-click on the UpdateProduct operation, enter a valid product ID, and specify a name, price, quantity per unit, and then click on Invoke. This time you will get an exception as shown in the following screenshot: From this image we can see that the update failed. The error details, which are in HTML View in the preceding screenshot, actually tell us it is a concurrency error. This is because, from WCF Test Client, we can't enter a row version as it is not a simple datatype parameter, thus we didn't pass in the original RowVersion for the object to be updated, and when updating the object in the database, the Entity Framework thinks this product has been updated by some other users.
Read more
  • 0
  • 0
  • 4836

article-image-top-features-knockoutjs
Packt
04 Feb 2013
15 min read
Save for later

Top features of KnockoutJS

Packt
04 Feb 2013
15 min read
(For more resources related to this topic, see here.) Subscribables When creating our Inventory Management app, it was easy to see that a Knockout Observable is one of the core objects that Knockout was built around. However, under the covers of Observables, Observable Arrays, and Computed Observables is Subscribable. A Subscribable is simply an object that has three methods and an array of Subscriptions. The three methods are: subscribe: This adds a Subscription callback for a certain topic to be invoked when subscriptions are "notified". The default topic is "changed". notifySubscribers: This calls all subscriptions for a certain topic and passes one argument to all the Subscription's callbacks. extend: This applies an Extender to the Subscribable object. The following code snippet shows an example of how the first two methods allow Subscribables to perform basic Pub/Sub functionality: ar test = ko.observable(); // create a subscription for the "test-event" test.subscribe(function (val) { console.log(val); }, test, "test-event"); test.notifySubscribers("Hello World", "test-event"); One of the coolest abilities of a Knockout Subscribable is its ability to "mix-in" to any JavaScript object. The following code example shows a great way that you can leverage the Knockout Subscribable in some of your current code: // Dummy Subscribable function PubSub(){ // inherit Subscribable ko.subscribable.call(this); } // create an instance of our Subscribable var pubsub = new PubSub(); // make a subscription var subscription = pubsub.subscribe(function (val) { console.log(val); }, pubsub, 'test-topic'); pubsub.notifySubscribers("hello world", "test-topic"); // console: "hello world" // clean up things subscription.dispose(); Whenever we call the subscribe function of a Subscribable, we are returned a Subscription. Often developers will ignore the Subscription that they are returned from these calls, but it is important to understand that a Subscription allows you to properly dispose of your Subscription's callback and also your app's other objects that are referenced by that callback. Just simply calling dispose on your Subscription takes care of this, and you can ensure that your app doesn't create memory leaks! Now that you understand one of Knockout's absolute core building blocks, the Subscribable, we can learn how Knockout extends its usage into other building blocks. Observables The Knockout Observable is one of the first objects that leverages the Subscribable functionality, and is one of the most simple, yet powerful pieces of the Knockout library. The following shows the very basic idea of implementation of Observable: // Very Simple Knockout Observable Implementation // ko.observable is actually a function factory ko.observable = function (initialValue) { // private variable to hold the Observable's value var _latestValue = initialValue; // the actual "Observable" function function observable() { // one or more args, so it's a Write if (arguments.length > 0) { // set the private variable _latestValue = arguments[0]; // tell any subscribers that things have changed observable["notifySubscribers"](_latestValue); return this; // Permits chained assignments } else { // no args, so it's a Read // just hand back the private variable's value return _latestValue; } } // inherit from Subscribable ko.subscribable.call(observable); // return the freshly created Observable function return observable; }; Again, this is very basic example implementation of the Knockout Observable (the actual implementation has much more robust logic, equality checking, and dependency detection). You can see from its implementation that the ko.observable function is essentially a function factory. When you call this factory method, it generates a function (simply called observable) that provides a basic "get/set" API. If you invoke the returned function without an argument, then it returns the _initialValue, but if you invoke the function with an argument, it sets the _initialValue to that argument and notifies all subscribers. Observables are useful for just about anything you can imagine when building your application as they are event-driven. We can build an application architecture that only worries about reacting to events (such as a button click, or an input element's change event), rather than a procedural, single entry-point type of code base. Observable Arrays I briefly mentioned Observable Arrays earlier, and I promised that I would dig deeper into this concept. While the previous example implementation of the Observable is relatively simple, the Observable Array is even simpler, as shown in the following example implementation: // Very Simple Knockout Observable Array Implementation // function factory for observable arrays ko.observableArray = function (initialValues) { // make sure we have an array initialValues = initialValues || []; // create a Knockout Observable around our Array var result = ko.observable(initialValues); // add our Observable Array member functions // like "push", "pop", and so forth ko.utils.extend(result, ko.observableArray['fn']); // hand back the Observable we've created return result; }; As you can see, it is really just an Observable, except that the value of Observable is an Array. Functions have also been added to the Observable Array that match methods of a native Array. The Knockout authors have done this in order to allow us as developers to be able to work with an Observable Array in the same way we can work with normal Arrays. A little known extensibility point is the fn property of the Observable Array constructing function. You can add your own functions to this special property, and they will be included on all Observable Arrays that your application uses. The following implementation shows how we can easily add a function that filters items in the array: ko.observableArray.fn['filter'] = function (filterFunc) { // get the array var underlyingArray = this(); var result = []; for (var i = 0; i < underlyingArray.length; i++) { var value = underlyingArray[i]; // execute filter logic if (filterFunc(value)) { result.push(value); } } return result; }; var list = ko.observableArray([1, 2, 3]); // filter down the list to only odd numbers var odds = list.filter(function (item) { return (item % 2 === 1); }); console.log(odds); // [1, 3] The use of a fn property on constructor functions is a common pattern that you see in many libraries (including jQuery), and Knockout is no different. The fn property also exists on the Observable, so you can extend its functionality the same way. Computed Observables Computed Observables are arguably the most powerful feature of Knockout. You may have noticed that creating a ViewModel with nothing but Observables and Observable Arrays is a huge step up from plain JavaScript, but it would be really nice to have other properties on your ViewModel that simply depended upon other Observables and Observable Arrays and updated themselves when their dependencies change. Enter the Computed Observable (or in older versions of Knockout, the dependentObservable). A Computed Observable looks to be very similar to a regular Observable; however instead of holding a value, it contains a function that is used to evaluate the return value of the Computed Observable. The evaluation function is only executed when the Observables that it depends upon change. The following example shows a very basic example of how this works. var a = ko.observable(1); var b = ko.observable(2); var sum = ko.computed(function () { var total = a() + b(); // let's log every time this runs console.log(total); return total; }); // console: 3 a(2); // console: 4 b(3); // console: 5 b(3); // (nothing logged) Computed Observables have an extremely interesting and ingenious implementation, which you can dig into on your own. However, there are a few important things to note. First, Computed Observables build up a list of dependencies (or Subscriptions to be precise) when the evaluation function executes. If you want a certain Observable to be registered as a subscription to your Computed Observable, then you need to ensure that the Observable is always called in your evaluation function. The following example shows how you can set up a changing dependency set for a Computed Observable: var dep1 = ko.observable(1); var dep2 = ko.observable(2); var skipDep1 = false; var comp = ko.computed(function () { dep2(); // register dep2 as dependency if(!skipDep1) { dep1(); // register dep1 as dependency } console.log('evaluated'); }); // console: evaluated dep1(99); // console: evaluated skipDep1 = true; dep2(98); // console: evaluated dep1(97); // (nothing logged) Generally, it is a bad idea to do something like the previous example. It is extremely hard to debug, and is not intuitive for other developers to realize what is going on. Instead, the suggested pattern is to retrieve the value of each of your dependencies at the beginning of the evaluation function, and then perform any conditional logic on private scoped variables after that. Secondly, and this applies to Observables in general, a subscription callback will only be called if the Observable determines that the before and after values are different. Each Observable has a property called equalityComparer which determines if the before and after values of an Observable are in fact different. If you notice in the first example, the very last call to set the value of b did nothing. This is because b was already 3 and had no reason to notify its subscriptions. The default equalityComparer of each Observable is a function that only compares JavaScript primitives. So to make that simple, if a Computed Observable's dependencies are all objects, complex or not, the evaluator function will be re-executed every time one of its dependencies change. The following example provides an illustration of this important concept: var depPrimitive = ko.observable(1); var depObj = ko.observable({ val: 1 }); var comp = ko.computed(function () { // register dependencies var prim = depPrimitive(); var obj = depObj(); console.log("evaluated"); }); // console: evaluated depPrimitive(1); // (nothing logged) var previous = depObj(); depObj(previous); // console: evaluated It is important to understand this because many novice Knockout developers hurt themselves by overusing Computed Observables and accidentally building long event chains that ultimately lead to slow-performing applications. Lastly, Computed Observables can have both an evaluation function for "setting" and "getting" their values. This is incredibly useful, because it allows us to have private Observable fields on our ViewModel that are protected through publicly exposed "get/set" functions and we also get the power of a normal Observable. In the following code you can see an example of this useful concept: var _val = ko.observable(1); var vm = { val: ko.computed({ read: function () { return _val(); }, write: function (newVal) { _val(newVal); } }) }; vm.val(2); console.log(_val()); // console: 2 _val(3); console.log(vm.val()); // console: 3 Utilities Knockout is full of many useful utilities that you can use in your application development. You can find these by exploring the ko.utils namespace, but some of my favorite utilities are as follows: extend: This function "mashes" two objects together. Essentially all of the properties and functions of the second argument to this function call are added to the first argument. unwrapObservable: This function takes an object property and intelligently returns its value, figuring out whether it is a Knockout Observable or just a simple property. It is extremely useful if you have to write functions that might take an object, a primitive, or an Observable as an argument and you need it to figure that out properly at runtime. All of the Array utilities: Knockout has several Array-manipulation functions that allow you to do filtering, mapping, and removing items. I usually add these utilities to the special ko.observableArray.fn property when I first start a project. The following shows some example usage of these utilities: // extend usage var a = { val: 1 }, b = { val: 2 }; ko.utils.extend(a, b); console.log(a.val); // console: 2 // unwrapObservable usage var c = ko.observable(99), d = 98; console.log(ko.utils.unwrapObservable(c)); // console: 99 console.log(ko.utils.unwrapObservable(d)); // console: 98 // array "map" utility function usage var arr = [100, 101]; var mapped = ko.utils.arrayMap(arr, function (item) { return item + 50; }) console.log(arr); // console: [ 150, 151 ] Data-bind statements So far, we've focused on quite a bit of the JavaScript components of the Knockout library. However, Knockout was designed to make binding JavaScript objects and HTML extremely easy. The API that is given to us to use is through the HTML5 compliant data-bind statement. From the examples in our previous app, you may think that all the data-bind statements are simply matching an HTML element's attribute to a property of our ViewModel. In actuality, though, the data-bind syntax supports quite a bit more. We can actually implement small bits of JavaScript in these statements, and it will be evaluated correctly at runtime. The data-bind statement also allows declaring a comma-separated list of binding declarations. The following implementation shows some of the allowed syntaxes for data-bind statements on HTML elements: <span data-bind="text: myText"></span> <div data-bind="visible: isVisible, with: someProp"></div> <input data-bind="value: someVal, css: { 'error': !someVal.isValid(), 'success': someVal.isValid() }"/> Applying bindings The applyBindings function is where all of the Knockout magic gets kicked off. Many examples show the applyBindings function being called with a ViewModel object being passed in as the only argument, but you can also specify a DOM node as the second argument. When passing a DOM node as the second argument, Knockout only binds your ViewModel to that node and its children. Most basic applications are fine with only having one ViewModel, and just simply calling applyBindings with that single ViewModel. However, I've built several complex applications where we used multiple ViewModels for one page. It is important to consider the advantages of having a separate ViewModel in your application for, alerts, settings, and current user information. In some cases, you can also gain performance benefits by limiting the number of nodes that Knockout has to walk in order to finish its binding process. If you are only enhancing a small portion of your HTML page with Knockout, don't call applyBindings blindly across the entire DOM. Binding handlers I've mentioned Knockout's amazing extensibility points, and few are better than Knockout's binding handler objects. Every Knockout binding that you see in a data-bind statement is implemented as a binding handler, and Knockout allows us to define our own binding handlers so that we can implement, or override, our own custom functionality. With MVVM-style application development, we have two types of bindings—one-way bindings and two-way bindings. One-way bindings are simply read-only bindings, and are for pushing a ViewModel's property updates through to the DOM. You can probably guess that two-way bindings are bindings that take that one step further and allow DOM changes to be pushed back through to a ViewModel's property. Knockout allows us to create both of these types of bindings. The following shows a very basic template for a binding handler: ko.bindingHandlers['myHandler'] = { init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext){ }, update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext){ } }; As we can see, we are provided two hooks for implementing our own logic—init and update functions that have the same signature. The arguments for those functions are as follows: element: The HTML element that the data-bind statement is declared on. valueAccessor: A function that returns the ViewModel value to which the binding is set. If the binding is set to an Observable property, then the Observable is returned (and we'll need to unwrap within our logic). allBindingsAccessor: Similar to the valueAccessor, but it returns an object that contains all the bindings and their respective bound values. viewModel: The ViewModel or root object that was passed into the applyBindings statement. bindingContext: A special object that has specific properties that represent the data context of the current binding. The binding context has a $data property that represents the currently bound context (which most often would be same as the ViewModel argument). It also has $parent and $parents properties that represent any data contexts that may be bound to elements higher in the DOM tree. We usually only use the parent properties if we have used the with binding in our application. You might be wondering, which functions we should implement our custom logic in since they both look to do the same thing. The init function will be called only once when applyBindings function has been called. Knockout walks the DOM, finds data-bind statements, processes them, and calls the init method on each binding handler that is needed. The update function is then called immediately after the init function is called, and then also whenever the value it is bound to changes (if the value is a Subscribable). My rule-of-thumb for binding handlers is that I register all of my event handlers (change, blur, focus) in the init function, then I perform my HTML manipulation in the update function. The following code snippet shows a common one-way binding that I add to my projects. It simply reverses the Boolean logic of the visible binding so that I can handle situations where a ViewModel property makes more sense to be developed as vm.isHidden(); instead of vm.isVisible();. // invisible -> the inverse of 'visible' ko.bindingHandlers['invisible'] = { update: function (element, valueAccessor) { var newValueAccessor = function () { // just return the opposite of the visible flag! return !ko.utils.unwrapObservable(valueAccessor()); }; return ko.bindingHandlers.visible.update(element, newValueAccessor); } }; The following code snippet shows a two-way binding that I use for ensuring I have a valid number. It manually handles the updates from the UI and pushes those to the ViewModel, and then also updates the UI when the ViewModel changes. // simple number parsing function parseNumber(strVal){ return parseInt(strVal, 10); } // very basic two-way binding handler ko.bindingHandlers['number'] = { init: function (element, valueAccessor, allBindingsAccessor) { //handle the input changing ko.utils.registerEventHandler(element, "change", function () { var observable = valueAccessor(); var number = parseNumber(element.value); if (number !== NaN) { observable(element.value); } }); }, update: function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); var number = parseNumber(value); if (number !== NaN) { element.setAttribute("value", number); } } }; Summary All in all you can see that Knockout is a very mature and thorough JavaScript library. In this article you have learned: The core objects of Knockout and their usage Knockout utilities How to create and manage an app with multiple ViewModels Custom Binding Handlers Knockout has a lot of goodies under the hood. I've included many snippets of Knockout's actual source code in this section so that you won't be afraid to look through the source code and learn more about how you can take advantage of its APIs. Resources for Article : Further resources on this subject: Framework Comparison: Backbase AJAX framework Vs Other Similar Framework (Part 1) [Article] Framework Comparison: Backbase AJAX framework Vs Other Similar Framework (Part 2) [Article] How to Build a RSS Reader for Windows Phone 7 [Article]
Read more
  • 0
  • 0
  • 4176

article-image-marker-based-augmented-reality-iphone-or-ipad
Packt
01 Feb 2013
23 min read
Save for later

Marker-based Augmented Reality on iPhone or iPad

Packt
01 Feb 2013
23 min read
(For more resources related to this topic, see here.) Creating an iOS project that uses OpenCV In this section we will create a demo application for iPhone/iPad devices that will use the OpenCV ( Open Source Computer Vision ) library to detect markers in the camera frame and render 3D objects on it. This example will show you how to get access to the raw video data stream from the device camera, perform image processing using the OpenCV library, find a marker in an image, and render an AR overlay. We will start by first creating a new XCode project by choosing the iOS Single View Application template, as shown in the following screenshot: Now we have to add OpenCV to our project. This step is necessary because in this application we will use a lot of functions from this library to detect markers and estimate position position. OpenCV is a library of programming functions for real-time computer vision. It was originally developed by Intel and is now supported by Willow Garage and Itseez. This library is written in C and C++ languages. It also has an official Python binding and unofficial bindings to Java and .NET languages. Adding OpenCV framework Fortunately the library is cross-platform, so it can be used on iOS devices. Starting from version 2.4.2, OpenCV library is officially supported on the iOS platform and you can download the distribution package from the library website at http://opencv.org/. The OpenCV for iOS link points to the compressed OpenCV framework. Don't worry if you are new to iOS development; a framework is like a bundle of files. Usually each framework package contains a list of header files and list of statically linked libraries. Application frameworks provide an easy way to distribute precompiled libraries to developers. Of course, you can build your own libraries from scratch. OpenCV documentation explains this process in detail. For simplicity, we follow the recommended way and use the framework for this article. After downloading the file we extract its content to the project folder, as shown in the following screenshot: To inform the XCode IDE to use any framework during the build stage, click on Project options and locate the Build phases tab. From there we can add or remove the list of frameworks involved in the build process. Click on the plus sign to add a new framework, as shown in the following screenshot: From here we can choose from a list of standard frameworks. But to add a custom framework we should click on the Add other button. The open file dialog box will appear. Point it to opencv2.framework in the project folder as shown in the following screenshot: Including OpenCV headers Now that we have added the OpenCV framework to the project, everything is almost done. One last thing—let's add OpenCV headers to the project's precompiled headers. The precompiled headers are a great feature to speed up compilation time. By adding OpenCV headers to them, all your sources automatically include OpenCV headers as well. Find a .pch file in the project source tree and modify it in the following way. The following code shows how to modify the .pch file in the project source tree: // // Prefix header for all source files of the 'Example_MarkerBasedAR' // #import <Availability.h> #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __cplusplus #include <opencv2/opencv.hpp> #endif #ifdef __OBJC__ #import <UIKit/UIKit.h> #import <Foundation/Foundation.h> #endif Now you can call any OpenCV function from any place in your project. That's all. Our project template is configured and we are ready to move further. Free advice: make a copy of this project; this will save you time when you are creating your next one! Application architecture Each iOS application contains at least one instance of the UIViewController interface that handles all view events and manages the application's business logic. This class provides the fundamental view-management model for all iOS apps. A view controller manages a set of views that make up a portion of your app's user interface. As part of the controller layer of your app, a view controller coordinates its efforts with model objects and other controller objects—including other view controllers—so your app presents a single coherent user interface. The application that we are going to write will have only one view; that's why we choose a Single-View Application template to create one. This view will be used to present the rendered picture. Our ViewController class will contain three major components that each AR application should have (see the next diagram): Video source Processing pipeline Visualization engine The video source is responsible for providing new frames taken from the built-in camera to the user code. This means that the video source should be capable of choosing a camera device (front- or back-facing camera), adjusting its parameters (such as resolution of the captured video, white balance, and shutter speed), and grabbing frames without freezing the main UI. The image processing routine will be encapsulated in the MarkerDetector class. This class provides a very thin interface to user code. Usually it's a set of functions like processFrame and getResult. Actually that's all that ViewController should know about. We must not expose low-level data structures and algorithms to the view layer without strong necessity. VisualizationController contains all logic concerned with visualization of the Augmented Reality on our view. VisualizationController is also a facade that hides a particular implementation of the rendering engine. Low code coherence gives us freedom to change these components without the need to rewrite the rest of your code. Such an approach gives you the freedom to use independent modules on other platforms and compilers as well. For example, you can use the MarkerDetector class easily to develop desktop applications on Mac, Windows, and Linux systems without any changes to the code. Likewise, you can decide to port VisualizationController on the Windows platform and use Direct3D for rendering. In this case you should write only new VisualizationController implementation; other code parts will remain the same. The main processing routine starts from receiving a new frame from the video source. This triggers video source to inform the user code about this event with a callback. ViewController handles this callback and performs the following operations: Sends a new frame to the visualization controller. Performs processing of the new frame using our pipeline. Sends the detected markers to the visualization stage. Renders a scene. Let's examine this routine in detail. The rendering of an AR scene includes the drawing of a background image that has a content of the last received frame; artificial 3D objects are drawn later on. When we send a new frame for visualization, we are copying image data to internal buffers of the rendering engine. This is not actual rendering yet; we are just updating the text with a new bitmap. The second step is the processing of new frame and marker detection. We pass our image as input and as a result receive a list of the markers detected. on it. These markers are passed to the visualization controller, which knows how to deal with them. Let's take a look at the following sequence diagram where this routine is shown: We start development by writing a video capture component. This class will be responsible for all frame grabbing and for sending notifications of captured frames via user callback. Later on we will write a marker detection algorithm. This detection routine is the core of your application. In this part of our program we will use a lot of OpenCV functions to process images, detect contours on them, find marker rectangles, and estimate their position. After that we will concentrate on visualization of our results using Augmented Reality. After bringing all these things together we will complete our first AR application. So let's move on! Accessing the camera The Augmented Reality application is impossible to create without two major things: video capturing and AR visualization. The video capture stage consists of receiving frames from the device camera, performing necessary color conversion, and sending it to the processing pipeline. As the single frame processing time is so critical to AR applications, the capture process should be as efficient as possible. The best way to achieve maximum performance is to have direct access to the frames received from the camera. This became possible starting from iOS Version 4. Existing APIs from the AVFoundation framework provide the necessary functionality to read directly from image buffers in memory. You can find a lot of examples that use the AVCaptureVideoPreviewLayer class and the UIGetScreenImage function to capture videos from the camera. This technique was used for iOS Version 3 and earlier. It has now become outdated and has two major disadvantages: Lack of direct access to frame data. To get a bitmap, you have to create an intermediate instance of UIImage, copy an image to it, and get it back. For AR applications this price is too high, because each millisecond matters. Losing a few frames per second (FPS) significantly decreases overall user experience. To draw an AR, you have to add a transparent overlay view that will present the AR. Referring to Apple guidelines, you should avoid non-opaque layers because their blending is hard for mobile processors. Classes AVCaptureDevice and AVCaptureVideoDataOutput allow you to configure, capture, and specify unprocessed video frames in 32 bpp BGRA format. Also you can set up the desired resolution of output frames. However, it does affect overall performance since the larger the frame the more processing time and memory is required. There is a good alternative for high-performance video capture. The AVFoundation API offers a much faster and more elegant way to grab frames directly from the camera. But first, let's take a look at the following figure where the capturing process for iOS is shown: AVCaptureSession is a root capture object that we should create. Capture session requires two components—an input and an output. The input device can either be a physical device (camera) or a video file (not shown in diagram). In our case it's a built-in camera (front or back). The output device can be presented by one of the following interfaces: AVCaptureMovieFileOutput AVCaptureStillImageOutput AVCaptureVideoPreviewLayer AVCaptureVideoDataOutput The AVCaptureMovieFileOutput interface is used to record video to the file, the AVCaptureStillImageOutput interface is used to to make still images, and the AVCaptureVideoPreviewLayer interface is used to play a video preview on the screen. We are interested in the AVCaptureVideoDataOutput interface because it gives you direct access to video data. The iOS platform is built on top of the Objective-C programming language. So to work with AVFoundation framework, our class also has to be written in Objective-C. In this section all code listings are in the Objective-C++ language. To encapsulate the video capturing process, we create the VideoSource interface as shown by the following code: @protocol VideoSourceDelegate<NSObject> -(void)frameReady:(BGRAVideoFrame) frame; @end @interface VideoSource : NSObject<AVCaptureVideoDataOutputSampleBuffe rDelegate> { } @property (nonatomic, retain) AVCaptureSession *captureSession; @property (nonatomic, retain) AVCaptureDeviceInput *deviceInput; @property (nonatomic, retain) id<VideoSourceDelegate> delegate; - (bool) startWithDevicePosition:(AVCaptureDevicePosition) devicePosition; - (CameraCalibration) getCalibration; - (CGSize) getFrameSize; @end In this callback we lock the image buffer to prevent modifications by any new frames, obtain a pointer to the image data and frame dimensions. Then we construct temporary BGRAVideoFrame object that is passed to outside via special delegate. This delegate has following prototype: @protocol VideoSourceDelegate<NSObject> -(void)frameReady:(BGRAVideoFrame) frame; @end Within VideoSourceDelegate, the VideoSource interface informs the user code that a new frame is available. The step-by-step guide for the initialization of video capture is listed as follows: Create an instance of AVCaptureSession and set the capture session quality preset. Choose and create AVCaptureDevice. You can choose the front- or backfacing camera or use the default one. Initialize AVCaptureDeviceInput using the created capture device and add it to the capture session. Create an instance of AVCaptureVideoDataOutput and initialize it with format of video frame, callback delegate, and dispatch the queue. Add the capture output to the capture session object. Start the capture session. Let's explain some of these steps in more detail. After creating the capture session, we can specify the desired quality preset to ensure that we will obtain optimal performance. We don't need to process HD-quality video, so 640 x 480 or an even lesser frame resolution is a good choice: - (id)init { if ((self = [super init])) { AVCaptureSession * capSession = [[AVCaptureSession alloc] init]; if ([capSession canSetSessionPreset:AVCaptureSessionPreset64 0x480]) { [capSession setSessionPreset:AVCaptureSessionPreset640x480]; NSLog(@"Set capture session preset AVCaptureSessionPreset640x480"); } else if ([capSession canSetSessionPreset:AVCaptureSessionPresetL ow]) { [capSession setSessionPreset:AVCaptureSessionPresetLow]; NSLog(@"Set capture session preset AVCaptureSessionPresetLow"); } self.captureSession = capSession; } return self; } Always check hardware capabilities using the appropriate API; there is no guarantee that every camera will be capable of setting a particular session preset. After creating the capture session, we should add the capture input—the instance of AVCaptureDeviceInput will represent a physical camera device. The cameraWithPosition function is a helper function that returns the camera device for the requested position (front, back, or default): - (bool) startWithDevicePosition:(AVCaptureDevicePosition) devicePosition { AVCaptureDevice *videoDevice = [self cameraWithPosition:devicePosit ion]; if (!videoDevice) return FALSE; { NSError *error; AVCaptureDeviceInput *videoIn = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; self.deviceInput = videoIn; if (!error) { if ([[self captureSession] canAddInput:videoIn]) { [[self captureSession] addInput:videoIn]; } else { NSLog(@"Couldn't add video input"); return FALSE; } } else { NSLog(@"Couldn't create video input"); return FALSE; } } [self addRawViewOutput]; [captureSession startRunning]; return TRUE; } Please notice the error handling code. Take care of return values for such an important thing as working with hardware setup is a good practice. Without this, your code can crash in unexpected cases without informing the user what has happened. We created a capture session and added a source of the video frames. Now it's time to add a receiver—an object that will receive actual frame data. The AVCaptureVideoDataOutput class is used to process uncompressed frames from the video stream. The camera can provide frames in BGRA, CMYK, or simple grayscale color models. For our purposes the BGRA color model fits best of all, as we will use this frame for visualization and image processing. The following code shows the addRawViewOutput function: - (void) addRawViewOutput { /*We setupt the output*/ AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init]; /*While a frame is processes in -captureOutput:didOutputSampleBuff er:fromConnection: delegate methods no other frames are added in the queue. If you don't want this behaviour set the property to NO */ captureOutput.alwaysDiscardsLateVideoFrames = YES; /*We create a serial queue to handle the processing of our frames*/ dispatch_queue_t queue; queue = dispatch_queue_create("com.Example_MarkerBasedAR. cameraQueue", NULL); [captureOutput setSampleBufferDelegate:self queue:queue]; dispatch_release(queue); // Set the video output to store frame in BGRA (It is supposed to be faster) NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]; NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; [captureOutput setVideoSettings:videoSettings]; // Register an output [self.captureSession addOutput:captureOutput]; } Now the capture session is finally configured. When started, it will capture frames from the camera and send it to user code. When the new frame is available, an AVCaptureSession object performs a captureOutput: didOutputSampleBuffer:fromConnection callback. In this function, we will perform a minor data conversion operation to get the image data in a more usable format and pass it to user code: - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // Get a image buffer holding video frame CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer (sampleB uffer); // Lock the image buffer CVPixelBufferLockBaseAddress(imageBuffer,0); // Get information about the image uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(image Buffer); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); size_t stride = CVPixelBufferGetBytesPerRow(imageBuffer); BGRAVideoFrame frame = {width, height, stride, baseAddress}; [delegate frameReady:frame]; /*We unlock the image buffer*/ CVPixelBufferUnlockBaseAddress(imageBuffer,0); } We obtain a reference to the image buffer that stores our frame data. Then we lock it to prevent modifications by new frames. Now we have exclusive access to the frame data. With help of the CoreVideo API, we get the image dimensions, stride (number of pixels per row), and the pointer to the beginning of the image data. I draw your attention to the CVPixelBufferLockBaseAddress/ CVPixelBufferUnlockBaseAddress function call in the callback code. Until we hold a lock on the pixel buffer, it guarantees consistency and correctness of its data. Reading of pixels is available only after you have obtained a lock. When you're done, don't forget to unlock it to allow the OS to fill it with new data. Marker detection A marker is usually designed as a rectangle image holding black and white areas inside it. Due to known limitations, the marker detection procedure is a simple one. First of all we need to find closed contours on the input image and unwarp the image inside it to a rectangle and then check this against our marker model. In this sample the 5 x 5 marker will be used. Here is what it looks like: In the sample project that you will find in this book, the marker detection routine is encapsulated in the MarkerDetector class: /** * A top-level class that encapsulate marker detector algorithm */ class MarkerDetector { public: /** * Initialize a new instance of marker detector object * @calibration[in] - Camera calibration necessary for pose estimation. */ MarkerDetector(CameraCalibration calibration); void processFrame(const BGRAVideoFrame& frame); const std::vector<Transformation>& getTransformations() const; protected: bool findMarkers(const BGRAVideoFrame& frame, std::vector<Marker>& detectedMarkers); void prepareImage(const cv::Mat& bgraMat, cv::Mat& grayscale); void performThreshold(const cv::Mat& grayscale, cv::Mat& thresholdImg); void findContours(const cv::Mat& thresholdImg, std::vector<std::vector<cv::Point> >& contours, int minContourPointsAllowed); void findMarkerCandidates(const std::vector<std::vector<cv::Point> >& contours, std::vector<Marker>& detectedMarkers); void detectMarkers(const cv::Mat& grayscale, std::vector<Marker>& detectedMarkers); void estimatePosition(std::vector<Marker>& detectedMarkers); private: }; To help you better understand the marker detection routine, a step-by-step processing on one frame from a video will be shown. A source image taken from an iPad camera will be used as an example: Marker identification Here is the workflow of the marker detection routine: Convert the input image to grayscale. Perform binary threshold operation. Detect contours. Search for possible markers. Detect and decode markers. Estimate marker 3D pose. Grayscale conversion The conversion to grayscale is necessary because markers usually contain only black and white blocks and it's much easier to operate with them on grayscale images. Fortunately, OpenCV color conversion is simple enough. Please take a look at the following code listing in C++: void MarkerDetector::prepareImage(const cv::Mat& bgraMat, cv::Mat& grayscale) { // Convert to grayscale cv::cvtColor(bgraMat, grayscale, CV_BGRA2GRAY); } This function will convert the input BGRA image to grayscale (it will allocate image buffers if necessary) and place the result into the second argument. All further steps will be performed with the grayscale image. Image binarization The binarization operation will transform each pixel of our image to black (zero intensity) or white (full intensity). This step is required to find contours. There are several threshold methods; each has strong and weak sides. The easiest and fastest method is absolute threshold. In this method the resulting value depends on current pixel intensity and some threshold value. If pixel intensity is greater than the threshold value, the result will be white (255); otherwise it will be black (0). This method has a huge disadvantage—it depends on lighting conditions and soft intensity changes. The more preferable method is the adaptive threshold. The major difference of this method is the use of all pixels in given radius around the examined pixel. Using average intensity gives good results and secures more robust corner detection. The following code snippet shows the MarkerDetector function: void MarkerDetector::performThreshold(const cv::Mat& grayscale, cv::Mat& thresholdImg) { cv::adaptiveThreshold(grayscale, // Input image thresholdImg,// Result binary image 255, // cv::ADAPTIVE_THRESH_GAUSSIAN_C, // cv::THRESH_BINARY_INV, // 7, // 7 // ); } After applying adaptive threshold to the input image, the resulting image looks similar to the following one: Each marker usually looks like a square figure with black and white areas inside it. So the best way to locate a marker is to find closed contours and approximate them with polygons of 4 vertices. Contours detection The cv::findCountours function will detect contours on the input binary image: void MarkerDetector::findContours(const cv::Mat& thresholdImg, std::vector<std::vector<cv::Point> >& contours, int minContourPointsAllowed) { std::vector< std::vector<cv::Point> > allContours; cv::findContours(thresholdImg, allContours, CV_RETR_LIST, CV_ CHAIN_APPROX_NONE); contours.clear(); for (size_t i=0; i<allContours.size(); i++) { int contourSize = allContours[i].size(); if (contourSize > minContourPointsAllowed) { contours.push_back(allContours[i]); } } } The return value of this function is a list of polygons where each polygon represents a single contour. The function skips contours that have their perimeter in pixels value set to be less than the value of the minContourPointsAllowed variable. This is because we are not interested in small contours. (They will probably contain no marker, or the contour won't be able to be detected due to a small marker size.) The following figure shows the visualization of detected contours: Candidates search After finding contours, the polygon approximation stage is performed. This is done to decrease the number of points that describe the contour shape. It's a good quality check to filter out areas without markers because they can always be represented with a polygon that contains four vertices. If the approximated polygon has more than or fewer than 4 vertices, it's definitely not what we are looking for. The following code implements this idea: void MarkerDetector::findCandidates ( const ContoursVector& contours, std::vector<Marker>& detectedMarkers ) { std::vector<cv::Point> approxCurve; std::vector<Marker> possibleMarkers; // For each contour, analyze if it is a parallelepiped likely to be the marker for (size_t i=0; i<contours.size(); i++) { // Approximate to a polygon double eps = contours[i].size() * 0.05; cv::approxPolyDP(contours[i], approxCurve, eps, true); // We interested only in polygons that contains only four points if (approxCurve.size() != 4) continue; // And they have to be convex if (!cv::isContourConvex(approxCurve)) continue; // Ensure that the distance between consecutive points is large enough float minDist = std::numeric_limits<float>::max(); for (int i = 0; i < 4; i++) { cv::Point side = approxCurve[i] - approxCurve[(i+1)%4]; float squaredSideLength = side.dot(side); minDist = std::min(minDist, squaredSideLength); } // Check that distance is not very small if (minDist < m_minContourLengthAllowed) continue; // All tests are passed. Save marker candidate: Marker m; for (int i = 0; i<4; i++) m.points.push_back( cv::Point2f(approxCurve[i].x,approxCu rve[i].y) ); // Sort the points in anti-clockwise order // Trace a line between the first and second point. // If the third point is at the right side, then the points are anticlockwise cv::Point v1 = m.points[1] - m.points[0]; cv::Point v2 = m.points[2] - m.points[0]; double o = (v1.x * v2.y) - (v1.y * v2.x); if (o < 0.0) //if the third point is in the left side, then sort in anti-clockwise order std::swap(m.points[1], m.points[3]); possibleMarkers.push_back(m); } // Remove these elements which corners are too close to each other. // First detect candidates for removal: std::vector< std::pair<int,int> > tooNearCandidates; for (size_t i=0;i<possibleMarkers.size();i++) { const Marker& m1 = possibleMarkers[i]; //calculate the average distance of each corner to the nearest corner of the other marker candidate for (size_t j=i+1;j<possibleMarkers.size();j++) { const Marker& m2 = possibleMarkers[j]; float distSquared = 0; for (int c = 0; c < 4; c++) { cv::Point v = m1.points[c] - m2.points[c]; distSquared += v.dot(v); } distSquared /= 4; if (distSquared < 100) { tooNearCandidates.push_back(std::pair<int,int>(i,j)); } } } // Mark for removal the element of the pair with smaller perimeter std::vector<bool> removalMask (possibleMarkers.size(), false); for (size_t i=0; i<tooNearCandidates.size(); i++) { float p1 = perimeter(possibleMarkers[tooNearCandidates[i]. first ].points); float p2 = perimeter(possibleMarkers[tooNearCandidates[i].second]. points); size_t removalIndex; if (p1 > p2) removalIndex = tooNearCandidates[i].second; else removalIndex = tooNearCandidates[i].first; removalMask[removalIndex] = true; } // Return candidates detectedMarkers.clear(); for (size_t i=0;i<possibleMarkers.size();i++) { if (!removalMask[i]) detectedMarkers.push_back(possibleMarkers[i]); } } Now we have obtained a list of parallelepipeds that are likely to be the markers. To verify whether they are markers or not, we need to perform three steps: First, we should remove the perspective projection so as to obtain a frontal view of the rectangle area. Then we perform thresholding of the image using the Otsu algorithm. This algorithm assumes a bimodal distribution and finds the threshold value that maximizes the extra-class variance while keeping a low intra-class variance. Finally we perform identification of the marker code. If it is a marker, it has an internal code. The marker is divided into a 7 x 7 grid, of which the internal 5 x 5 cells contain ID information. The rest correspond to the external black border. Here, we first check whether the external black border is present. Then we read the internal 5 x 5 cells and check if they provide a valid code. (It might be required to rotate the code to get the valid one.) To get the rectangle marker image, we have to unwarp the input image using perspective transformation. This matrix can be calculated with the help of the cv::getPerspectiveTransform function. It finds the perspective transformation from four pairs of corresponding points. The first argument is the marker coordinates in image space and the second point corresponds to the coordinates of the square marker image. Estimated transformation will transform the marker to square form and let us analyze it: cv::Mat canonicalMarker; Marker& marker = detectedMarkers[i]; // Find the perspective transfomation that brings current marker to rectangular form cv::Mat M = cv::getPerspectiveTransform(marker.points, m_ markerCorners2d); // Transform image to get a canonical marker image cv::warpPerspective(grayscale, canonicalMarker, M, markerSize); Image warping transforms our image to a rectangle form using perspective transformation: Now we can test the image to verify if it is a valid marker image. Then we try to extract the bit mask with the marker code. As we expect our marker to contain only black and white colors, we can perform Otsu thresholding to remove gray pixels and leave only black and white pixels: //threshold image cv::threshold(markerImage, markerImage, 125, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
Read more
  • 0
  • 0
  • 10994

article-image-behavior-driven-development-selenium-webdriver
Packt
31 Jan 2013
15 min read
Save for later

Behavior-driven Development with Selenium WebDriver

Packt
31 Jan 2013
15 min read
Behavior-driven Development (BDD) is an agile software development practice that enhances the paradigm of Test Driven Development (TDD) and acceptance tests, and encourages the collaboration between developers, quality assurance, domain experts, and stakeholders. Behavior-driven Development was introduced by Dan North in the year 2003 in his seminal article available at http://dannorth.net/introducing-bdd/. In this article by Unmesh Gundecha, author of Selenium Testing Tools Cookbook, we will cover: Using Cucumber-JVM and Selenium WebDriver in Java for BDD Using SpecFlow.NET and Selenium WebDriver in .NET for BDD Using JBehave and Selenium WebDriver in Java Using Capybara, Cucumber, and Selenium WebDriver in Ruby (For more resources related to this topic, see here.) Using Cucumber-JVM and Selenium WebDriver in Java for BDD BDD/ATDD is becoming widely accepted practice in agile software development, and Cucumber-JVM is a mainstream tool used to implement this practice in Java. Cucumber-JVM is based on Cucumber framework, widely used in Ruby on Rails world. Cucumber-JVM allows developers, QA, and non-technical or business participants to write features and scenarios in a plain text file using Gherkin language with minimal restrictions about grammar in a typical Given, When, and Then structure. This feature file is then supported by a step definition file, which implements automated steps to execute the scenarios written in a feature file. Apart from testing APIs with Cucumber-JVM, we can also test UI level tests by combining Selenium WebDriver. In this recipe, we will use Cucumber-JVM, Maven, and Selenium WebDriver for implementing tests for the fund transfer feature from an online banking application. Getting ready Create a new Maven project named FundTransfer in Eclipse. Add the following dependencies to POM.XML: <project xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http:// maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>FundTransfer</groupId> <artifactId>FundTransfer</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>1.0.14</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>1.0.14</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>2.25.0</version> </dependency> </dependencies> </project> How to do it... Perform the following steps for creating BDD/ATDD tests with Cucumber-JVM: Select the FundTransfer project in Package Explorer in Eclipse. Select and right-click on src/test/resources in Package Explorer. Select New | Package from the menu to add a new package as shown in the following screenshot: Enter fundtransfer.test in the Name: textbox and click on the Finish button. Add a new file to this package. Name this file as fundtransfer.feature as shown in the following screenshot: Add the Fund Transfer feature and scenarios to this file: Feature: Customer Transfer's Fund As a customer, I want to transfer funds so that I can send money to my friends and family Scenario: Valid Payee Given the user is on Fund Transfer Page When he enters "Jim" as payee name And he enters "100" as amount And he Submits request for Fund Transfer Then ensure the fund transfer is complete with "$100 transferred successfully to Jim!!" message Scenario: Invalid Payee Given the user is on Fund Transfer Page When he enters "Jack" as payee name And he enters "100" as amount And he Submits request for Fund Transfer Then ensure a transaction failure message "Transfer failed!! 'Jack' is not registered in your List of Payees" is displayed Scenario: Account is overdrawn past the overdraft limit Given the user is on Fund Transfer Page When he enters "Tim" as payee name And he enters "1000000" as amount And he Submits request for Fund Transfer Then ensure a transaction failure message "Transfer failed!! account cannot be overdrawn" is displayed Select and right-click on src/test/java in Package Explorer. Select New | Package from menu to add a new Package as shown in the following screenshot: Create a class named FundTransferStepDefs in the newly-created package. Add the following code to this class: package fundtransfer.test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.By; import cucumber.annotation.*; import cucumber.annotation.en.*; import static org.junit.Assert.assertEquals; public class FundTransferStepDefs { protected WebDriver driver; @Before public void setUp() { driver = new ChromeDriver(); } @Given("the user is on Fund Transfer Page") public void The_user_is_on_fund_transfer_page() { driver.get("http://dl.dropbox.com/u/55228056/fundTransfer. html"); } @When("he enters "([^"]*)" as payee name") public void He_enters_payee_name(String payeeName) { driver.findElement(By.id("payee")).sendKeys(payeeName); } @And("he enters "([^"]*)" as amount") public void He_enters_amount(String amount) { driver.findElement(By.id("amount")).sendKeys(amount); } @And("he Submits request for Fund Transfer") public void He_submits_request_for_fund_transfer() { driver.findElement(By.id("transfer")).click(); Behavior-driven Development 276 } @Then("ensure the fund transfer is complete with "([^"]*)" message") public void Ensure_the_fund_transfer_is_complete(String msg) { WebElement message = driver.findElement(By.id("message")); assertEquals(message.getText(),msg); } @Then("ensure a transaction failure message "([^"]*)" is displayed") public void Ensure_a_transaction_failure_message(String msg) { WebElement message = driver.findElement(By.id("message")); assertEquals(message.getText(),msg); } @After public void tearDown() { driver.close(); } } Create a support class RunCukesTest which will define the Cucumber-JVM configurations: package fundtransfer.test; import cucumber.junit.Cucumber; import org.junit.runner.RunWith; @RunWith(Cucumber.class) @Cucumber.Options(format = {"pretty", "html:target/cucumber-htmlreport", "json-pretty:target/cucumber-report.json"}) public class RunCukesTest { } To run the tests in Maven life cycle select the FundTransfer project in Package Explorer. Right-click on the project name and select Run As | Maven test. Maven will execute all the tests from the project. At the end of the test, an HTML report will be generated as shown in the following screenshot. To view this report open index.html in the targetcucumber-htmlreport folder: How it works... Creating tests in Cucumber-JVM involves three major steps: writing a feature file, implementing automated steps using the step definition file, and creating support code as needed. For writing features, Cucumber-JVM uses 100 percent Gherkin syntax. The feature file describes the feature and then the scenarios to test the feature: Feature: Customer Transfer's Fund As a customer, I want to transfer funds so that I can send money to my friends and family You can write as many scenarios as needed to test the feature in the feature file. The scenario section contains the name and steps to execute the defined scenario along with test data required to execute that scenario with the application: Scenario: Valid Payee Given the user is on Fund Transfer Page When he enters "Jim" as payee name And he enters "100" as amount And he Submits request for Fund Transfer Then ensure the fund transfer is complete with "$100 transferred successfully to Jim!!" message Team members use these feature files and scenarios to build and validate the system. Frameworks like Cucumber or JBehave provide an ability to automatically validate the features by allowing us to implement automated steps. For this we need to create the step definition file that maps the steps from the feature file to automation code. Step definition files implement a method for steps using special annotations. For example, in the following code, the @When annotation is used to map the step "When he enters "Jim" as payee name" from the feature file in the step definition file. When this step is to be executed by the framework, the He_enters_payee_name() method will be called by passing the data extracted using regular expressions from the step: @When("he enters "([^"]*)" as payee name") public void He_enters_payee_name(String payeeName) { driver.findElement(By.id("payee")).sendKeys(payeeName); } In this method, the WebDriver code is written to locate the payee name textbox and enter the name value using the sendKeys() method. The step definition file acts like a template for all the steps from the feature file while scenarios can use a mix and match of the steps based on the test conditions. A helper class RunCukesTest is defined to provide Cucumber-JVM configurations such as how to run the features and steps with JUnit, report format, and location, shown as follows: @RunWith(Cucumber.class) @Cucumber.Options(format = {"pretty", "html:target/cucumber-htmlreport", "json-pretty:target/cucumber-report.json"}) public class RunCukesTest { } There's more… In this example, step definition methods are calling Selenium WebDriver methods directly. However, a layer of abstraction can be created using the Page object where a separate class is defined with the definition of all the elements from FundTransferPage: import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.CacheLookup; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class FundTransferPage { @FindBy(id = "payee") @CacheLookup Chapter 11 279 public WebElement payeeField; @FindBy(id = "amount") public WebElement amountField; @FindBy(id = "transfer") public WebElement transferButton; @FindBy(id = "message") public WebElement messageLabel; public FundTransferPage(WebDriver driver) { if(!"Online Fund Transfers".equals(driver.getTitle())) throw new IllegalStateException("This is not Fund Transfer Page"); PageFactory.initElements(driver, this); } } Using SpecFlow.NET and Selenium WebDriver in .NET for BDD We saw how to use Selenium WebDriver with Cucumber-JVM for BDD/ATDD. Now let's try using a similar combination in .NET using SpecFlow.NET. We can implement BDD in .NET using the SpecFlow.NET and Selenium WebDriver .NET bindings. SpecFlow.NET is inspired by Cucumber and uses the same Gherkin language for writing specs. In this recipe, we will implement tests for the Fund Transfer feature using SpecFlow.NET. We will also use the Page objects for FundTransferPage in this recipe. Getting ready This recipe is created with SpecFlow.NET Version 1.9.0 and Microsoft Visual Studio Professional 2012. Download and install SpecFlow from Visual Studio Gallery http://visualstudiogallery.msdn.microsoft.com/9915524d-7fb0-43c3-bb3c-a8a14fbd40ee. Download and install NUnit Test Adapter from http://visualstudiogallery.msdn.microsoft.com/9915524d-7fb0-43c3-bb3c-a8a14fbd40ee. This will install the project template and other support files for SpecFlow.NET in Visual Studio 2012. How to do it... You will find the Fund Transfer feature in any online banking application where users can transfer funds to a registered payee who could be a family member or a friend. Let's test this feature using SpecFlow.NET by performing the following steps: Launch Microsoft Visual Studio. In Visual Studio create a new project by going to File | New | Project. Select Visual C# Class Library Project. Name the project FundTransfer.specs as shown in the following screenshot: Next, add SpecFlow.NET, WebDriver, and NUnit using NuGet. Right-click on the FundTransfer.specs solution in Solution Explorer and select Manage NuGet Packages... as shown in the following screenshot: On the FundTransfer.specs - Manage NuGet Packages dialog box, select Online, and search for SpecFlow packages. The search will result with the following suggestions: Select SpecFlow.NUnit from the list and click on Install button. NuGet will download and install SpecFlow.NUnit and any other package dependencies to the solution. This will take a while. Next, search for the WebDriver package on the FundTransfer.specs - Manage NuGet Packages dialog box. Select Selenium WebDriver and Selenium WebDriver Support Classes from the list and click on the Install button. Close the FundTransfer.specs - Manage NuGet Packages dialog box. Creating a spec file The steps for creating a spec file are as follows: Right-click on the FundTransfer.specs solution in Solution Explorer. Select Add | New Item. On the Add New Item – FundTransfer.specs dialog box, select SpecFlow Feature File and enter FundTransfer.feature in the Name: textbox. Click Add button as shown in the following screenshot: In the Editor window, your will see the FundTransfer.feature tab. By default, SpecFlow will add a dummy feature in the feature file. Replace the content of this file with the following feature and scenarios: Feature: Customer Transfer's Fund As a customer, I want to transfer funds so that I can send money to my friends and family Scenario: Valid Payee Given the user is on Fund Transfer Page When he enters "Jim" as payee name And he enters "100" as amount And he Submits request for Fund Transfer Then ensure the fund transfer is complete with "$100 transferred successfully to Jim!!" message Scenario: Invalid Payee Given the user is on Fund Transfer Page When he enters "Jack" as payee name And he enters "100" as amount And he Submits request for Fund Transfer Then ensure a transaction failure message "Transfer failed!! 'Jack' is not registered in your List of Payees" is displayed Scenario: Account is overdrawn past the overdraft limit Given the user is on Fund Transfer Page When he enters "Tim" as payee name And he enters "1000000" as amount And he Submits request for Fund Transfer Then ensure a transaction failure message "Transfer failed!! account cannot be overdrawn" is displayed Creating a step definition file The steps for creating a step definition file are as follows: To add a step definition file, right-click on the FundTransfer.sepcs solution in Solution Explorer. Select Add | New Item. On the Add New Item - FundTransfer.specs dialog box, select SpecFlow Step Definition File and enter FundTransferStepDefs.cs in the Name: textbox. Click on Add button. A new C# class will be added with dummy steps. Replace the content of this file with the following code: using System; using System.Collections.Generic; using System.Linq; using System.Text; using TechTalk.SpecFlow; using NUnit.Framework; using OpenQA.Selenium; namespace FundTransfer.specs { [Binding] public class FundTransferStepDefs { FundsTransferPage _ftPage = new FundsTransferPage(Environment.Driver); [Given(@"the user is on Fund Transfer Page")] public void GivenUserIsOnFundTransferPage() { Environment.Driver.Navigate().GoToUrl("http:// localhost:64895/Default.aspx"); } [When(@"he enters ""(.*)"" as payee name")] public void WhenUserEneteredIntoThePayeeNameField(string payeeName) { _ftPage.payeeNameField.SendKeys(payeeName); } [When(@"he enters ""(.*)"" as amount")] public void WhenUserEneteredIntoTheAmountField(string amount) { _ftPage.amountField.SendKeys(amount); } [When(@"he enters ""(.*)"" as amount above his limit")] public void WhenUserEneteredIntoTheAmountFieldAboveLimit (string amount) { _ftPage.amountField.SendKeys(amount); } [When(@"he Submits request for Fund Transfer")] public void WhenUserPressTransferButton() { _ftPage.transferButton.Click(); } [Then(@"ensure the fund transfer is complete with ""(.*)"" message")] public void ThenFundTransferIsComplete(string message) { Assert.AreEqual(message, _ftPage.messageLabel.Text); } [Then(@"ensure a transaction failure message ""(.*)"" is displayed")] public void ThenFundTransferIsFailed(string message) { Assert.AreEqual(message, _ftPage.messageLabel.Text); } } } Defining a Page object and a helper class The steps for defining a Page object and a helper class are as follows: Define a Page object for the Fund Transfer Page by adding a new C# class file. Name this class FundTransferPage. Copy the following code to this class: using System; using System.Collections.Generic; using System.Linq; using System.Text; using OpenQA.Selenium; using OpenQA.Selenium.Support.PageObjects; namespace FundTransfer.specs { class FundTransferPage { public FundTransferPage(IWebDriver driver) { PageFactory.InitElements(driver, this); } [FindsBy(How = How.Id, Using = "payee")] public IWebElement payeeNameField { get; set; } [FindsBy(How = How.Id, Using = "amount")] public IWebElement amountField { get; set; } [FindsBy(How = How.Id, Using = "transfer")] public IWebElement transferButton { get; set; } [FindsBy(How = How.Id, Using = "message")] public IWebElement messageLabel { get; set; } } } We need a helper class that will provide an instance of WebDriver and perform clean up activity at the end. Name this class Environment and copy the following code to this class: using System; using System.Collections.Generic; using System.Linq; using System.Text; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using TechTalk.SpecFlow; namespace FundTransfer.specs { [Binding] public class Environment { private static ChromeDriver driver; public static IWebDriver Driver { get { return driver ?? (driver = new ChromeDriver(@"C:ChromeDriver")); } } [AfterTestRun] public static void AfterTestRun() { Driver.Close(); Driver.Quit(); driver = null; } } } Build the solution. Running tests The steps for running tests are as follows: Open the Test Explorer window by clicking the Test Explorer option on Test | Windows on Main Menu. It will display the three scenarios listed in the feature file as shown in the following screenshot: Click on Run All to test the feature as shown in the following screenshot: How it works... SpecFlow.NET first needs the feature files for the features we will be testing. SpecFlow.NET supports the Gherkin language for writing features. In the step definition file, we create a method for each step written in a feature file using the Given, When, and Then attributes. These methods can also take the parameter values specified in the steps using the arguments. Following is an example where we are entering the name of the payee: [When(@"he enters ""(.*)"" as payee name")] public void WhenUserEneteredIntoThePayeeNameField(string payeeName) { _ftPage.payeeNameField.SendKeys(payeeName); } In this example, we are automating the "When he enters "Jim" as payee name" step. We used the When attribute and created a method: WhenUserEneteredIntoThePayeeNameField. This method will need the value of the payee name embedded in the step which is extracted using the regular expression by the SpecFlow.NET. Inside the method, we are using an instance of the FundTransferPage class and calling its payeeNameField member's SendKeysl() method, passing the name of the payee extracted from the step. Using the Page object helps in abstracting locator and page details from the step definition files, making it more manageable and easy to maintain. SpecFlow.NET automatically generates the NUnit test code when the project is built. Using the Visual Studio Test Explorer and NUnit Test Adaptor for Visual Studio, these tests are executed and the features are validated.
Read more
  • 0
  • 0
  • 14538

article-image-article-getting-started-with-html5-modernizer-url
Packt
31 Jan 2013
4 min read
Save for later

Getting Started with Modernizr

Packt
31 Jan 2013
4 min read
(For more resources on this subject, see here.) Detect and design with features, not User agents (browsers) What if you could build your website based on features instead of for the individual browser idiosyncrasies by manufacturer and version, making your website not just backward compatible but also forward compatible? You could quite potentially build a fully backward and forward compatible experience using a single code base across the entire UA spectrum. What I mean by this is instead of baking in an MSIE 7.0 version, an MSIE 8.0 version , a Firefox version, and so on of your website, and then using JavaScript to listen for, or sniff out, the browser version in play, it would be much simpler to instead build a single version of your website that supports all of the older generation, latest generation, and in many cases even future generation technologies, such as a video API,box-shadow,and first-of-type. Think of your website as a full-fledged cable television network broadcasting over 130 channels, and your users as customers that sign up for only the most basic package available, of only 15 channels. Any time that they upgrade their cable (browser) package to one offering additional channels (features), they can begin enjoying them immediately because you have already been broadcasting to each one of those channels the entire time. What happens now is that a proverbial line is drawn in the sand, and the site is built on the assumption that a particular set of features will exist and are thus supported. If not, fallbacks are in place to allow a smooth degradation of the experience as usual, but more importantly the site is built to adopt features that the browser will eventually have. Modernizr can detect CSS features, such as @font-face , box-shadow , and CSS gradients. It can also detect HTML5 elements, such as canvas , localstorage, and application cache. In all it can detect over 40 features for you, the developer. Another term commonly used to describe this technique is "progressive enhancement ". When the time fi nally comes that the user decides to upgrade their browser, the new features that the more recent browser version brings with it, for example text-shadow , will automatically be detected and picked up by your website, to be leveraged by your site with no extra work or code from you when they do. Without any additional work on your part, any text that is assigned text-shadow attributes will turn on at the fl ick of a switch so that user's experience will smoothly, and progressively be enhanced. What is Modernizr? More importantly, why should you use it? At its foundation, Modernizr is a feature-detection library powered by none other than JavaScript. Here is an example of conditionally adding CSS classes based on the browser, also known as the User Agent . When the browser parses this HTML document and fi nds a match, that class will be conditionally added to the page. Now that the browser version has been found, the developer can use CSS to alter the page based on the version of the browser that is used to parse the page. In the following example, IE 7, IE 8, and IE 9 all use a different method for a drop shadow attribute on an anchor element: /* IE7 Conditional class using UA sniffing */ .lt-ie7 a{ display: block; float: left; background: url( drop-shadow. gif ); } .lt-ie8 a{ display: inline-block; background: url( drop-shadow.png ); } .lt-ie9 a{ display: inline-block; box-shadow: 10px 5px 5px rgba(0,0,0,0.5); } The problem with the conditional method of applying styles is that not only does it require more code, but it also leaves a burden on the developer to know what browser version is capable of a given feature, in this case box-shadow . Here is the same example using Modernizr . Note how Modernizr has done the heavy lifting for you, irrespective of whether or not the box-shadow feature is supported by the browser: /* Box shadow using Modernizr CSS feature detected classes */ .box-shadow a{ box-shadow: 10px 5px 5px rgba(0,0,0,0.5); } .no-box-shadow a{ background: url( drop-shadow.gif ); }   The Modernizr namespace The Modernizr JavaScript library in your web page's header is a lightweight feature library that will test your browser for the features that it may or may not support, and store those results in a JavaScript namespace aptly named Modernizr. You can then use those test results as you build your website. From this point on everything you need to know about what features your user's browser can and cannot support can be checked for in two separate places. Going back to the cable television analogy, you now know what channels (features) your user does and does not have, and can act accordingly.
Read more
  • 0
  • 0
  • 5100

article-image-why-coffeescript
Packt
31 Jan 2013
9 min read
Save for later

Why CoffeeScript?

Packt
31 Jan 2013
9 min read
(For more resources related to this topic, see here.) CoffeeScript CoffeeScript compiles to JavaScript and follows its idioms closely. It's quite possible to rewrite any CoffeeScript code in Javascript and it won't look drastically different. So why would you want to use CoffeeScript? As an experienced JavaScript programmer, you might think that learning a completely new language is simply not worth the time and effort. But ultimately, code is for programmers. The compiler doesn't care how the code looks or how clear its meaning is; either it will run or it won't. We aim to write expressive code as programmers so that we can read, reference, understand, modify, and rewrite it. If the code is too complex or filled with needless ceremony, it will be harder to understand and maintain. CoffeeScript gives us an advantage to clarify our ideas and write more readable code. It's a misconception to think that CoffeeScript is very different from JavaScript. There might be some drastic syntax differences here and there, but in essence, CoffeeScript was designed to polish the rough edges of JavaScript to reveal the beautiful language hidden beneath. It steers programmers towards JavaScript's so-called "good parts" and holds strong opinions of what constitutes good JavaScript. One of the mantras of the CoffeeScript community is: "It's just JavaScript", and I have also found that the best way to truly comprehend the language is to look at how it generates its output, which is actually quite readable and understandable code. Throughout this article, we'll highlight some of the differences between the two languages, often focusing on the things in JavaScript that CoffeeScript tries to improve. In this way, I would not only like to give you an overview of the major features of the language, but also prepare you to be able to debug your CoffeeScript from its generated code once you start using it more often, as well as being able to convert existing JavaScript. Let's start with some of the things CoffeeScript fixes in JavaScript. CoffeeScript syntax One of the great things about CoffeeScript is that you tend to write much shorter and more succinct programs than you normally would in JavaScript. Some of this is because of the powerful features added to the language, but it also makes a few tweaks to the general syntax of JavaScript to transform it to something quite elegant. It does away with all the semicolons, braces, and other cruft that usually contributes to a lot of the "line noise" in JavaScript. To illustrate this, let's look at an example. On the left-hand side of the following table is CoffeeScript; on the right-hand side is the generated JavaScript: CoffeeScript JavaScript fibonacci = (n) -> return 0 if n == 0 return 1 if n == 1 (fibonacci n-1) + (fibonacci n-2) alert fibonacci 10 var fibonacci; fibonacci = function(n) { if (n === 0) { return 0; } if (n === 1) { return 1; } return (fibonacci(n - 1)) + (fibonacci(n - 2)); }; alert(fibonacci(10)); To run the code examples in this article, you can use the great Try CoffeeScript online tool, at http://coffeescript.org. It allows you to type in CoffeeScript code, which will then display the equivalent JavaScript in a side pane. You can also run the code right from the browser (by clicking the Run button in the upper-left corner). At first, the two languages might appear to be quite drastically different, but hopefully as we go through the differences, you'll see that it's all still JavaScript with some small tweaks and a lot of nice syntactical sugar. Semicolons and braces As you might have noticed, CoffeeScript does away with all the trailing semicolons at the end of a line. You can still use a semicolon if you want to put two expressions on a single line. It also does away with enclosing braces (also known as curly brackets) for code blocks such as if statements, switch, and the try..catch block. Whitespace You might be wondering how the parser figures out where your code blocks start and end. The CoffeeScript compiler does this by using syntactical whitespace. This means that indentation is used for delimited code blocks instead of braces. This is perhaps one of the most controversial features of the language. If you think about it, in almost all languages, programmers tend to already use indentation of code blocks to improve readability, so why not make it part of the syntax? This is not a new concept, and was mostly borrowed from Python. If you have any experience with significant whitespace language, you will not have any trouble with CoffeeScript indentation. If you don't, it might take some getting used to, but it makes for code that is wonderfully readable and easy to scan, while shaving off quite a few keystrokes. I'm willing to bet that if you do take the time to get over some initial reservations you might have, you might just grow to love block indentation. Blocks can be indented with tabs or spaces, but be careful about being consistent using one or the other, or CoffeeScript will not be able to parse your code correctly. Parenthesis You'll see that the clause of the if statement does not need be enclosed within parentheses. The same goes for the alert function; you'll see that the single string parameter follows the function call without parentheses as well. In CoffeeScript, parentheses are optional in function calls with parameters, clauses for if..else statements, as well as while loops. Although functions with arguments do not need parentheses, it is still a good idea to use them in cases where ambiguity might exist. The CoffeeScript community has come up with a nice idiom: wrapping the whole function call in parenthesis. The use of the alert function in CoffeeScript is shown in the following table: CoffeeScript JavaScript alert square 2 * 2.5 + 1 alert(square(2 * 2.5 + 1)); alert (square 2 * 2.5) + 1 alert((square(2 * 2.5)) + 1); Functions are first class objects in JavaScript. This means that when you refer to a function without parentheses, it will return the function itself, as a value. Thus, in CoffeeScript you still need to add parentheses when calling a function with no arguments. By making these few tweaks to the syntax of JavaScript, CoffeeScript arguably already improves the readability and succinctness of your code by a big factor, and also saves you quite a lot of keystrokes. But it has a few other tricks up its sleeve. Most programmers who have written a fair amount of JavaScript would probably agree that one of the phrases that gets typed the most frequently would have to be the function definition function(){}. Functions are really at the heart of JavaScript, yet not without its many warts. CoffeeScript has great function syntax The fact that you can treat functions as first class objects as well as being able to create anonymous functions is one of JavaScript's most powerful features. However, the syntax can be very awkward and make the code hard to read (especially if you start nesting functions). But CoffeeScript has a fix for this. Have a look at the following snippets: CoffeeScript JavaScript -> alert 'hi there!' square = (n) -> n * n var square; (function() { return alert('hi there!'); }); square = function(n) { return n * n; }; Here, we are creating two anonymous functions, the first just displays a dialog and the second will return the square of its argument. You've probably noticed the funny -> symbol and might have figured out what it does. Yep, that is how you define a function in CoffeeScript. I have come across a couple of different names for the symbol but the most accepted term seems to be a thin arrow or just an arrow. Notice that the first function definition has no arguments and thus we can drop the parenthesis. The second function does have a single argument, which is enclosed in parenthesis, which goes in front of the -> symbol. With what we now know, we can formulate a few simple substitution rules to convert JavaScript function declarations to CoffeeScript. They are as follows: Replace the function keyword with -> If the function has no arguments, drop the parenthesis If it has arguments, move the whole argument list with parenthesis in front of the -> symbol Make sure that the function body is properly indented and then drop the enclosing braces Return isn't required You might have noted that in both the functions, we left out the return keyword. By default, CoffeeScript will return the last expression in your function. It will try to do this in all the paths of execution. CoffeeScript will try turning any statement (fragment of code that returns nothing) into an expression that returns a value. CoffeeScript programmers will often refer to this feature of the language by saying that everything is an expression. This means you don't need to type return anymore, but keep in mind that this can, in many cases, alter your code subtly, because of the fact that you will always return something. If you need to return a value from a function before the last statement, you can still use return. Function arguments Function arguments can also take an optional default value. In the following code snippet you'll see that the optional value specified is assigned in the body of the generated Javascript: CoffeeScript JavaScript square = (n=1) -> alert(n * n) var square; square = function(n) { if (n == null) { n = 1; } return alert(n * n); }; In JavaScript, each function has an array-like structure called arguments with an indexed property for each argument that was passed to the function. You can use arguments to pass in a variable number of parameters to a function. Each parameter will be an element in arguments and thus you don't have to refer to parameters by name. Although the arguments object acts somewhat like an array, it is in not in fact a "real" array and lacks most of the standard array methods. Often, you'll find that arguments doesn't provide the functionality needed to inspect and manipulate its elements like they are used with an array. Summary We saw how it can help you write shorter, cleaner, and more elegant code than you normally would in JavaScript and avoid many of its pitfalls. We came to realize that even though CoffeeScripts' syntax seems to be quite different from JavaScript, it actually maps pretty closely to its generated output. Resources for Article : Further resources on this subject: ASP.Net Site Performance: Improving JavaScript Loading [Article] Build iPhone, Android and iPad Applications using jQTouch [Article] An Overview of the Node Package Manager [Article]
Read more
  • 0
  • 0
  • 7687
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
article-image-creating-your-course-presenter
Packt
30 Jan 2013
16 min read
Save for later

Creating Your Course with Presenter

Packt
30 Jan 2013
16 min read
(For more resources related to this topic, see here.) Animating images and objects Animating your objects is done on the Animations ribbon. Please note that this ribbon is different on PowerPoint 2010 than it is on PowerPoint 2007. Just about everything you do will be the same, however the locations and names of some things may be different. For the purpose of this book, we're going to look at PowerPoint 2010, but we will tell you about the differences. Getting ready To start we'll need a slide with a couple of objects on it. You can copy a slide from an existing presentation, putting it into a new presentation to experiment with. I'm going to use the first slide from our "Mary Had a Little Lamb" presentation. So that we can do anything we want to with this slide, I'm going to add a few buttons and an arrow to it, giving us some more objects to work with. When animating objects, it can be extremely useful to group them. This causes the animation to act on the group of objects as if they are one object. To group two or more objects, select them together, then right-click on one of the objects. Click on the Group button; it will open another fly-out menu, then click on Group. How to do it... Animations can only be done to already existing objects. Often, it is easiest to place all the objects on the slide before beginning the animations. That makes it easier to see how the animations will react with one another. For animating images and objects, perform the following steps: Open up the Selections pane by clicking on the Selection Pane button in the Arrange section of the Format ribbon. This will help you keep track of the various objects on the slide and their order of layering. To rename the objects, click on the name in the Selections pane and type in a new name. The small button to the right-hand side of each object's name is for making the object visible or invisible. Open the Animation pane in the Animations ribbon. For PowerPoint 2010, click on the Animation Pane button in the Advanced Animation section of the ribbon. For PowerPoint 2007, click on the Custom Animation button in the Animations section of the Animations ribbon. Adding an animation to a particular object consists of selecting the object and then selecting the animation for it. On the 2010 ribbon, you can add an animation either by clicking on the animation's icon in the center part of the ribbon, or by clicking on the Add Animation button in the Advanced Animations section of the ribbon. Clicking on this button opens the Animation drop-down menu, as shown in the following screenshot: Select the animation you want by clicking on it. How it works... When you first open the Selection pane, the objects will all appear with names such as Rounded Rectangle 12, TextBox 13, and Picture 14. This numbering will be sequential in the order that the objects have been added to that slide. The order in which the objects appear in the pane is the order in which they are layered in the slide. The top object that is listed is the top one in the order. To change the order, right-click on the object and select Bring to Top or Send to Bottom on the context-sensitive menu. PowerPoint works like a desktop publishing program in the same way that it handles the ordering and layering of the objects. If you were doing the design that you are doing in PowerPoint on paper, gluing blocks of text, pictures, and other objects to the paper would have caused some of them to overlap others. This is the concept of "layering". Every object that you put on a slide is layered even if none of them overlap each other. When they do overlap each other, the objects that were added later overlap the earlier ones. This can be changed by changing the object order, using Bring to Top, Send Forward, Send Backward, and Send to Bottom in the context-sensitive menu. The Animation dropbox itself shows the most popular animations and is divided into four, color-coded sections for the type of animation that is to be added, as follows: The Entrance animations are green The Emphasis animations are yellow or gray The Exit animations are red The Motion Paths animations are multicolored In addition, there are links at the bottom of the dialog box to access more animations than the ones shown in the dropbox. Selecting an animation automatically adds it to the Animations pane, so that you can see it. The animations you have added will be listed in the Animations pane in the order in which they run. There will be an icon of a mouse as well, to show that the animation is intended to run with the click of a mouse. The numbers to the left-hand side of the animation tell you what order they are in. If an animation does not have a number to its left-hand side, it is set to run with the previous one. If it has a clock face to the left-hand side, it is intended to run after the previous animation, as shown in the following screenshot: Even though we're setting the animations to start with a mouse click, that won't actually be happening in the Flash video. Later we'll be sequencing the animations, syncing them with the narration. We will need them to be activated by a mouse click in order to do this. There's more… There are a number of different changes you can make to how your animations are run. Animation timing Articulate offers the capability to precisely time your animations, which is much easier than what PowerPoint offers. This makes it easier to match the timing of the animations with the narration. Perform the following steps: By right-clicking on any of the animations, you can access a context-sensitive menu, which allows you to select the animations to run on a mouse click, concurrent with the previous animation or after the previous animation. This can be done using options such as Start On Click, Start With Previous, and Start After Previous, as shown in the following screenshot: For some animations you can change other settings, such as the direction that the object enters the slide from. This is done by clicking on Effect Options... in the context-sensitive menu. When the dialog box opens, you can set Direction or any other setting particular to that animation in the upper part of the dialog box. You can add sound effects to the animation using the Sound drop-down menu in the lower part of the dialog box. Clicking on Timing in the context-sensitive menu opens a dialog box that allows you to change the speed with which the animations run, as shown in the following screenshot: There are five settings available in the Speed drop-down menu, from very slow to very fast. As you can see from the preceding screenshot, the program tells you how long the animation will run for at each speed. Using the Repeat drop-down menu, you can set the animation to repeat itself a set number of times until the slide ends or until the next mouse click. The context-sensitive menu also allows you to open an advanced timeline by clicking on Show Advanced Timeline, which will allow you to further fine-tune your animation and its timing, including adding a delay between it and the previous animation. The timeline view allows you to see how the overlap and running of your animations will come out in terms of seconds. You never want to have the first animation in the slide run automatically; you only want to have it run on the mouse click. Setting it to Start With Previous or Start After Previous is not compatible with Flash animation. Multiple animations Multiple animations can be used for a single object. You can bring the item in with an entrance animation, have a second animation performed on it for emphasis, and then have it exit with an exit animation. These can either be one after the other, or with other things happening in between them. These additional animations are added in the same way that the first animation was added in the main part of this recipe. Additional animations will show up in the list in the order in which they were added. They will also play in this order. Checking your animations To check your animation, you can click on the Play button at the top of the Animation pane. If you would like to run it as a slide slow, you will need to click on the Slide Show icon, which is located in the information bar at the bottom of the PowerPoint window, as shown in the following screenshot: A word about style Your viewers are not going to be impressed by presentations filled with too many animations that are all too common. Therefore, it is of utmost importance to use animations with discretion. They are a great way to add additional objects to a slide, if they can do so without being a distraction. Adding audio narration to your slides Articulate Presenter allows the use of two different types of audio in your presentation. The first is the audio track, which provides background music for your presentation. The second is narration. This program automatically adjusts the volume of your background music whenever there is narration, avoiding competition between the two. There are two ways of creating your narration in Articulate Presenter, either by recording the narration right into the presentation, or by having your narration recorded professionally and importing it into your presentation. In this section, we will look at how both of these methods are accomplished. Getting ready To record narration directly into your presentation, you will need a microphone connected to your computer. It is worthwhile buying a good quality microphone, especially if you are going to be doing a large number of presentations. The sound quality that you can get off a good quality microphone is better than a cheap one. You don't want to use the microphone that's in your webcam. Not only is this not a high-quality microphone, but the distance between you and the microphone will make you sound like you're speaking from inside a tunnel. Ideally, a microphone should be between three to six inches from your mouth, pointed directly at your mouth. Avoid moving your head from side to side as you speak, as this will make your volume level go up and down. The following are some of the key points to look at before you add an audio narration to your slide: A windscreen on your microphone is a good investment as it will help prevent the noise from your breathing on it. You may also want to consider a desktop mic stand so that you don't have to hold it in your hand. Before recording your narration, it's a good idea to have it written out. Many people think that they can do it off the cuff, but when they get in front of the mic, they forget everything that they were going to say. A great place to write out your script is in the notes area at the bottom of the PowerPoint screen. How to do it... We are going to record the narration directly into the presentation using Articulate Presenter's built-in recording function. Perform the following steps to do so: Before recording your narration, you need to ensure that your presentation is ready to receive your recording. To do this, open the Presentation Options dialog box from the Articulate ribbon. On the Other tab, make sure that the Show notes pane on narration window and Record narration for one slide at a time checkboxes under the Recording section are both checked, as shown in the following screenshot: Open the recording screen by clicking on the Record Narration button in the Narration section of the Articulate ribbon. If you have not yet saved your presentation, you will be asked to do so before the Record Narration screen opens. Your PowerPoint screen will seem to disappear when you open the recording screen. Don't worry, you haven't lost it; when you finish recording your narration, it will appear again. If you are using multiple monitors for your computer, the Narration recording screen will always appear on the far right monitor. So if you have something there that you will need to access, you may want to move it before entering the record mode. To begin recording, click on the START RECORDING button on the Articulate ribbon. While you are recording, the START RECORDING button changes to STOP RECORDING. When you have finished recording the narration for the slide, click on the STOP RECORDING button. If you click on the START RECORDING button again after you've stopped recording, it will start the recording again, overwriting what you just recorded. As you are recording, the length of the recording you are making will show in the yellow message bar and will be broken down into hours, minutes, seconds, and tenths of a second. You can check your recording using the Play and Stop buttons to the right-hand side of the START RECORDING button. These buttons are identified with the standard graphical symbols for play and record. Now that you have recorded the narration for the first slide, you can move to the next slide using the right and left arrow buttons to the right-hand side of the Record and Play buttons. You can also select which slide to edit using the drop-down menu located below these buttons. To the right-hand side of the Play and Record buttons, there is an area for selecting the slide that you want to record the narration for. To verify which slides you have already recorded, click on the small arrow pointing downwards below the slide number in the ribbon. This will open a dropbox with a list of all the slides and thumbnails. All the slides that have a narration recorded will show a small icon of a microphone. Above this, they will tell you the duration of the narration that you have recorded, as shown in the following screenshot: To exit the narration recorder and return to PowerPoint, click on the Save & Close button on the ribbon. How it works... Your narrations will be saved in a new file, which has the same name as you gave your presentation, with the .ppta filename extension. The file will be automatically created at this time, if the program has not already created it. If you have to move your presentation for any reason, be sure to move this file along with the .ppta file, which is the presentation. Otherwise you will lose your narrations. There's more... Not only can you record narrations, but you can also import them into the presentation. You may choose to do this using professional "voice" for a specific voice style or for a more professional presentation. Importing narrations into your presentation If you decide to use professionally recorded narrations using professional talent, you will probably not be able to record them with Articulate's recorder. This isn't a problem, as you can very easily import those recordings into your presentation. There are some technical requirements for your recordings. They must be recorded in either the .wav or .mp3 format. Between the two, you are better off using the .wav format files as they are not compressed like the .mp3 files. This means that your finished presentation will be a bigger file, but it will provide a better sound quality for editing. They must be recorded at a sampling rate of 44.1 kHz, 16-bit resolution, and either stereo or mono. Many recording studios and artists prefer to use a resolution of 32 bits, however if you attempt to import 32- bit files into an Articulate presentation, all you will hear is a screech. Perform the following steps for importing narrations: To import these files, click on the Import Audio button in the Narration section of the Articulate ribbon. This will open the Import Audio dialog box. This dialog box contains a simple chart showing the slide numbers, the slide names, and the audio track for the narration. If you have recorded a narration for a slide, it will state the existing narration; if you have no narration, this column will be empty. Select the slide that you wish to import an audio file to by clicking on it. Then click on the Browse... button at the bottom of the screen. This will open a standard, Windows open file dialog box, where you can search for and select your audio file for that particular narration. You can select multiple narration files to be imported at once. Simply select the first file you need in the Windows open file dialog box, then hold down the Shift key and select the last. If the files are not sequentially located in the folder, you can hold down the Ctrl key, select each file individually, and then import them all together. When you do this, a new dialog box will open, allowing you to put the audio files in their correct order. The list of files will be shown in the central part of the dialog box. To change the order, select the file you wish to move, and use the Up, Down, Top, Bottom, and Reverse buttons on the right-hand side of the dialog box to move them as you need to. If you do not get the order of your narration files correct in the dialog box, you will need to individually change the audio files associated with the slides, as there is no way of moving them around in the Import Audio dialog box. Summary This article covered the basics of creating a simple course using Presenter by itself. It taught us the basics of inserting media elements and assets. Resources for Article : Further resources on this subject: Python Multimedia: Video Format Conversion, Manipulations and Effects [Article] Using Web Pages in UPK 3.5 [Article] Adding Flash to your WordPress Theme [Article]
Read more
  • 0
  • 0
  • 1556

article-image-layout-extnet
Packt
30 Jan 2013
16 min read
Save for later

Layout with Ext.NET

Packt
30 Jan 2013
16 min read
(For more resources related to this topic, see here.) Border layout The Border layout is perhaps one of the more popular layouts. While quite complex at first glance, it is popular because it turns out to be quite flexible to design and to use. It offers common elements often seen in complex web applications, such as an area for header content, footer content, a main content area, plus areas to either side. All are separately scrollable and resizable if needed, among other benefits. In Ext speak, these areas are called Regions, and are given names of North, South, Center, East, and West regions. Only the Center region is mandatory. It is also the one without any given dimensions; it will resize to fit the remaining area after all the other regions have been set. A West or East region must have a width defined, and North or South regions must have a height defined. These can be defined using the Width or Height property (in pixels) or using the Flex property which helps provide ratios. Each region can be any Ext.NET component; a very common option is Panel or a subclass of Panel. There are limits, however: for example, a Window is intended to be floating so cannot be one of the regions. This offers a lot of flexibility and can help avoid nesting too many Panels in order to show other components such as GridPanels or TabPanels, for example. Here is a screenshot showing a simple Border layout being applied to the entire page (that is, the viewport) using a 2-column style layout: We have configured a Border layout with two regions; a West region and a Center region. The Border layout is applied to the whole page (this is an example of using it with Viewport. Here is the code: <%@ Page Language="C#" %> <!DOCTYPE html> <html> <head runat="server"> <title>Border Layout Example</title> </head> <body> <ext:ResourceManager runat="server" Theme="Gray" /> <ext:Viewport runat="server" Layout="border"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="200" Collapsible="true" /> <ext:Panel Region="Center" Title="Center content" /> </Items> </ext:Viewport> </body> </html> The code has a Viewport configured with a Border layout via the Layout property. Then, into the Items collection two Panels are added, for the West and Center regions. The value of the Layout property is case insensitive and can take variations, such as Border, border, borderlayout, BorderLayout, and so on. As regions of a Border layout we can also configure options such as whether you want split bars, whether Panels are collapsible, and more. Our example uses the following: The West region Panel has been configured to be collapsible (using Collapsible="true"). This creates a small button in the title area which, when clicked, will smoothly animate the collapse of that region (which can then be clicked again to open it). When collapsed, the title area itself can also be clicked which will float the region into appearance, rather than permanently opening it (allowing the user to glimpse at the content and mouse away to close the region). This floating capability can be turned off by using Floatable="false" on the Panel. Split="true" gives a split bar with a collapse button between the regions. This next example shows a more complex Border layout where all regions are used: The markup used for the previous is very similar to the first example, so we will only show the Viewport portion: <ext:Viewport runat="server" Layout="border"> <Items> <ext:Panel Region="North" Split="true" Title="North" Height="75" Collapsible="true" /> <ext:Panel Region="West" Split="true" Title="West" Width="150" Collapsible="true" /> <ext:Panel runat="server" Region="Center" Title="Center content" /> <ext:Panel Region="East" Split="true" Title="East" Width="150" Collapsible="true" /> <ext:Panel Region="South" Split="true" Title="South" Height="75" Collapsible="true" /> </Items> </ext:Viewport> Although each Panel has a title set via the Title property, it is optional. For example, you may want to omit the title from the North region if you want an application header or banner bar, where the title bar could be superfluous. Different ways to create the same components The previous examples were shown using the specific Layout="Border" markup. However, there are a number of ways this can be marked up or written in code. For example, You can code these entirely in markup as we have seen You can create these entirely in code You can use a mixture of markup and code to suit your needs Here are some quick examples: Border layout from code This is the code version of the first two-panel Border layout example: <%@ Page Language="C#" %> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { var viewport = new Viewport { Layout = "border", Items = { new Ext.Net.Panel { Region = Region.West, Title = "West", Width = 200, Collapsible = true, Split = true }, new Ext.Net.Panel { Region = Region.Center, Title = "Center content" } } }; this.Form.Controls.Add(viewport); } </script> <!DOCTYPE html> <html> <head runat="server"> <title>Border Layout Example</title> </head> <body> <form runat="server"> <ext:ResourceManager runat="server" Theme="Gray" /> </form> </body> </html> There are a number of things going on here worth mentioning: The appropriate panels have been added to the Viewport's Items collection Finally, the Viewport is added to the page via the form's Controls Collection If you are used to programming with ASP.NET, you normally add a control to the Controls collection of an ASP.NET control. However, when Ext.NET controls add themselves to each other, it is usually done via the Items collection. This helps create a more optimal initialization script. This also means that only Ext.NET components participate in the layout logic. There is also the Content property in markup (or ContentControls property in code-behind) which can be used to add non-Ext.NET controls or raw HTML, though they will not take part in the layout. It is important to note that configuring Items and Content together should be avoided, especially if a layout is set on the parent container. This is because the parent container will only use the Items collection. Some layouts may hide the Content section altogether or have other undesired results. In general, use only one at a time, not both because the Viewport is the outer-most control; it is added to the Controls collection of the form itself. Another important thing to bear in mind is that the Viewport must be the only top-level visible control. That means it cannot be placed inside a div, for example it must be added directly to the body or to the <form runat="server"> only. In addition, there should not be any sibling controls (except floating widgets, like Window). Mixing markup and code The same 2-panel Border layout can also be mixed in various ways. For example: <%@ Page Language="C#" %> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { this.WestPanel.Title = "West"; this.WestPanel.Split = true; this.WestPanel.Collapsible = true; this.Viewport1.Items.Add(new Ext.Net.Panel { Region = Region.Center, Title = "Center content" }); } </script> <!DOCTYPE html> <html> <head runat="server"> <title>Border Layout Example</title> </head> <body> <ext:ResourceManager runat="server" /> <ext:Viewport ID="Viewport1" runat="server" Layout="Border"> <Items> <ext:Panel ID="WestPanel" runat="server" Region="West" Width="200" /> </Items> </ext:Viewport> </body> </html> In the previous example, the Viewport and the initial part of the West region have been defined in markup. The Center region Panel has been added via code and the rest of the West Panel's properties have been set in code-behind. As with most ASP. NET controls, you can mix and match these as you need. Loading layout items via User Controls A powerful capability that Ext.NET provides is being able to load layout components from User Controls. This is achieved by using the UserControlLoader component. Consider this example: <ext:Viewport runat="server" Layout="Border"> <Items> <ext:UserControlLoader Path="WestPanel.ascx" /> <ext:Panel Region="Center" /> </Items> </ext:Viewport> In this code, we have replaced the West region Panel that was used in earlier examples with a UserControlLoader component and set the Path property to load a user control in the same directory as this page. That user control is very simple for our example: <%@ Control Language="C#" %> <ext:Panel runat="server" Region="West" Split="true" Title="West" Width="200" Collapsible="true" /> In other words, we have simply moved our Panel from our earlier example into a user control and loaded that instead. Though a small example, this demonstrates some useful reuse capability. Also note that although we used the UserControlLoader in this Border layout example, it can be used anywhere else as needed, as it is an Ext.NET component. The containing component does not have to be a Viewport Note also that the containing component does not have to be a Viewport. It can be any other appropriate container, such as another Panel or a Window. Let's do just that: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="150" Collapsible="true" /> <ext:Panel Region="Center" Title="Center content" /> </Items> </ext:Window> The container has changed from a Viewport to a Window (with dimensions). It will produce this: More than one item with the same region In previous versions of Ext JS and Ext.NET you could only have one component in a given region, for example, only one North region Panel, one West region Panel, and so on. New to Ext.NET 2 is the ability to have more than one item in the same region. This can be very flexible and improve performance slightly. This is because in the past if you wanted the appearance of say multiple West columns, you would need to create nested Border layouts (which is still an option of course). But now, you can simply add two components to a Border layout and give them the same region value. Nested Border layouts are still possible in case the flexibility is needed (and helps make porting from an earlier version easier). First, here is an example using nested Border layouts to achieve three vertical columns: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="100" Collapsible="true" /> <ext:Panel Region="Center" Layout="Border" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="Inner West" Width="100" Collapsible="true" /> <ext:Panel Region="Center" Title="Inner Center" /> </Items> </ext:Panel> </Items> </ext:Window> This code will produce the following output: The previous code is only a slight variation of the example preceding it, but has a few notable changes: The Center region Panel has itself been given the layout as Border. This means that although this is a Center region for the window that it is a part of, this Panel is itself another Border layout. The nested Border layout then has two further Panels, an additional West region and an additional Center region. Note, the Title has also been removed from the outer Center region so that when they are rendered, they line up to look like three Panels next to each other. Here is the same example, but without using a nested border Panel and instead, just adding another West region Panel to the containing Window: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="100" Collapsible="true" /> <ext:Panel Region="West" Split="true" Title="Inner West" Width="100" Collapsible="true" /> <ext:Panel Region="Center" Title="Center content" Border="false" /> </Items> </ext:Window> Regions are not limited to Panels only A common problem with layouts is to start off creating more deeply nested controls than needed and the example earlier shows that it is not always needed. Multiple items with the same region helps to prevent nesting Border Layouts unnecessarily. Another inefficiency typical with the Border layout usage is using too many containing Panels in each region. For example, there may be a Center region Panel which then contains a TabPanel. However, as TabPanel is a subclass of Panel it can be given a region directly, therefore avoiding an unnecessary Panel to contain the TabPanel: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="False"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="100" Collapsible="True" /> <ext:TabPanel Region="Center"> <Items> <ext:Panel Title="First Tab" /> <ext:Panel Title="Second Tab" /> </Items> </ext:TabPanel> </Items> </ext:Window> This code will produce the following output: The differences with the nested Border layout example shown earlier are: The outer Center region has been changed from Panel to TabPanel. TabPanels manage their own items' layout so Layout="Border" is removed. The TabPanel also has Border="false" taken out (so it is true by default). The inner Panels have had their regions, Split, and other border related attributes taken out. This is because they are not inside a nested Border layout now; they are tabs. Other Panels, such as TreePanel or GridPanel, can also be used as we will see. Something that can be fiddly from time to time is knowing which borders to take off and which ones to keep when you have nested layouts and controls like this. There is a logic to it, but sometimes a quick bit of trial and error can also help figure it out! As a programmer this sounds minor and unimportant, but usually you want to prevent the borders becoming too thick, as aesthetically it can be off-putting, whereas just the right amount of borders can help make the application look clean and professional. You can always give components a class via the Cls property and then in CSS you can fine tune the borders (and other styles of course) as you need. Weighted regions Another feature new to Ext.NET 2 is that regions can be given weighting to influence how they are rendered and spaced out. Prior versions would require nested Border layouts to achieve this. To see how this works, consider this example to put a South region only inside the Center Panel: To achieve this output, if we used the old way—the nested Border layouts—we would do something like this: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="100" Collapsible="true" /> <ext:Panel Region="Center" Layout="Border" Border="false"> <Items> <ext:Panel Region="Center" Title="Center" /> <ext:Panel Region="South" Split="true" Title="South" Height="100" Collapsible="true" /> </Items> </ext:Panel> </Items> </ext:Window> In the preceding code, we make the Center region itself be a Border layout with an inner Center region and a South region. This way the outer West region takes up all the space on the left. If the South region was part of the outer Border layout, then it would span across the entire bottom area of the window. But the same effect can be achieved using weighting. This means you do not need nested Border layouts; the three Panels can all be items of the containing window, which means a few less objects being created on the client: <ext:Window runat="server" Layout="Border" Height="200" Width="400" Border="false"> <Items> <ext:Panel Region="West" Split="true" Title="West" Width="100" Collapsible="true" Weight="10" /> <ext:Panel Region="Center" Title="Center" /> <ext:Panel Region="South" Split="true" Title="South" Height="100" Collapsible="true" /> </Items> </ext:Window> The way region weights work is that the region with the highest weight is assigned space from the border before other regions. If more than one region has the same weight as another, they are assigned space based on their position in the owner's Items collection (that is first come, first served). In the preceding code, we set the Weight property to 10 to the West region only, so it is rendered first and, thus, takes up all the space it can before the other two are rendered. This allows for many flexible options and Ext.NET has an example where you can configure different values to see the effects of different weights: http://examples.ext.net/#/Layout/BorderLayout/Regions_Weights/ As the previous examples show, there are many ways to define the layout, offering you more flexibility, especially if generating from code-behind in a very dynamic way. Knowing that there are so many ways to define the layout, we can now speed up our look at many other types of layouts. Summary This article covered one of the numerous layout options available in Ext.NET, that is, the Border layout, to help you organize your web applications. Resources for Article : Further resources on this subject: Your First ASP.NET MVC Application [Article] Customizing and Extending the ASP.NET MVC Framework [Article] Tips & Tricks for Ext JS 3.x [Article]
Read more
  • 0
  • 0
  • 4842

article-image-null-16
Packt
30 Jan 2013
4 min read
Save for later

Getting Started with HTML5 Modernizr

Packt
30 Jan 2013
4 min read
Detect and design with features, not User Agents (browsers) What if you could build your website based on features instead of for the individual browser idiosyncrasies by manufacturer and version, making your website not just backward compatible but also forward compatible? You could quite potentially build a fully backward and forward compatible experience using a single code base across the entire UA spectrum. What I mean by this is instead of baking in an MSIE 7.0 version, an MSIE 8.0 version , a Firefox version, and so on of your website, and then using JavaScript to listen for, or sniff out, the browser version in play, it would be much simpler to instead build a single version of your website that supports all of the older generation, latest generation, and in many cases even future generation technologies, such as a video API , box-shadow, first-of-type , and more. Think of your website as a full-fledged cable television network broadcasting over 130 channels, and your users as customers that sign up for only the most basic package available, of only 15 channels. Any time that they upgrade their cable (browser) package to one offering additional channels (features), they can begin enjoying them immediately because you have already been broadcasting to each one of those channels the entire time. What happens now is that a proverbial line is drawn in the sand, and the site is built on the assumption that a particular set of features will exist and are thus supported. If not, fallbacks are in place to allow a smooth degradation of the experience as usual, but more importantly the site is built to adopt features that the browser will eventually have. Modernizr can detect CSS features, such as @font-face , box-shadow , and CSS gradients. It can also detect HTML5 elements, such as canvas, localstorage, and application cache. In all it can detect over 40 features for you, the developer. Another term commonly used to describe this technique is progressive enhancement. When the time finally comes that the user decides to upgrade their browser, the new features that the more recent browser version brings with it, for example text-shadow, will automatically be detected and picked up by your website, to be leveraged by your site with no extra work or code from you when they do. Without any additional work on your part, any text that is assigned text-shadow attributes will turn on at the flick of a switch so that user's experience will smoothly, and progressively be enhanced. What is Modernizr? More importantly, why should you use it? At its foundation, Modernizr is a feature-detection library powered by none other than JavaScript. Here is an example of conditionally adding CSS classes based on the browser, also known as the User Agent. When the browser parses this HTML document and finds a match, that class will be conditionally added to the page. code 1 Now that the browser version has been found, the developer can use CSS to alter the page based on the version of the browser that is used to parse the page. In the following example, IE 7, IE 8, and IE 9 all use a different method for a drop shadow attribute on an anchor element: code 2 The problem with the conditional method of applying styles is that not only does it require more code, but it also leaves a burden on the developer to know what browser version is capable of a given feature, in this case box-shadow . Here is the same example using Modernizr. Note how Modernizr has done the heavy lifting for you, irrespective of whether or not the box-shadow feature is supported by the browser: code 3
Read more
  • 0
  • 0
  • 6768

article-image-null-17
Packt
30 Jan 2013
4 min read
Save for later

Modernizr JavaScript library

Packt
30 Jan 2013
4 min read
Detect and design with features, not User Agents (browsers) What if you could build your website based on features instead of for the individual browser idiosyncrasies by manufacturer and version, making your website not just backward compatible but also forward compatible? You could quite potentially build a fully backward and forward compatible experience using a single code base across the entire UA spectrum. What I mean by this is instead of baking in an MSIE 7.0 version, an MSIE 8.0 version , a Firefox version, and so on of your website, and then using JavaScript to listen for, or sniff out, the browser version in play, it would be much simpler to instead build a single version of your website that supports all of the older generation, latest generation, and in many cases even future generation technologies, such as a video API ,box-shadow, first-of-type , and more. Think of your website as a full-fledged cable television network broadcasting over 130 channels, and your users as customers that sign up for only the most basic package available, of only 15 channels. Any time that they upgrade their cable (browser) package to one offering additional channels (features), they can begin enjoying them immediately because you have already been broadcasting to each one of those channels the entire time. What happens now is that a proverbial line is drawn in the sand, and the site is built on the assumption that a particular set of features will exist and are thus supported. If not, fallbacks are in place to allow a smooth degradation of the experience as usual, but more importantly the site is built to adopt features that the browser will eventually have. Modernizr can detect CSS features, such as @font-face,box-shadow, and CSS gradients. It can also detect HTML5 elements, such as canvas,localstorage, and application cache. In all it can detect over 40 features for you, the developer. Another term commonly used to describe this technique is "progressive enhancement ". When the time finally comes that the user decides to upgrade their browser, the new features that the more recent browser version brings with it, for example text-shadow, will automatically be detected and picked up by your website, to be leveraged by your site with no extra work or code from you when they do. Without any additional work on your part, any text that is assigned text-shadow attributes will turn on at the flick of a switch so that user's experience will smoothly, and progressively be enhanced. What is Modernizr? More importantly, why should you use it? At its foundation, Modernizr is a feature-detection library powered by none other than JavaScript. Here is an example of conditionally adding CSS classes based on the browser, also known as the User Agent. When the browser parses this HTML document and finds a match, that class will be conditionally added to the page. Code1 Now that the browser version has been found, the developer can use CSS to alter the page based on the version of the browser that is used to parse the page. In the following example, IE 7, IE 8, and IE 9 all use a different method for a drop shadow attribute on an anchor element: Code2 The problem with the conditional method of applying styles is that not only does it require more code, but it also leaves a burden on the developer to know what browser version is capable of a given feature, in this case box-shadow. Here is the same example using Modernizr. Note how Modernizr has done the heavy lifting for you, irrespective of whether or not the box-shadow feature is supported by the browser: Code3
Read more
  • 0
  • 0
  • 1388
article-image-downloading-and-setting-bootstrap
Packt
30 Jan 2013
4 min read
Save for later

Downloading and setting up Bootstrap

Packt
30 Jan 2013
4 min read
(For more resources related to this topic, see here.) Getting ready Twitter Bootstrap is more than a set of code. It is an online community. To get started, you will do well to familiarize yourself with Twitter Bootstrap's home base: http://twitter.github.com/bootstrap/ Here you'll find the following: The documentation: If this is your first visit, grab a cup of coffee and spend some time perusing the pages, scanning the components, reading the details, and soaking it in. (You'll see this is going to be fun.) The download button: You can get the latest and greatest versions of the Twitter Bootstrap's CSS, JavaScript plugins, and icons, compiled and ready for action, coming to you in a convenient ZIP folder. This is where we'll start. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you. How to do it… Whatever your experience level, as promised, I'll walk you through all the necessary steps. Here goes! Go to the Bootstrap homepage: http://twitter.github.com/bootstrap/ Click on the large Download Bootstrap button. Locate the download file and unzip or extract it. You should get a folder named simply bootstrap. Inside this folder you should find the folders and files shown in thefollowing screenshot: From the homepage, click on the main navigation item: Get started. Scroll down, or use the secondary navigation, to navigate to the heading: Examples. The direct link is: http://twitter.github.com/bootstrap/getting-started. html#examples Right-click and download the leftmost example, labeled Basic Marketing Site. You'll see that it is an HTML file, named hero.html Save (or move) it to your main bootstrap folder, right alongside the folders named css, img, and js. Rename the file index.html (a standard name for what will become our homepage). You should now see something similar to the following screenshot: Next, we need to update the links to the stylesheets Why? When you downloaded the starter template file, you changed the relationship between the file and its stylesheets. We need to let it know where to find the stylesheets in this new file structure. Open index.html (formerly, hero.html) in your code editor. Need a code editor? Windows users: You might try Notepad++ (http://notepadplus-plus.org/download/) Mac users: Consider TextWrangler (http://www. barebones.com/products/textwrangler/)   Find these lines near the top of the file (lines 11-18 in version 2.0.2): Update the href attributes in both link tags to read as follows: Save your changes! You're set to go! Open it up in your browser! (Double-click on index.html.) You should see something like this: Congratulations! Your first Bootstrap site is underway. Problems? Don't worry. If your page doesn't look like this yet, let me help you spot the problem. Revisit the steps above and double-check a couple of things: Are your folders and files in the right relationship? (see step 3 as detailed previosuly) In your index.html, did you update the href attributes in both stylesheet links? (These should be lines 11 and 18 as of Twitter Bootstrap version 2.1.0.) There's more… Of course, this is not the only way you could organize your files. Some developers prefer to place stylesheets, images, and JavaScript files all within a larger folder named assets or library. The organization method I've presented is recommended by the developers who contribute to the HTML5 Boilerplate. One advantage of this approach is that it reduces the length of the paths to our site assets. Thus, whereas others might have a path to a background image such as this: url('assets/img/bg.jpg'); In the organization scheme I've recommended it will be shorter: url('img/bg.jpg'); This is not a big deal for a single line of code. However, when you consider that there will be many links to stylesheets, JavaScript files, and images running throughout your site files, when we reduce each path a few characters, this can add up. And in a world where speed matters, every bit counts. Shorter paths save characters, reduce file size, and help support faster web browsing. Summary This article gave us a quick introduction to the Twitter Bootstrap. We've got a fair idea as to how to download and set up our Bootstrap. By following these simple steps, we can easily create our first Bootstrap site. Resources for Article : Further resources on this subject: Starting Up Tomcat 6: Part 1 [Article] Build your own Application to access Twitter using Java and NetBeans: Part 2 [Article] Integrating Twitter with Magento [Article]
Read more
  • 0
  • 0
  • 10132

article-image-disaster-recovery-hyper-v
Packt
29 Jan 2013
9 min read
Save for later

Disaster Recovery for Hyper-V

Packt
29 Jan 2013
9 min read
(For more resources related to this topic, see here.) Hyper-V and Windows Server 2012 come with tools and solutions to make sure that your virtual machines will be up, running, and highly available. Components such as Failover Cluster can ensure that your servers are accessible, even in case of failures. However, disasters can occur and bring all the servers and services offline. Natural disasters, viruses, data corruption, human errors, and many other factors can make your entire system unavailable. People think that High Available (HA) is a solution for Disaster Recovery (DR) and that they can use it to replace DR. Actually HA is a component of a DR plan, which consists of process, policies, procedures, backup and recovery plan, documentation, tests, Service Level Agreements (SLA), best practices, and so on. The objective of a DR is simply to have business continuity in case of any disaster. In a Hyper-V environment, we have options to utilize the core components, such as Hyper-V Replica, for a DR plan, which replicates your virtual machines to another host or cluster and makes them available if the first host is offline, or even backs up and restores to bring VMs back, in case you lose everything. This module will walk you through the most important processes for setting up disaster recovery for your virtual machines running on Hyper-V. Backing up Hyper-V and virtual machines using Windows Server Backup Previous versions of Hyper-V had complications and incompatibilities with the built-in backup tool, forcing the administrators to acquire other solutions for backing up and restoring. Windows Server 2012 comes with a tool known as Windows Server Backup (WSB), which has full Hyper-V integration, allowing you to back up and restore your server, applications, Hyper-V, and virtual machines. WSB is easy and provides for a low cost scenario for small and medium companies. This recipe will guide you through the steps to back up your virtual machines using the Windows Server Backup tool. Getting ready Windows Server Backup does not support tapes. Make sure that you have a disk, external storage, network share, and free space to back up your virtual machines before you start. How to do it... The following steps will show you how to install the Windows Server Backup feature and how to schedule a task to back up your Hyper-V settings and virtual machines: To install the Windows Server Backup feature, open Server Manager from the taskbar. In the Server Manager Dashboard, click on Manage and select Add Roles and Features. On the Before you begin page, click on Next four times. Under the Add Roles and Features Wizard, select Windows Server Backup from the Features section, as shown in the following screenshot: Click on Next and then click on Install. Wait for the installation to be completed. After the installation, open the Start menu and type wbadmin.msc to open the Windows Server Backup tool. To change the backup performance options, click on Configure Performance from the pane on the right-hand side in the Windows Server Backup console. In the Optimize Backup Performance window, we have three options to select from—Normal backup performance, Faster backup performance, and Custom, as shown in the following screenshot: In the Windows Server Backup console, in the pane on the right-hand side, select the backup that you want to perform. The two available options are Backup Schedule to schedule an automatic backup and Backup Once for a single backup. The next steps will show how to schedule an automatic backup. In the Backup Schedule Wizard, in the Getting Started page, click on Next. In the Select Backup Configuration page, select Full Server to back up all the server data or click on Custom to select specific items to back up. If you want to backup only Hyper-V and virtual machines, click on Custom and then Next. In Select Items for Backup, click on Add Items. In the Select Items window, select Hyper-V to back up all the virtual machines and the host component, as shown in the following screenshot. You can also expand Hyper-V and select the virtual machines that you want to back up. When finished, click on OK. Back to the Select Items for Backup, click on Advanced Settings to change Exclusions and VSS Settings. In the Advanced Settings window, in the Exclusions tab, click on Add Exclusion to add any necessary exclusions. Click on the VSS Settings tab to select either VSS full Backup or VSS copy Backup as shown in the following screenshot. Click on OK. In the Select Items for Backup window, confirm the items that will be backed up and click on Next. In the Specify Backup Time page, select Once a day and the time for a daily backup or select More than once a day and the time and click on Next. In the Specify Destination Type page, select the option Back up to a hard disk that is dedicated for backups (recommended), back up to a volume, or back up to a shared network folder, as shown in the following screenshot and click on Next. If you select the first option, the disk you choose will be formatted and dedicated to storing the backup data only. In Select Destination Disk, click on Show All Available Disks to list the disks, select the one you want to use to store your backup, and click on OK. Click on Next twice. If you have selected the Back up to a hard disk that is dedicated for backups (recommended) option, you will see a warning message saying that the disk will be formatted. Click on Yes to confirm. In the Confirmation window, double-check the options you selected and click on Finish, as shown in the following screenshot: After that, the schedule will be created. Wait until the scheduled time to begin and check whether the backup has been finished successfully. How it works... Many Windows administrators used to miss the NTBackup tool from the old Windows Server 2003 times because of its capabilities and features. The Windows Server Backup tool, introduced in Windows Server 2008, has many limitations such as no tape support, no advanced schedule options, fewer backup options, and so on. When we talk about Hyper-V in this regards, the problem is even worse. Windows Server 2008 has minimal support and features for it. In Windows Server 2012, the same tool is available with some limitations; however, it provides at least the core components to back up, schedule, and restore Hyper-V and your virtual machines. By default, WSB is not installed. The feature installation is made by Server Manager. After its installation, the tool can be accessed via console or command lines. Before you start the backup of your servers, it is good to configure the backup performance options you want to use. By default, all the backups are created as normal. It creates a full backup of all the selected data. This is an interesting option when low amounts of data are backed up. You can also select the Faster backup performance option. This backs up the changes between the last and the current backup, increasing the backup time and decreasing the stored data. This is a good option to save storage space and backup time for large amounts of data. A backup schedule can be created to automate your backup operations. In the Backup Schedule Wizard, you can back up your entire server or a custom selection of volumes, applications, or files. For backing up Hyper-V and its virtual machines, the best option is the customized backup, so that you don't have to back up the whole physical server. When Hyper-V is present on the host, the system shows Hyper-V, and you will be able to select all the virtual machines and the host component configuration to be backed up. During the wizard, you can also change the advanced options such as exclusions and Volume Shadow Copy Services (VSS) settings. WSB has two VSS backup options—VSS full backup and VSS copy backup. When you opt for VSS full backup, everything is backed up and after that, the application may truncate log files. If you are using other backup solutions that integrate with WSB, these logs are essential to be used in future backups such as incremental ones. To preserve the log files you can use VSS copy backup so that other applications will not have problems with the incremental backups. After selecting the items for backup, you have to select the backup time. This is another limitation from the previous version—only two schedule options, namely Once a day or More than once a day. If you prefer to create different backup schedule such as weekly backups, you can use the WSB commandlets in PowerShell. Moving forward, in the backup destination type, you can select between a dedicated hard disk, a volume, or a network folder to save your backups in. When confirming all the items, the backup schedule will be ready to back up your system. You can also use the option Backup once to create a single backup of your system. There's more... To check whether previous backups were successful or not, you can use the details option in the WSB console. These details can be used as logs to get more information about the last (previous), next, and all the backups. To access these logs, open Windows Server Backup, under Status select View details. The following screenshot shows an example of the Last backup. To see which files where backed up, click on the View list of all backed up files link. Checking the Windows Server Backup commandlets Some options such as advanced schedule, policies, jobs, and other configurations can only be created through commandlets on PowerShell. To see all the available Windows Server Backup commandlets, type the following command: Get-Command –Module WindowsServerBackup See also The Restoring Hyper-V and virtual machines using Windows Server Backup recipe in this article
Read more
  • 0
  • 0
  • 5082

article-image-adding-and-importing-configuration-items-system-center-2012-service-manager
Packt
28 Jan 2013
11 min read
Save for later

Adding and Importing Configuration items in System Center 2012 Service Manager

Packt
28 Jan 2013
11 min read
(For more resources related to this topic, see here.) Adding configuration items manually This recipe will show you how to use the Service Manager console to manually create a computer configuration item without using the connector framework or by importing any information. How to do it... The following steps will guide you through the process of adding CIs manually to the Service Manager CMDB. In the Service Manager console navigate to Configuration Items | Computers | All Windows Computers Click on Create Computer in the task pane on the right-hand side of the console. A new form screen will open. Fill out the form with relevant data, ensuring any field marked with a red asterisk is filled in as they are mandatory fields. Click on OK. How it works... Filling in the form submits the data to the database, creating the CI and a unique GUID identifier within the database. There's more... This method can be repeated for any configuration item within Service Manager. For a basic installation, this includes CIs such as the following: Computers/servers Software Users Business services Environments Importing active directory configuration items This recipe will show you how to set up the active directory connector, which will allow you to import users, groups, and printers from your active directory forest as CIs within Service Manager. Getting ready Before you set up the connector you will need an account within your active directory forest that has Read permissions to the organizational units containing the items you would like to import. How to do it... The following steps will guide you through the process of importing data from Active Directory into the Service Manager CMDB. In the Service Manager console, navigate to Administration | Connectors. In the task pane on the right-hand side click on Create Connector and select Active Directory Connector. Review the information on the Before You Begin screen and click on Next. Enter a name and description for the connector. In this example I've called it demo. local Active Directory Connector. Ensure the Enable this connector box is checked and then click on Next. Choose to either synchronize the entire domain or a specific OU. In this example I've chosen to synchronize the entire domain. A specific OU may be a more appropriate choice where the active directory structure may contain lots of non-relevant information and you require a more targeted import of data. Next to the Run As Account drop-down options, click on New and enter the details of the account you set up before starting this recipe, which has Read rights to active directory. Click on Next. When prompted, supply the password for the account used for the connector. The Select objects screen allows you to drill down and choose either specific objects to synchronize with this connector or provide an LDAP query to select the objects based on a custom criteria. As shown in this example, just select All computers, printers, users and user groups. Ensure that both options at the bottom of the screen are selected and click on Next. Review the summary and then, click on Create. When the completion screen shows that the connector was successfully created, click on Close. How it works... By default the active directory connector polls active directory every 24 hours for new objects. If new objects are present, they are inserted into Service Manager as new configuration items, otherwise the connector becomes dormant until the next 24 hour interval. By default the AD connector schedule is not configurable via the GUI console but can be changed via PowerShell. There's more... The active directory connector can be accessed via the Connectors folder under the Administration Workspace of the Service Manager console. Select the connector and click on Properties from the tasks pane on the right-hand side of the console. Mapping active directory domain attributes to Service Manager properties The following link is to the Service Manager TechNet library documentation and shows the active directory attribute and the corresponding Service Manager property that it maps to. http://technet.microsoft.com/en-us/library/hh524307 Changing the active directory connector schedule via PowerShell Unfortunately, changing the schedule of a connector isn't an easy PowerShell cmdlet and requires the use of the SDK via PowerShell. Anton Gritsenko has a good blog post here that explains how to achieve this: http://blog.scsmsolutions.com/2012/03/update-ad-and-sccm-connectorscheduler- with-powershell/ Importing Configuration Manager configuration items This recipe will show you how to set up the Configuration Manager connector, which will allow you to import information such as hardware and software information from your configuration manager system as CIs within Service Manager. Getting ready Before you set up the connector you will need an account within your active directory forest for the connector that has the following permissions: Configuration Manager SQL Database—smsdbrole_extract&db_datareader roles Service Manager—advanced operator How to do it... The following steps will guide you through the process of importing data from System Center Configuration Manager into the Service Manager CMDB. In the Service Manager console, navigate to Administration | Connectors. In the task pane on the right-hand side, click on Create Connector and select Configuration Manager Connector. Review the Before You Begin screen and then click on Next. On the General screen, enter a name and a description for the connector. In this example, shown as follows), I've called it demo.local Configuration Manager Connector. Ensure the Enable is ticked so that this connector box is enabled and then click on Next. On the Select Management Pack screen, use the drop-down list under Management Pack to select the appropriate version of Configuration Manager that you wish to connect to, and then click on Next. On the Connect to System Center Configuration Manager Database screen, supply the name of the server hosting the SQL site database (including any instance information if applicable). Then supply the name of the database. In this example, I've used SCCM01 as the name of the server holding the site database and SMS_DL1 as the database name. Next to the Run As Account drop-down selection, click on New and enter the details of the account you had set up before starting this recipe, which is a member of smsdbrole_extract and the db_datareader groups for the site database. Click on Test Connection and enter the password for the account, when prompted. Click on Next. On the Collections screen select the collection containing the CIs you would like to synchronize, for this example, All Systems. Ensure that the box next to Do not write null values for properties that are not set in Configuration Manager is checked. Click on Next. On the Schedule screen, select when and how often you would like the connector to run. For this recipe, set it to every day at 06:00 and click on Next. Review the information on the Summary screen then click on Create. Review the information on the Confirmation screen the click on Close. How it works... The Service Manager connector queries the configuration manager database and extracts information related to computers, software, hardware, operating systems, software updates, users, and DCM baselines, and stores it within the CMDB. There's more... The Configuration Manager connector can be accessed via the Connectors folder under the Administration Workspace of the Service Manager console. Select the connector and click on Properties from the task pane on the right-hand side of the console. Mapping Configuration Manager attributes to Service Manager properties The following link is to the Service Manager TechNet library documentation and shows the Configuration Manager attribute and the corresponding Service Manager property that it maps to. http://technet.microsoft.com/en-us/library/hh519741 Importing Operations Manager configuration items This recipe will show you how to set up the Operations Manager connector, which will allow you to import information such as server IP addresses, SQL databases, and distributed Getting ready Before you set up the connector you will need an account within your Active Directory forest for the connector that has the following permissions: Operations Manager—operator privileges Service Manager—advanced operator For the Operations Manager connector to know what to synchronize with Service Manager, it is required that the management packs containing the classes that define the information are imported into Service Manager. The Service Manager installation directory contains the base management packs required to get started with the Operations Manager connector. For Operations Manager 2007: Open a PowerShell window. Type the following commands: Set-ExecutionPolicy Unrestricted Set-Location "Program FilesMicrosoft System Center 2012Service ManagerOperations Manager Management Packs" .installOMMPs.ps1 Close the PowerShell window This will import the Operations Manager 2007 Management Packs required for a basic connector. For Operations Manager 2012: In the Service Manager console navigate to the Administration | Management Packs. In the tasks pane on the right-hand side under Management Packs click on Import. On the Select Management Packs to Import screen, click on Add and navigate to the drive where Service Manager is installed: Program FilesMicrosoft System Center 2012Service Manager Operations Manager 2012 Management Packs. Click on the Change the File Type drop-down menu to select MP Files (*.mp). Select all the management packs displayed and click on Open. On the Import Management Packs screen, click on Import. When the import process is complete, click on OK. How to do it... The following steps will guide you through the process of importing data from System Center Operations Manager into the Service Manager CMDB. In the Service Manager console navigate to Administration | Connectors. In the task pane on the right-hand side, click on Create Connector then select Operations Manager CI Connector. Review the Before You Begin screen then click on Next. On the General screen, enter a name and a description for the connector. In this example I've called it demo.local Operations Manager Connector. Ensure the Enable box is checked and then click on Next. On the Server Details screen in the Server Name box, supply the name of the server that the Operations Manager Management Server is installed on. Next to the Run As Account drop-down menu, click on New and enter the details of the account you had set up before starting. Click the test connection and enter the password for the account, when prompted. Click on Next. On the MP Selection screen, check the Select all box and ensure that the Do not write null values for properties that are not set in Operations Manager box is checked. Click on Next. On the Schedule screen, select when and how often you would like the connector to run. For this recipe, set this to every day at 05:00 and click on Next. Review the information on the Summary screen then click on Create. Review the information on the Confirmation screen the click on Close. How it works... The Service Manager connector queries the Operations Manager Management Server and extracts information related to servers and related items and stores it within the CMDB according to the schedule specified. There's more... The Operations Manager connector can be accessed via the Connectors folder under the Administration Workspace of the Service Manager console. Select the connector and click on Properties from the task pane on the right-hand side of the console. Adding new Operations Manager CIs Every so often you will import new management packs into Operations Manager to extend its monitoring capabilities and/or update its management packs with newer versions. These will require importing into Service Manager to either allow these new classes of data to be brought across as CIs or to ensure that any changes to the classes within the management packs are mirrored across both systems. First use the same method described in the Getting ready section of this recipe to import and browse for the updated/new management packs. Next you must edit the Operations Manager CI connector, as follows: In the Service Manager console, navigate to the Service Manager console to Administration | Connectors. Select the Operations Manager CI Connector, named demo.local Operations Manager Connector in this recipe. In the task pane on the right-hand side, click on Properties. In the Edit screen on the left-hand side, click on Management Packs and then click on Refresh. Enter the password for the account used by the Operations Manager CI Connector and click on OK. In the Management Packs list, select the new management packs that you have just imported or check the Select All box, and click on OK. Summary In this article we added configuration items manually, imported active directory configuration items, imported configuration manager configuration items, and imported operations manager configuration items. Resources for Article : Further resources on this subject: Overview of FIM 2010 R2 [Article] Oracle Web Services Manager: Authentication and Authorization [Article] Active Directory Design Principles: Part 2 [Article]
Read more
  • 0
  • 0
  • 4832
article-image-eloquent-relationships
Packt
28 Jan 2013
12 min read
Save for later

Eloquent relationships

Packt
28 Jan 2013
12 min read
(For more resources related to this topic, see here.) 1 — Eloquent relationships ActiveRecord is a design pattern that describes an object-oriented way of interacting with your database. For example, your database's users table contains rows and each of these rows represents a single user of your site. Your User model is a class that extends the Eloquent Model class. When you query a record from your database, an instantiation of your User model class is created and populated with the information from the database. A distinct advantage of ActiveRecord is that your data and the business logic that is related to the data are housed within the same object. For example, it's typical to store the user's password in your model as a hash, to prevent it from being stored as plaintext. It's also typical to store the method, which creates this password hash within your User class. Another powerful aspect of the ActiveRecord pattern is the ability to define relationships between models. Imagine that you're building a blog site and your users are authors who must be able to post their writings. Using an ActiveRecord implementation, you are able to define the parameters of the relationship. The task of maintaining this relationship is then simplified dramatically. Simple code is the easy code to change. Difficult to understand code is the easy code to break. As a PHP developer, you're probably already familiar with the concept of database normalization. If you're not, normalization is the process of designing databases so that there is little redundancy in the stored data. For example, you wouldn't want to have both a users table which contains the user's name and a table of blog posts which also contains the author's name. Instead, your blog post record would refer to the user using their user ID. In this way we avoid synchronization problems and a lot of extra work! There are a number of ways in which relationships can be established in normalized database schemas. One-to-one relationship When a relationship connects two records in a way that doesn't allow for more records to be related, it is a one-to-one relationship. For example, a user record might have a one-to-one relationship with a passport record. In this example, a user record is not permitted to be linked to more than one passport record. Similarly, it is not permitted for a passport record to relate to more than one user record. How would the database look? Your users table contains information about each user in your database. Your passports table contains passport numbers and a link to the user which owns the passport. In this example, each user has no more than one passport and each passport must have an owner. The passports table contains its own id column which it uses as a primary key. It also contains the column user_id, which contains the ID of the user to whom the passport belongs. Last but not least, the passports table contains a column for the passport number. First, let's model this relationship in the User class: class User extends Eloquent { public function passport() { return $this->has_one('Passport'); } } We created a method named passport() that returns a relationship. It might seem strange to return relationships at first. But, you'll soon come to love it for the flexibility it offers. You'll notice that we're using the has_one() method and passing the name of the model as a parameter. In this case, a user has one passport. So, the parameter is the name of the passport model class. This is enough information for Eloquent to understand how to acquire the correct passport record for each user. Now, let's look at the Passport class: class Passport extends Eloquent { public function users() { return $this->belongs_to('User'); } } We're defining the passport's relationship differently. In the User class, we used the has_one() method. In the Passport class we used belongs_to(). It's vital to identify the difference early so that understanding the rest of the relationships is more simple. When a database table contains a foreign key, it is said that it belongs to a record in another table. In this example, our passports table refers to records in the users table through the foreign key user_id. Consequently, we would say that a passport belongs to a user. Since this is a one-to-one relationship the user has one (has_one()) passport. Let's say that we want to view the passport number of the user with the id of 1. $user = User::find(1); If(is_null($user)) { echo "No user found."; return; } If($user->passport) { echo "The user's passport number is " . $user->passport->number; } else { echo "This user has no passport."; } In this example, we're dutifully checking to make sure that our user object was returned as expected. This is a necessary step that should not be overlooked. Then, we check whether or not the user has a passport record associated with it. If a passport record for this user exists, the related object will be returned. If it doesn't exist, $user->passport will return null. In the preceding example, we test for the existence of a record and return the appropriate response. One-to-many relationships One-to-many relationships are similar to one-to-one relationships. In this relationship type, one model has many of other relationships, which in turn belongs to the former. One example of a one-to-many relationship is a professional sports team's relationship to its players. One team has many players. In this example, each player can only belong to one team. The database tables have the same structure. Now, let's look at the code which describes this relationship. class Team extends Eloquent { public function players() { return $this->has_many('Player'); } } class Player extends Eloquent { public function team() { return $this->belongs_to('Team'); } } This example is almost identical to the one-to-one example. The only difference is that the team's players() relationship uses has_many() rather than has_one(). The has_one() relationship returns a model object. The has_many() relationship returns an array of model objects. Let's display all of the players on a specific team: $team = Team::find(2); if(is_null($team)) { echo "The team could not be found."; } if(!$team->players) { echo "The team has no players."; } foreach($team->players as $player) { echo "$player->name is on team $team->name. "; } Again, we test to make sure that our team could be found. Then, we test to make sure that the team has players. Once we know that for sure, we can loop through those players and echo their names. If we tried to loop through the players without first testing and if the team had players, we'd get an error. Many-to-many relationships The last relationship type that we're going to cover is the many-to-many relationship. This relationship is different in that each record from each table could potentially be tied simultaneously to each record in another. We aren't storing foreign keys in either of these tables. Instead, we have a third table that exists solely to store our foreign keys. Let's take a look at the schema. Here we have a students table and a courses table. A student can be enrolled in many courses and a course can contain many students. The connection between students and courses is stored in a pivot table. A pivot table is a table that exists to connect two tables specifically for many-to-many relationships. Standard convention for naming a pivot table is to combine the names of both of the related tables, singularized, alphabetically ordered, and connected with an underscore. This gives us the table name course_student. This convention is not only used by Laravel and it's a good idea to follow the naming conventions covered in this document as strictly as possible as they're widely used in the web-development industry. It's important to notice that we're not creating a model for the pivot table. Laravel allows us to manage these tables without needing to interact with a model. This is especially nice because it doesn't make sense to model a pivot table with business logic. Only the students and courses are a part of our business. The connection between them is important, but only to the students and to the course. It's not important for its own sake. Let's define these models, shall we? class Student extends Eloquent { public function courses() { return $this->has_many_and_belongs_to('Course'); } } class Course extends Eloquent { public function students() { return $this->has_many_and_belongs_to('Student'); } } We have two models, each with the same type of relationship to each other. has_many_and_ belongs_to is a long name. But, it's a fairly simple concept. A course has many students. But, it also belongs to (belongs_to) student records and vice-versa. In this way, they are considered equal. Let's look at how we'll interact with these models in practice: $student = Student::find(1); if(is_null($student)) { echo "The student can't be found."; exit; } if(!$student->courses) { echo "The student $student->name is not enrolled in any courses."; exit; } foreach($student->courses as $course) { echo "The student $student->name is enrolled in the course $course->name."; } Here you can see that we can loop through the courses much the same way we could with the one-to-many relationship. Any time a relationship includes the word many, you know that you'll be receiving an array of models. Conversely, let's pull a course and see which students are a part of it. $course = Course::find(1); if(is_null($course)) { echo "The course can't be found."; exit; } if(!$course->students) { echo "The course $course->name seems to have no students enrolled."; exit; } foreach($course->students as $student) { echo "The student $student->name is enrolled in the course $course->name."; } The relationship functions exactly the same way from the course side. Now that we have established this relationship, we can do some fun things with it. Let's look at how we'd enroll a new student into an existing course: $course = Course::find(13); if(is_null($course)) { echo "The course can't be found."; exit; } $new_student_information = array( 'name' => 'Danielle' ); $course->students()->insert($new_student_information); Here we're adding a new student to our course by using the method insert(). This method is specific to this relationship type and creates a new student record. It also adds a record to the course_student table to link the course and the new student. Very handy! But, hold on. What's this new syntax? $course->students()->insert($new_student_information); Notice how we're not using $course->students->insert(). Our reference to students is a method reference rather than a property reference. That's because Eloquent handles methods that return relationship objects differently from other model methods. When you access a property of a model that doesn't exist, Eloquent will look to see if you have a function that matches that property's name. For example, if we try to access the property $course->students, Eloquent won't be able to find a member variable named $students. So it'll look for a function named students(). We do have one of those. Eloquent will then receive the relationship object from that method, process it, and return the resulting student records. If we access a relationship method as a method and not as a property, we directly receive the relationship object back. The relationship's class extends the Query class. This means that you can operate on a relationship object in the same way that you can operate on a query object, except that it now has new methods that are specific to the relationship type. The specific implementation details aren't important at this point. It's just important to know that we're calling the insert() method on the relationship object returned from $course->students(). Imagine that you have a user model and it has many relationships and belongs to a role model. Roles represent different permission groupings. Example roles might include customer, admin, super admin, and ultra admin. It's easy to imagine a user form for managing its roles. It would contain a number of checkboxes, one for each potential role. The name of the checkboxes is role_ids[] and each value represents the ID of a role in the roles table./p> When that form is posted we'll retrieve those values with the Input::get() method. $role_ids = Input::get('role_ids'); $role_ids is now an array that contains the values 1, 2, 3, and 4. $user->roles()->sync($role_ids); The sync() method is specific to this relationship type and is also perfectly suited for our needs. We're telling Eloquent to connect our current $user to the roles whose IDs exist within the $role_ids array. Let's look at what's going on here in further detail. $user->roles() is returning a has_ many_and_belongs_to relationship object. We're calling the sync() method on that object. Eloquent now looks at the $role_ids array and acknowledges it as the authoritative list of roles for this user. It then removes any records that shouldn't exist in the role_user pivot table and adds records for any role that should exist in the pivot table. Summary In this article we discussed three types of Eloquent relationships—one-to-one relationship, one-to-many relationship, and many-to-many ralationship. Resources for Article : Further resources on this subject: Modeling Relationships with GORM [Article] Working with Simple Associations using CakePHP [Article] NHibernate 2: Mapping relationships and Fluent Mapping [Article]
Read more
  • 0
  • 0
  • 3766

article-image-smart-processes-using-rules
Packt
25 Jan 2013
16 min read
Save for later

Smart Processes Using Rules

Packt
25 Jan 2013
16 min read
(For more resources related to this topic, see here.) Good old integration patterns What we've learnt from experience about jBPM3 is that a rule engine can become very handy when evaluating different situations and making automatic decisions based on the information available. Based on my experience in consulting, I've noticed that people who understand how a process engine works and feel comfortable with it, start looking at rule engines such as Drools. The most intuitive first step is to delegate the business decisions in your processes and the data validations to a rule engine. In the past, adopting one of these two different technologies at the same time was difficult, mostly because of the learning curve as well as the maturity and investment required by a company to learn and use both technologies at once. At the end of the day, companies spend time and money to create in-house integrations and solutions so that they can merge these two worlds. The following example shows what people have done with jBPM3 and the Drools Rule Engine: The first and most typical use case is to use a rule engine to choose between different paths in a process. Usually, the information that is sent to the rule engine is the same information that is flowing through the process tasks or just some pieces of it; we expect a return value from the rule engine that will be used to select which path to take. Most of the time, we send small pieces of information (for example, the age or salary of a person, and so on) and we expect to get a Boolean (true/false) value, in the case that we want to decide between just two paths, or a value (integers such as 1, 2, 3, and so on) that will be used to match each outgoing sequence flow. In this kind of integration, the rule engine is considered just an external component. We expect a very stateless behavior and an immediate response from the rule engine. The previous figure shows a similar situation, when we want to validate some data and then define a task inside our process to achieve this information's validation or decoration. Usually we send a set of objects that we want to validate or decorate, and we expect an immediate answer from the rule engine. The type of answer that we receive depends on the type of validation or the decoration rules that we write. Usually, these interactions interchange complex data structures, such as a full graph of objects. And that's it! Those two examples show the classic interaction from a process engine to a rule engine. You may have noticed the stateless nature of both the examples, where the most interesting features of the rule engine are not being used at all. In order to understand a little bit better why a rule engine is an important tool and the advantages of using it (in contrast to any other service), we need to understand some of the basic concepts behind it. The following section briefly introduces the Drools Rule Engine and its features, as well as an explanation of the basic topics that we need to know in order to use it. The Drools Rule Engine The reason why rule engines are extremely useful is that they allow us to express declaratively what to do in specific scenarios. In contrast to imperative languages such as Java, the Rule Engine provides a declarative language that is used to evaluate the available information. In this article we will analyze some Drools rules, but this article will not explain in detail the Drools Rule syntax. For more information on this take a look at the official documentation at http://docs.jboss.org/drools/release/5.5.0.Final/drools-expert-docs/html_single/. Let's analyze the following simple example to understand the differences between the declarative approaches and the imperative approaches: rule "over 18 enabled to drive" when $p: Person( age > 18, enabledToDrive == false) then $p.setEnabledToDrive(true); update($p); end rule "over 21 enabled to vote" when $p: Person( age > 21, enabledToVote == false) Then $p.setEnabledToVote(true); update($p); end Before explaining what these two rules are doing (which is kind of obvious as they are self-descriptive!), let's analyze the following java code snippet: if(person.getAge() > 18 && person.isEnabledToDrive() == false){ person.setEnabledToDrive(true); } if(person.getAge() > 21 && person.isEnabledToVote() == false){ person.setEnabledToVote(true); } Usually most people, who are not familiar with rule engines but have heard of them, think that rule engines are used to extract if/else statements from the application's code. This definition is far from reality, and doesn't explain the power of rule engines. First of all, rule engines provide us a declarative language to express our rules, in contrast to the imperative nature of languages such as Java. In Java code, we know that the first line is evaluated first, so if the expression inside the if statement evaluates to true, the next line will be executed; if not, the execution will jump to the next if statement. There are no doubts about how Java will analyze and execute these statements: one after the other until there are no more instructions. We commonly say that Java is an imperative language in which we specify the actions that need to be executed and the sequence of these actions. Java, C, PHP, Python, and Cobol are imperative languages, meaning that they follow the instructions that we give them, one after the other. Now if we analyze the DRL snippet (DRL means Drools Rule Language), we are not specifying a sequence of imperative actions. We are specifying situations that are evaluated by the rule engine, so when those situations are detected, the rule consequence (the then section of the rule) is eligible to be executed. Each rule defines a situation that the engine will evaluate. Rules are defined using two sections: the conditional section, which starts with the when keyword that defines the filter that will be applied to the information available inside the rule engine. This example rule contains the following condition: when $p: Person( age > 18 ) This DRL conditional statement filters all the objects inside the rule engine instance that match this condition. This conditional statement means "match for each person whose age is over 18". If we have at least one Person instance that matches this condition, this rule will be activated for that Person instance. A rule that is activated is said to be eligible to be fired. When a rule is fired, the consequence side of the rule is executed. For this example rule, the consequence section looks like this: then $p.setEnabledToDrive(true); update($p); In the rule consequence, you can write any Java code you want. This code will be executed as regular Java code. In this case, we are getting the object that matches the filter—Person ( age > 18 )—that is bonded to the variable called $p and changing one of its attributes. The second line inside the consequence notifies the rule engine of this change so it can be used by other rules. A rule is composed of a conditional side, also called Left-Hand Side (LHS for short) and a consequence side, also called Right-Hand Side (RHS for short). rule "Person over 60 – apply discount" when // LHS $p: Person(age > 60) then // RHS $p.setDiscount(40); end We will be in charge of writing these rules and making them available to a rule engine that is prepared to host a large number of rules. To understand the differences and advantages between the following lines, we need to understand how a rule engine works. The first big difference is behavior: we cannot force the rule engine to execute a given rule. The rule engine will pick up only the rules that match with the expressed conditions. if(person.getAge() > 18) And $p: Person( age > 18 ) If we try to compare rules with imperative code, we usually analyze how the declarative nature of rule languages can help us to create more maintainable code. The following example shows how application codes usually get so complicated, that maintaining them is not a simple task: If(…){ If(){ If(){ } }else(){ if(…){ } } } All of the evaluations must be done in a sequence. When the application grows, maintaining this spaghetti code becomes complex—even more so when the logic that it represents needs to be changed frequently to reflect business changes. In our simple example, if the person that we are analyzing is 19 years old, the only rule that will be evaluated and activated is the rule called "over 18 enabled to drive". Imagine that we had mixed and nested if statements evaluating different domain entities in our application. There would be no simple way to do the evaluations in the right order for every possible combination. Business rules offer us a simple and atomic way to describe the situations that we are interested in, which will be analyzed based on the data available. When the number of these situations grows and we need to frequently apply changes to reflect the business reality, a rule engine is a very good alternative to improve readability and maintenance. Rules represent what to do for a specific situation. That's why business rules must be atomic. When we read a business rule, we need to clearly identify what's the condition and exactly what will happen when the condition is true. To finish this quick introduction to the Drools Rule Engine, let's look at the following example: rule "enabled to drive must have a car" When $p: Person( enabledToDrive == true ) not(Car(person == $p)) then insert(new Car($p)); end rule "person with new car must be happy" when $p: Person() $c: Car(person == $p) then $p.setHappy(true); end rule "over 18 enabled to drive" when $p: Person( age > 18, enabledToDrive == false) then $p.setEnabledToDrive(true); update($p); end When you get used to the Drools Rule Language, you can easily see how the rules will work for a given situation. The rule called "over 18 enabled to drive" checks the person's age in order to see if he/she is enabled to drive or not. By default, persons are not enabled to drive. When this rule finds one instance of the Person object that matches with this filter, it will activate the rule; and when the rule's consequence gets executed, the enabledToDrive attribute will be set to true and we will notify the engine of this change. Because the Person instance has been updated, the rule called "enabled to drive must have a car" is now eligible to be fired. Because there is no other active rule, the rule's consequence will be executed, causing the insertion of a new car instance. As soon as we insert a new car instance, the last rule's conditions will be true. Notice that the last rule is evaluating two different types of objects as well as joining them. The rule called "person with new car must be happy" is checking that the car belongs to the person with $c: Car(person == $p). As you may imagine, the $p: creates a binding to the object instances that match the conditions for that pattern. In all the examples in this book, I've used the $ sign to denote variables that are being bound inside rules. This is not a requirement, but it is a good practice that allows you to quickly identify variables versus object field filters. Please notice that the rule engine doesn't care about the order of the rules that we provide; it will analyze them by their conditional sections, not by the order in which we provide the rules. This article provides a very simple project implementing this scenario, so feel free to open it from inside the chapter_09 directory and experiment with it. It's called drools5-SimpleExample. This project contains a test class called MyFirstDrools5RulesTest, which tests the previously introduced rules. Feel free to change the order of the rules provided in the /src/test/resources/simpleRules.drl file. Please take a look at the official documentation at www.drools.org to find more about the advantages of using a rule engine. What Drools needs to work If you remember the jBPM5 API introduction section, you will recall the StatefulKnowledgeSession interface that hosts our business processes. This stateful knowledge session is all that we need in order to host and interact with our rules as well. We can run our processes and business rules in the same instance of a knowledge session without any trouble. In order to make our rules available in our knowledge session, we will need to use the knowledge builder to parse and compile our business rules and to create the proper knowledge packages. Now we will use the ResourceType.DRL file instead of the ResourceType.BPMN2 file that we were using for our business processes. So the knowledge session will represent our world. The business rules that we put in it will evaluate all the information available in the context. From our application side, we will need to notify the rule engine which pieces of information will be available to be analyzed by it. In order to inform and interact with the engine, there are four basic methods provided by the StatefulKnowledgeSession object that we need to know. We will be sharing a StatefulKnowledgeSession instance between our processes and our rules. From the rule engine perspective, we will need to insert information to be analyzed. These pieces of information (which are Java objects) are called facts according to the rule engine's terminology. Our rules are in charge of evaluating these facts against our defined conditions. The following four methods become a fundamental piece of our toolbox: FactHandle insert(Object object) void update(FactHandle handle, Object object) void retract(FactHandle handle) int fireAllRules() The insert() method notifies the engine of an object instance that we want to analyze using our rules. When we use the insert() method, our object instance becomes a fact. A fact is just a piece of information that is considered to be true inside the rule engine. Based on this assumption, a wrapper to the object instance will be created and returned from the insert() method. This wrapper is called FactHandle and it will allow us to make references to an inserted fact. Notice that the update() and retract() methods use this FactHandle wrapper to modify or remove an object that we have previously inserted. Another important thing to understand at this point is that only top-level objects will be handled as facts, which implies the following: FactHandle personHandle = ksession.insert(new Person()); This sentence will notify the engine about the presence of a new fact, the Person instance. Having the instances of Person as facts will enable us to write rules using the pattern Person() to filter the available objects. What if we have a more complex structure? Here, for example, the Person class defines a list of addresses as: class Person{ private String name; private List<Address> addresses; } In such cases we will need to define if we are interested in making inferences about addresses. If we just insert the Person object instance, none of the addresses instances will be treated as facts by the engine. Only the Person object will be filtered. In other words, a condition such as the following would never be true: when $p: Person() $a: Address() This rule condition would never match, because we don't have any Address facts. In order to make the Address instances available to the engine, we can iterate the person's addresses and insert them as facts. ksession.insert(person); for(Address addr : person.getAddresses()){ ksession.insert(addr); } If our object changes, we need to notify the engine about the changes. For that purpose, the update() method allows us to modify a fact using its fact handler. Using the update() method will ensure that only the rules that were filtering this fact type gets re-evaluated. When a fact is no longer true or when we don't need it anymore, we can use the retract() method to remove that piece of information from the rule engine. Up until now, the rule engine has generated activations for all the rules and facts that match with those rules. No rule's consequence will be executed if we don't call the fireAllRules() method. The fireAllRules() method will first look for activations inside our ksession object and select one. Then it will execute that activation, which can cause new activations to be created or current ones canceled. At this point, the loop begins again; the method picks one activation from the Agenda (where all the activations go) and executes it. This loop goes on until there are no more activations to execute. At that point the fireAllRules() method returns control to our application. The following figure shows this execution cycle: This cycle represents the inference process, since our rules can generate new information (based on the information that is available), and new conclusions can be derived by the end of this cycle. Understanding this cycle is vital in working with the rule engine. As soon as we understand the power of making data inferences as opposed to just plain data validation, the power of the rule engine is unleashed. It usually takes some time to digest the full range of possibilities that can be modeled using rules, but it's definitely worth it. Another characteristic of rule engines that you need to understand is the difference between stateless and stateful sessions. In this book, all the examples use the StatefulKnowledgeSession instance to interact with processes and rules. A stateless session is considered a very simple StatefulKnowledgeSession that can execute a previously described execution cycle just once. Stateless sessions can be used when we only need to evaluate our data once and then dispose of that session, because we are not planning to use it anymore. Most of the time, because the processes are long running and multiple interactions will be required, we need to use a StatefulKnowledgeSession instance. In a StatefulKnowledgeSession, we will be able to go throughout the previous cycle multiple times, which allows us to introduce more information over time instead of all at the beginning. Just so you know, the StatelessKnowledgeSession instance in Drools exposes an execute() method that internally inserts all the facts provided as parameters, calls the fireAllRules() method, and finally disposes of the session. There have been several discussions about the performance of these two approaches, but inside the Drools Engine, both stateful and stateless sessions perform the same, because StatelessKnowledgeSession uses StatefulKnowledgeSession under the hood. There is no performance difference between stateless and stateful sessions in Drools. The last function that we need to know is the dispose() method provided by the StatefulKnowledgeSession interface. Disposing of the session will release all the references to our domain objects that are kept, allowing those objects to be collected by the JVM's garbage collector. As soon as we know that we are not going to use a session anymore, we should dispose of it by using dispose().
Read more
  • 0
  • 0
  • 2363
Modal Close icon
Modal Close icon