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-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

article-image-securing-portal-contents
Packt
24 Jan 2013
8 min read
Save for later

Securing Portal Contents

Packt
24 Jan 2013
8 min read
(For more resources related to this topic, see here.) Introduction This article discusses the configurations aimed at providing security features to portals and all the related components. We will see that we can work using either the web console or the XML configuration files. As you would expect, the latter is more flexible in most instances. Many of the configuration snippets shown in the article are based on Enterprise Deployment Descriptors (DD). Keep in mind that XML always remains the best option for configuring a product. We will configure GateIn in different ways to show how to adapt some of the internal components for your needs. Enterprise Deployment Descriptors (DD) are configuration files related to an enterprise application component that must be deployed in an application server. The goal of the deployment descriptor is to define how a component must be deployed in the container, configuring the state of the application and its internal components. These configuration files were introduced in the Java Enterprise Platform to manage the deployment of Java Enterprise components such as Web Applications, Enterprise Java Beans, Web Services, and so on. Typically, for each specific container, you have a different definition of the descriptor depending on vendors and standard specifications. Typically, a portal consists of pages related to a public section and a private section. Depending on the purpose, of course, we can also work with a completely private portal. The two main mechanisms used in any user-based application are the following: Authentication Authorization In this article we will discuss authorization: how to configure and manage permissions for all the objects involved in the portal. As an example, a User is a member of a Group, which provides him with some authorizations. These authorizations are the things that members of the Groups can do in the portal. On the other side, as an example, a page is defined with some permissions, which says which Groups can access it. Now, we are going to see how to configure and manage these permissions, for the pages, components in a page, and so on in the portal. Securing portals The authorization model of the portal is based on the association between the following actors: groups, memberships, users, and any content inside the portal (pages, categories, or portlets). In this recipe, we will assign the admin role against a set of pages under a specific URL of the portal. This configuration can be found in the default portal provided with GateIn so you can take the complete code from there. Getting ready Locate the web.xml file inside your portal application. How to do it... We need to configure the web.xml file assigning the admin role to the following pages under the URL http://localhost:8080/portal/admin/* in the following way: <security-constraint> <web-resource-collection> <web-resource-name> admin authentication </web-resource-name> <url-pattern>/admin/*</url-pattern> <http-method>POST</http-method> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> The role must be declared in a different section under the security-constraint tag through the security-role tag. The role-name tag defines the id of the role: <security-role> <description>the admin role</description> <role-name>admin</role-name> </security-role> How it works... GateIn allows you to add different roles for every sections of the portal simply by adding a path expression that can include a set of sub-pages using wildcard notation (/*). This is done by first defining all the needed roles using the security-role element, and then defining a security-constraint element for each set of pages that you want to involve. PicketLink is also for users and memberships, and can manage the organization of the groups. There's more... Configuring GateIn with JAAS GateIn uses JAAS (Java Authentication Authorization Service) as the security model. JAAS (Java Authentication Authorization Service) is the most common framework used in the Java world to manage authentication and authorization. The goal of this framework is to separate the responsibility of users' permissions from the Java application. In this way, you can have a bridge for permissions management between your application and the security provider. For more information about JAAS, please see the following URL: http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html Java EE Application servers and JSP/servlet containers, such as JBoss and Tomcat, also support JAAS with specific deployment descriptors. The default JAAS module implemented in GateIn synchronizes the users and roles from the database. In order to add your portal to a specific realm, add the following snippet in web.xml: <login-config> . . . <realm-name>gatein-domain</realm-name> . . . </login-config> Notice that a realm can be managed by JAAS or another authorization framework—it is not important which is used for the Java Enterprise Edition. gatein-domain is the ID of the default GateIn domain that we will use as the default reference for the following recipes. See also The Securing with JBoss AS recipe The Securing with Tomcat recipe Securing with JBoss AS In this recipe, we will configure GateIn with JAAS using JBoss AS (5.x and 6.x). Getting ready Locate the WEB-INF folder inside your portal application. How to do it... Create a new file named jboss-web.xml in the WEB-INF folder with the following content: <jboss-web> <security-domain> java:/jaas/gatein-domain </security-domain> </jboss-web> How it works... This is the JNDI URL where the JAAS module will be referenced. This URL will automatically search the JAAS modules called gatein-domain. The configuration of the modules can be found inside the file gatein-jboss-beans.xml. Usually, this file is inside the deployed <PORTAL_WAR_ROOT>/META-INF, but it could be placed anywhere inside the deploy directory of JBoss, thanks to the auto-discovery feature provided by the JBoss AS. Here is an example: <deployment > <application-policy name="gatein-domain"> <authentication> <login-module code= "org.gatein.wci.security.WCILoginModule" flag="optional"> <module-option name="portalContainerName"> portal </module-option> <module-option name="realmName"> gatein-domain </module-option> </login-module> <login-module code= "org.exoplatform.web.security.PortalLoginModule" flag="required"> ……….. </application-policy> </deployment> JAAS allows adding several login modules, which will be executed in cascade mode according to the flag attribute. The following represents a description of the valid values for the flag attribute and their respective semantics as mentioned in the Java standard API: Required: The LoginModule is required to succeed. If it succeeds or fails, authentication still continues to proceed to the next LoginModule in the list. Requisite: The LoginModule is required to succeed. If it succeeds, authentication continues on the next LoginModule in the list. If it fails, the control immediately returns to the application and the authentication process does not proceed to the next LoginModule. Sufficient: The LoginModule is not required to succeed. If it does succeed, the control immediately returns to the application and the authentication process does not proceed to the next LoginModule. If it fails, authentication continues forward to the next LoginModule Optional: The LoginModule is not required to succeed. If it succeeds or fails, authentication still continues to proceed to the next LoginModule. Look at the recipe Choosing the JAAS modules for details about each login module. See also The Securing portals recipe The Securing with Tomcat recipe The Choosing the JAAS modules recipe Securing with Tomcat In this recipe, we will configure a JAAS realm using Tomcat 6.x.x/7.x.x. Getting ready Locate the declaration of the realm inside <PORTAL_WAR_ROOT>/META-INF/context.xml. How to do it… Change the default configuration for your needs, as described in the previous recipe. The default configuration is the following: <Context path='/portal' docBase='portal' debug='0' reloadable='true' crossContext='true' privileged='true'> <Realm className= 'org.apache.catalina.realm.JAASRealm' appName='gatein-domain' userClassNames= 'org.exoplatform.services.security.jaas.UserPrincipal' roleClassNames= 'org.exoplatform.services.security.jaas.RolePrincipal' debug='0' cache='false'/> <Valve className= 'org.apache.catalina.authenticator.FormAuthenticator' characterEncoding='UTF-8'/> </Context> ; Change the default configuration of the JAAS domain that is defined in the TOMCAT_ HOME/conf/jaas.conf file. Here is the default configuration: <gatein-domain { org.gatein.wci.security.WCILoginModule optional; org.exoplatform.services.security.jaas.SharedStateLoginModule required; org.exoplatform.services.security.j2ee.TomcatLoginModule required; }; How it works… As we have seen in the previous recipe, we can configure the modules in Tomcat using a different configuration file. This means that we can change and add login modules that are related to a specific JAAS realm. The context.xml file is stored inside the web application. If you don't want to modify this file, you can add a new file called portal.xml in the conf folder to override the current configuration. See also The Security with JBoss AS recipe The Choosing the JAAS modules recipe
Read more
  • 0
  • 0
  • 1853

article-image-moving-space-between-partitions
Packt
23 Jan 2013
3 min read
Save for later

Moving space between partitions

Packt
23 Jan 2013
3 min read
(For more resources related to this topic, see here.) Getting ready Before performing this task, we highly recommend that you backup your data. This task involves moving the start of a partition boundary, which is a high risk activity. How to do it... Select the partition with plenty of free space. Choose the Partition | Resize/Move menu option and a Resize/Move window is displayed. Click on the left-hand side of the partition and drag it to the right so that the free space is reduced by half. Click on Resize/Move to queue the operation. Click on OK to acknowledge the move partition warning Select the extended partition. Choose the Partition | Resize/Move menu option and a Resize/Move window is displayed. Click on the left-hand side of the partition and drag it to the right so that there is no space between the outer extended partition boundary and the inner logical partition boundary. Click on Resize/Move to queue the operation Select the partition that needs more free space: Choose the Partition | Resize/Move menu option and a Resize/Move window is displayed. Click on the right-hand side of the partition and drag it as far to the right as possible: Click Resize/Move to queue the operation: Notice the unallocated space between sda1 and sda2. This gap, which can be up to about 8 MiB, occurs due to having cylinder aligned and MiB aligned partitions on the same disk device. In this example, the sda1 partition was created with cylinder alignment to demonstrate this potential gap. Choose the Edit | Apply All Operations menu option to apply the queued operations, to disk. Click on Apply to apply operations to disk. Click on Close to close the apply operations to disk window. How it works... In order to add space to a partition, unallocated space must be available immediately adjacent to the partition. To free up this space, we use many of the recipes covered earlier. First, we made unallocated space available by shrinking the logical partition where free space was available. Because the free space came from a logical partition inside an extended partition, and we needed to add the space to a primary partition, we had to edit three partitions to achieve the desired goal. There's more... If you resize or move a partition containing an NTFS file system, then you should reboot into Windows twice to permit Windows to perform file system consistency checks. Growing or moving a partition To grow or move a partition, unallocated space must be available adjacent to the partition: When growing a logical partition, the unallocated space must be within the extended partition. When growing a primary partition, the unallocated space must not be within the extended partition. You can move unallocated space inside or outside of an extended partition by resizing the extended partition boundaries. Summary We saw how to migrate free disk space to where it is most needed. Modern disk drives can store vast amounts of information. To effectively use all of this space, you can partition disk drives into separate storage areas. These separate storage areas enable you to organize your data, improve system performance, and install and use many operating systems. Resources for Article : Further resources on this subject: Installation of FreeNAS [Article] UNIX Monitoring Tool for PostgreSQL [Article] How Storage Works on Amazon [Article]
Read more
  • 0
  • 0
  • 3951

article-image-oracle-using-metadata-service-share-xml-artifacts
Packt
23 Jan 2013
11 min read
Save for later

Oracle: Using the Metadata Service to Share XML Artifacts

Packt
23 Jan 2013
11 min read
(For more resources related to this topic, see here.) The WSDL of a web service is made up of the following XML artifacts: WSDL Definition: It defines the various operations that constitute a service, their input and output parameters, and the protocols (bindings) they support. XML Schema Definition (XSD): It is either embedded within the WSDL definition or referenced as a standalone component; this defines the XML elements and types that constitute the input and output parameters. To better facilitate the exchange of data between services, as well as achieve better interoperability and re-usability, it is good practice to de?ne a common set of XML Schemas, often referred to as the canonical data model, which can be referenced by multiple services (or WSDL De?nitions). This means, we will need to share the same XML schema across multiple composites. While typically a service (or WSDL) will only be implemented by a single composite, it will often be invoked by multiple composites; so the corresponding WSDL will be shared across multiple composites. Within JDeveloper, the default behavior, when referencing a predefined schema or WSDL, is for it to add a copy of the file to our SOA project. However, if we have several composites, each referencing their own local copy of the same WSDL or XML schema, then every time that we need to change either the schema or WSDL, we will be required to update every copy. This can be a time-consuming and error-prone approach; a better approach is to have a single copy of each WSDL and schema that is referenced by all composites. The SOA infrastructure incorporates a Metadata Service (MDS), which allows us to create a library of XML artifacts that we can share across SOA composites. MDS supports two types of repositories: File-based repository: This is quicker and easier to set up, and so is typically used as the design-time MDS by JDeveloper. Database repository: It is installed as part of the SOA infrastructure. This is used at runtime by the SOA infrastructure. As you move projects from one environment to another (for example, from test to production), you must typically modify several environment-specific values embedded within your composites, such as the location of a schema or the endpoint of a referenced web service. By placing all this information within the XML artifacts deployed to MDS, you can make your composites completely agnostic of the environment they are to be deployed to. The other advantage of placing all your referenced artifacts in MDS is that it removes any direct dependencies between composites, which means that they can be deployed and started in any order (once you have deployed the artifacts to MDS). In addition, an SOA composite leverages many other XML artifacts, such as fault policies, XSLT Transformations, EDLs for event EDN event definitions, and Schematrons for validation, each of which may need to be shared across multiple composites. These can also be shared between composites by placing them in MDS. Defining a project structure Before placing all our XML artifacts into MDS, we need to define a standard file structure for our XML library. This allows us to ensure that if any XML artifact within our XML library needs to reference another XML artifact (for example a WSDL importing a schema), it can do so via a relative reference; in other words, the XML artifact doesn't include any reference to MDS and is portable. This has a number of benefits, including: OSB compatibility; the same schemas and WSDLs can be deployed to the Oracle Service Bus without modification Third-party tool compatibility; often we will use a variety of tools that have no knowledge of MDS to create/edit XML schemas, WSDLs, and so on (for example XML Spy, Oxygen) In this article, we will assume that we have defined the following directory structure under our <src> directory. Under the xmllib folder, we have defined multiple <solution> directories, where a solution (or project) is made up of one or more related composite applications. This allows each solution to maintain its XML artifacts independently. However, it is also likely that there will be a number of XML artifacts that need to be shared between different solutions (for example, the canonical data model for the organization), which in this example would go under <core>. Where we have XML artifacts shared between multiple solutions, appropriate governance is required to manage the changes to these artifacts. For the purpose of this article, the directory structure is over simpli?ed. In reality, a more comprehensive structure should be de?ned as part of the naming and deployment standards for your SOA Reference Architecture. The other consideration here is versioning; over time it is likely that multiple versions of the same schema, WSDL and so on, will require to be deployed side by side. To support this, we typically recommend appending the version number to the filename. We would also recommend that you place this under some form of version control, as it makes it far simpler to ensure that everyone is using an up-to-date version of the XML library. For the purpose of this article, we will assume that you are using Subversion. Creating a file-based MDS repository for JDeveloper Before we can reference this with JDeveloper, we need to define a connection to the file-based MDS. Getting ready By default, a file-based repository is installed with JDeveloper and sits under the directory structure: <JDeveloper Home>/jdeveloper/integration/seed This already contains the subdirectory soa, which is reserved for, and contains, artifacts used by the SOA infrastructure For artifacts that we wish to share across our applications in JDeveloper, we should create the subdirectory apps (under the seed directory); this is critical, as when we deploy the artifacts to the SOA infrastructure, they will be placed in the apps namespace We need to ensure that the content of the apps directory always contains the latest version of our XML library; as these are stored under Subversion, we simply need to check out the right portion of the Subversion project structure. How to do it... First, we need to create and populate our file-based repository. Navigate to the seed directory, and right-click and select SVN Checkout..., this will launch the Subversion Checkout window. For URL of repository, ensure that you specify the path to the apps subdirectory. For Checkout directory, specify the full pathname of the seed directory and append /apps at the end. Leave the other default values, as shown in the following screenshot, and then click on OK: Subversion will check out a working copy of the apps subfolder within Subversion into the seed directory. Before we can reference our XML library with JDeveloper, we need to define a connection to the file-based MDS. Within JDeveloper, from the File menu select New to launch the Gallery, and under Categories select General | Connections | SOA-MDS Connection from the Items list. This will launch the MDS Connection Wizard. Enter File Based MDS for Connection Name and select a Connection Type of File Based MDS. We then need to specify the MDS root folder on our local filesystem; this will be the directory that contains the apps directory, namely: <JDeveloper Home>jdeveloperintegrationseed Click on Test Connection; the Status box should be updated to Success!. Click on OK. This will create a file-based MDS connection in JDeveloper. Browse the File Based MDS connection in JDeveloper. Within JDeveloper, open the Resource Palette and expand SOA-MDS. This should contain the File Based MDS connection that we just created. Expand all the nodes down to the xsd directory, as shown in the following screenshot: If you double-click on one of the schema files, it will open in JDeveloper (in read-only mode). There's more... Once the apps directory has been checked out, it will contain a snapshot of the MDS artifacts at the point in time that you created the checkpoint. Over time, the artifacts in MDS will be modified or new ones will be created. It is important that you ensure that your local version of MDS is updated with the current version. To do this, navigate to the seed directory, right-click on apps, and select SVN Update. Creating Mediator using a WSDL in MDS In this recipe, we will show how we can create Mediator using an interface definition from a WSDL held in MDS. This approach enables us to separate the implementation of a service (a composite) from the definition of its contract (WSDL). Getting ready Make sure you have created a file-based MDS repository for JDeveloper, as described in the first recipe. Create an SOA application with a project containing an empty composite. How to do it... Drag Mediator from SOA Component Palette onto your composite. This will launch the Create Mediator wizard; specify an appropriate name (EmployeeOnBoarding in the following example), and for the Template select Interface Definition from WSDL Click on the Find Existing WSDLs icon (circled in the previous screenshot); this will launch the SOA Resource Browser. Select Resource Palette from the drop-down list (circled in the following screenshot). Select the WSDL that you wish to import and click on OK. This will return you to the Create Mediator wizard window; ensure that the Port Type is populated and click on OK. This will create Mediator based on the specified WSDL within our composite. How it works... When we import the WSDL in this fashion, JDeveloper doesn't actually make a copy of the schema; rather within the componentType file, it sets the wsdlLocation attribute to reference the location of the WSDL in MDS (as highlighted in the following screenshot). For WSDLs in MDS, the wsdlLocation attribute uses the following format: oramds:/apps/<wsdl name> Where oramds indicates that it is located in MDS, apps indicates that it is in the application namespace and <wsdl name> is the full pathname of the WSDL in MDS. The wsdlLocation doesn't specify the physical location of the WSDL; rather it is relative to MDS, which is specific to the environment in which the composite is deployed. This means that when the composite is open in JDeveloper, it will reference the WSDL in the file-based MDS, and when deployed to the SOA infrastructure, it will reference the WSDL deployed to the MDS database repository, which is installed as part of the SOA infrastructure. There's more... This method can be used equally well to create a BPEL process based on the WSDL from within the Create BPEL Process wizard; for Template select Base on a WSDL and follow the same steps. This approach works well with Contract First Design as it enables the contract for a composite to be designed first, and when ready for implementation, be checked into Subversion. The SOA developer can then perform a Subversion update on their file-based MDS repository, and then use the WSDL to implement the composite Creating Mediator that subscribes to EDL in MDS In this recipe, we will show how we can create Mediator that subscribes to an EDN event whose EDL is defined in MDS. This approach enables us to separate the definition of an event from the implementation of a composite that either subscribes to, or publishes, the event. Getting ready Make sure you have created a file-based MDS repository for JDeveloper, as described in the initial recipe. Create an SOA application with a project containing an empty composite. How to do it... Drag Mediator from SOA Component Palette onto your composite. This will launch the Create Mediator wizard; specify an appropriate name for it (UserRegistration in the following example), and for the Template select Subscribe to Events. Click on the Subscribe to new event icon (circled in the previous screenshot); this will launch the Event Chooser window. Click on the Browse for Event Definition (edl) files icon (circled in the previous screenshot); this will launch SOA Resource Browser. Select Resource Palette from the drop-down list. Select the EDL that you wish to import and click on OK. This will return you to the Event Chooser window; ensure that the required event is selected and click on OK. This will return you to the Create Mediator window; ensure that the required event is configured as needed, and click on OK. This will create an event subscription based on the EDL specified within our composite. How it works... When we reference an EDL in MDS, JDeveloper doesn't actually make a copy of the EDL; rather within the composite.xml file, it creates an import statement to reference the location of the EDL in MDS. There's more... This approach can be used equally well to subscribe to an event within a BPEL process or publish an event using either Mediator or BPEL.
Read more
  • 0
  • 0
  • 2433

article-image-getting-your-course-ready-new-semester
Packt
22 Jan 2013
10 min read
Save for later

Getting Your Course Ready for a New Semester

Packt
22 Jan 2013
10 min read
(For more resources related to this topic, see here.) Introduction Getting your course ready for students at the beginning of each semester can be a daunting task. You'll need to verify links to external content, make sure that previous materials have been copied successfully to your new course, and modify the existing assignment dates, among other tasks. You get the point—there are quite a few things you need to take care of before students ever see your course. This article offers recipes for streamlining this process to make setting up your course as stress-free as possible. The first two recipes deal with getting materials into your course, whether you're copying an entire course from a previous semester or importing a compatible course cartridge provided by a textbook publisher. You may be surprised to know that course cartridges created for other Learning Management Systems ( LMSs) , such as Blackboard and Moodle, can often be imported without any trouble! Other recipes in the article focus on making quick work of date changes and external link validation. We'll wrap up the article by previewing everything from the student's view. Please note that the recipes in this article, as well as the rest of the book, are written for Version 10.0 of the Desire2Learn Learning Environment. While many of the recipes are also applicable to earlier versions of the system, you may need to modify the steps to follow along. Copying course materials from a previous semester Copying materials, activities, and settings from one course to another can save you a considerable amount of time when preparing for the start of a new semester. The learning environment's Import /Export /Copy Components tool allows you to easily clone an entire course or select just the parts of the original course that you want to use in a new course. In this recipe, we will discuss copying materials from an existing course within the system. We will use the same tool to import a course cartridge from a publisher in the next recipe. Getting ready The Desire2Learn (D2L) Learning Environment is highly customizable, and each organization that uses it can customize many aspects of the user experience. This recipe assumes that your school has allowed the use of the Import/Export/Copy Components tool for your specific role within the system. In order to complete this recipe, you'll also need access to two courses—an empty course that we will be copying materials to and another one that contains the materials we will be copying. To copy materials from one course to another, your role in both courses needs to allow the use of the Copy Components function. For example, you wouldn't be able to copy quizzes from a class in which you are enrolled as a student into one that you are teaching. How to do it... We will be working with two courses in this recipe – a new, empty course and an existing course that contains the materials to be copied. Remember to start by accessing the destination course or the course that you want to copy materials to. Start by accessing the destination course from My Homepage. Click on the Edit Course link in the course navigation bar. Click the Import/Export/Copy Components link under the Site Resources heading. Select the option Copy Components from Another Org Unit and then click on Start. Locate the course from which we will be copying materials by clicking on the Search for offering link. If needed, use the search tool at the top of the list of courses to help locate the course. You can also click on any of the column headers to sort the list of courses based on that field (clicking twice reverses the order). Check the radio button to the left of the course, and click on the Add Selected button. Within a few seconds, the page updates to display all of the available components from the course we just selected. To clone an entire course, check the Select All Components box, and click on the Continue button. Since we chose to clone an entire course, we can continue on our way by clicking on the Finish button. Depending on the amount of materials being copied and the server load, the copy process may take a few seconds to several minutes. When the Done button becomes active, it means that the process has completed. As each tool finishes copying, you'll see its progress indicator change into a green checkmark. Anything that didn't copy successfully will be noted in the summary. How it works… We start off by accessing the destination course. The Search for Offering screen displays a list of all of the courses you currently have access to copy from. If you've been teaching for a while, this list may be quite large. The search and filtering tools at the top of the course offering list may be helpful if you are having difficulty finding the correct course from the list. In this recipe, we copied all the available components from the source offering by choosing the Select All Components option. However, you can copy individual tools or even individual items within those tools by choosing the Select individual items to copy option. If you decide to copy specific components, then you need to select those items on the Choose Components to Copy screen, as shown in the following screenshot: There's more... If you're copying large course files or complex question libraries, there's a chance that your browser will time out before the copy process is complete. If this happens, there are a few things you can do to complete the task: Break up the copy process into several smaller jobs. If, for example, you're getting error messages while copying Course Files, try only copying half of the files, then return to the tool and try the second half later. The current server load can greatly impact the time it takes to copy components. You may want to try copying the components during an off-peak time. If you experience a browser time-out while copying Course Files, you might want to visit File Manager and look for duplicate or large files in the source course. Deleting unnecessary files can speed up the process significantly. Your Desire2Learn administrator has access to other ways of cloning a course or copying files. If you continue to experience difficulty with the tool, talking with your friendly admin would be a great idea! Importing a publisher's course cartridge Publishers frequently offer complimentary course cartridges to instructors who adopt their textbooks. The content of these cartridges varies greatly, but can include content and files, assessments, web links, and more. In this recipe, we will walk through the process of importing a course cartridge into an existing Desire2Learn Learning Environment course. Getting ready In order to complete this recipe, you'll need either a publisher's cartridge or an export from another Desire2Learn Learning Environment course. These files come in the form of .zip archives. Publishers typically offer different versions of cartridges for several of the major learning management systems. While you may not always find a version of a particular cartridge formatted for the Desire2Learn Learning Environment, you may be surprised to know that versions made for other systems, such as Blackboard 6 and WebCT, will typically work just fine. Check with your system administrator, if you have any difficulties importing a cartridge. You will also need access to the Import/Export/Copy Components tool. You will need to talk with your Desire2Learn system administrator if your role in the current course does not include access to the tool. How to do it... Start off by accessing the destination course from the My Home page. Click on the Edit Course link in the course's navigation bar. Access the Import/Export/Copy Components tool by clicking on the link under the Site Resources heading. Select the option to Import Components. Then, select the from a File option and choose the cartridge to import by clicking on the Choose File button. Click on the Start button after locating and selecting the file: Click on the Continue button on the Preprocessing screen when it becomes available. Import the entire cartridge's contents by choosing the Select All Components checkbox and then clicking on the Continue button. Click on the Continue button on the Confirm Import Selections screen. The process is complete when all of the progress indicators have changed to green checkmarks. Click on Finish, then Done when the components are finished copying. How it works... We start off by accessing the Import/Export/Copy Components tool in the destination course. After selecting the .zip folder to import, the system uploads and pre-processes the archive's manifest file. Depending on the complexity of the cartridge and the size of the archive, this can happen very quickly or it may take quite some time. After the pre-process action is complete, we choose to import the entire cartridge into the course, just as we did in the previous recipe. While this is often the easiest approach, it is possible to pick and choose individual components (such as Quizzes or Grades) or even individual items (such as specific quizzes or grade items), as we will discuss in the following section. Once you verify the components to be imported, it's just a matter of waiting for the progress indicators to become green checkmarks. Any item not able to be imported will be displayed on screen at the end of the process. You probably won't run into too many problems unless you are importing extremely large or complex cartridges, but it is always a good idea to verify that everything was successful before clicking on the Done button. There's more... In the last two scenarios, we have seen examples of copying and importing entire courses. While this is common at the beginning of the semester, there may be times when you will need only certain parts of another course. Suppose, for example, you only want the question library portion of a publisher's course cartridge. Luckily, this is easily accomplished by selecting individual components on the Choose Components to Copy screen instead of the All Components option. In the following screenshot, I have chosen to copy all the available Content items, but only selected Discussions and Dropbox folders: After selecting the components to copy and clicking on the Continue button, I'm prompted to select the individual quizzes I want to copy into my course. Clicking on the Expand All link shows a list of all quizzes, and selecting individual items to be imported is as easy as checking the option next to the item titles. Since I've chosen to also import selected Dropbox folders, I would complete a similar process for selecting those items on the next screen: I should point out one "gotcha" that frequently causes trouble for new users of the Desire2Learn Learning Environment. Items under the Content heading are frequently linked to uploaded documents or system-generated HTML files, which are stored in the File Manager. Unfortunately, selecting the items under Content doesn't copy these associated files, so you need to manually select these files under Course Files. Since this can be a somewhat tricky task depending on how you've organized your files, you may find it easier to copy everything and delete what you do not need. See also The Copying course materials from a previous semester recipe
Read more
  • 0
  • 0
  • 1622

article-image-mobile-devices
Packt
21 Jan 2013
11 min read
Save for later

Mobile Devices

Packt
21 Jan 2013
11 min read
(For more resources related to this topic, see here.) So let's get on with it... Important preliminary points While you can use the Android emulator for the Android parts of the article, it is highly recommended that you have a real device that you can use. The reason is that the emulator tries to emulate the hardware that phones run on. This means that it needs to translate it to a low-level command that ARM-based devices would understand. A real iOS device is not needed as that simulates a device and therefore is significantly faster. The device will also need to have Android 4.0+ or better known as Ice Cream Sandwich. You will need to download the Android App from http://code.google.com/p/selenium/downloads/list. It will be named android-server-<version>.apk where <version> is the latest version. You will however need to have a machine with OS X on to start the simulator since it is part of XCode. If you do not have XCode installed you can download it via the AppStore. You will also need to install all of the command-line tools that come with XCode. You will also need to check out the Selenium code from its source repository. You need to build the WebDriver code for iOS since it can't be added to the Apple App Store to be downloaded on to devices. Working with Android Android devices are becoming commonplace with owners of smartphones and tablets. This is because there are a number of handset providers in the market. This has meant that in some parts of the world, it is the only way that some people can access the Internet. With this in mind, we need to make sure that we can test the functionality. Emulator While it is not recommended to use the emulator due to the speed of it, it can be really useful. Since it will act like a real device in that it will run all the bits of code that we want on the virtual device, we can see how a web application will react. Time for action — creating an emulator If you do not have an Android device that you can use for testing, then you can set up an Android emulator. The emulator will then get the Selenium WebDriver APK installed and then that will control the browser on the device. Before we start, you will need to download the Android SDK from http://developer.android.com/sdk/index.html. Open up a command prompt or a terminal. Enter cd <path>/android-sdk/tools where <path> is the path to the android-sdk directory. Now enter ./android create avd -n my_android -t 14 where: –n my_android gives the emulator the name my_android. –t 14 tells it which version of android to use. 14 and higher is Android 4 and higher support. When prompted Do you wish to create a custom hardware profile [no], enter no. Run the emulator with: ./emulator -avd my_android & It will take some time to come up but once it has been started, you will not have to restart unless it crashes or you purposefully close it. Once loaded you should see something like the following: What just happened? We have just seen what is involved in setting up the Android emulator that we can use for testing of mobile versions of our applications. As was mentioned, we need to make sure that we set up the emulator to work with Android 4.0 or later. For the emulator we need to have a target platform of 14 or later. Now that we have this done, we can have a look at installing the WebDriver Server on the device. Installing the Selenium WebDriver Android Server We have seen that we can access different machines and control the browsers on those machines with Selenium WebDriver RemoteDriver. We need to do the same with Android. The APK file that you downloaded earlier is the Selenium Server that is specifically designed for Android devices. It has a smaller memory footprint since mobile devices do not have the same amount of memory as your desktop machine. We need to install this on the emulator or the physical device that you have. Time for action — installing the Android Server In this section, we will learn the steps required to install the Android server on the device or emulator that you are going to be using. To do this, you will need to have downloaded the APK file from http://code.google.com/p/selenium/downloads/list. If you are installing this onto a real device make sure that you allow installs from Unknown Sources. Open a command prompt or a terminal. Start the emulator or device if you haven't already. We need to run the available devices: <path to>/android_sdk/platform-tools/adb devices It will look like this: Take the serial number of the device. Now we need to install. We do that with the following command: adb -s <serialId> -e install -r android-server.apk Once that is done you will see this in the command prompt or terminal: And on the device you will see: What just happened? We have just seen how we can install the Android Server on the server. This process is useful for installing any Android app from the command line. Now that this is done we are ready to start looking at running some Selenium WebDriver code against the device. Creating a test for Android Now that we have looked at getting the device or emulator ready, we are ready to start creating a test that will work against a site. The good thing about the Selenium WebDriver, like Selenium RC, is that we can easily move from browser to browser with only a small change. In this section, we are going to be introduced to the AndroidDriver. Time for action — using the Android driver In this section we are going to be looking at running some tests against an Android device or emulator. This should be a fairly simple change to our test, but there are a couple of things that we need to do right before the test runs. Open a command prompt or terminal. We need to start the server. We can do this by touching the app or we can do this from the command line with the following command: adb -s <serialId> shell am start -a android.intent.action.MAIN -n org.openqa.selenium.android.app/.MainActivity We now need to forward all the HTTP traffic to the device or emulator. This means that all the JSON Wire Protocol calls, that we learnt earlier, go to the device. We do it with: adb -s <serialId> forward tcp:8080 tcp:8080 Now we are ready to update our test. I will show an example from the previous test: import junit.framework.TestCase; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.android.AndroidDriver; public class TestChapter7 { WebDriver driver; @Before public void setUp(){ driver = new AndroidDriver(); driver.get("http://book.theautomatedtester.co.uk/chapter4"); } @After public void tearDown(){ driver.quit(); } @Test public void testExamples(){ WebElement element = driver.findElement(By.id("nextBid")); element.sendKeys("100"); } } Run the test. You will see that it runs the same test against the Android device. What just happened? We have just run our first test against an Android device. We saw that we had to forward the HTTP traffic to port 8080 to the device. This means that the normal calls, which use the JSON Wire Protocol, will then be run on the device. Currently Opera Software is working on getting OperaDriver to work on Mobile devices. There are a few technical details that are being worked on and hopefully in the future we will be able to use it. Mozilla is also working on their solution for Mobile with Selenium. Currently a project called Marionette is being worked on that allows Selenium to work on Firefox OS, Firefox Mobile for Android as well as Firefox for Desktop. You can read up on it at https://wiki.mozilla. org/Auto-tools/Projects/Marionette. Have a go hero — updating tests for Android Have a look at updating all of the tests that you would have written so far in the book to run on Android. It should not take you long to update them. Running with OperaDriver on a mobile device In this section we are going to have a look at using the OperaDriver, the Selenium WebDriver object to control Opera, in order to drive Opera Mobile. Opera has a large market share on mobile devices especially on lower end Android devices. Before we start we are going to need to download a special emulator for Opera Mobile. As of writing this, it has just come out of Opera's Labs so the download links may have been updated. Windows: http://www.opera.com/download/get.pl?id=34969⊂=true&nothanks=yes&location=360. Mac: http://www.opera.com/download/get.pl?id=34970⊂=true&nothanks=yes&location=360. Linux 64 Bit: Deb: http://www.opera.com/download/get.pl?id=34967⊂=true&nothanks=yes&location=360. Tarball: http://www.opera.com/download/get.pl?id=34968⊂=true&nothanks=yes&location=360. Linux 32 Bit: Deb: http://www.opera.com/download/get.pl?id=34965⊂=true&nothanks=yes&location=360. TarBall: http://www.opera.com/download/get.pl?id=34966⊂=true&nothanks=yes&location=360. Let's now see this in action. Time for action — using OperaDriver on Opera Mobile To make sure that we have the right amount of coverage over the browsers that users may be using, there is a good chance that you will need to add Opera Mobile. Before starting, make sure that you have downloaded the version of the emulator for your Operating System with one of the links mentioned previously. Create a new test file. Add the following code to it: import junit.framework.TestCase; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; public class TestChapter7OperaMobile{ WebDriver driver; } What we now need to do is add a setup method. We will have to add a couple of items to our DesiredCapabilities object. This will tell OperaDriver that we want to work against a mobile version. @Before public void setUp(){ DesiredCapabilities c = DesiredCapabilities.opera(); c.setCapability("opera.product", OperaProduct.MOBILE); c.setCapability("opera.binary", "/path/to/my/custom/opera-mobile-build"); driver = new OperaDriver(c); } Now we can add a test to make sure that we have a working test again: @Test public void testShouldLoadGoogle() { driver.get("http://www.google.com"); //Let's find an element to see if it works driver.findElement(By.name("q")); } Let's now add a teardown: @After public void teardown(){ driver.quit(); } Your class altogether should look like the following: import junit.framework.TestCase; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; public class TestChapter7OperaMobile{ WebDriver driver; @Before public void setUp(){ DesiredCapabilities c = DesiredCapabilities.opera(); c.setCapability("opera.product", OperaProduct.MOBILE); c.setCapability("opera.binary", "/path/to/my/custom/opera-mobile-build"); driver = new OperaDriver(c); } @After public void teardown(){ driver.quit(); } @Test public void testShouldLoadGoogle() { driver.get("http://book.theautomatedtester.co.uk"); } } And the following should appear in your emulator: What just happened? We have just seen what is required to run a test against Opera Mobile using OperaDriver. This uses the same communication layer that is used in communicating with the Opera desktop browser called Scope. We will see the mobile versions of web applications, if they are available, and be able to interact with them. If you would like the OperaDriver to load up tablet size UI, then you can add the following to use the tablet UI with a display of 1280x800 pixels. This is a common size for tablets that are currently on the market. c.setCapability("opera.arguments", "-tabletui -displaysize 1280x800"); If you want to see the current orientation of the device and to access the touch screen elements, you can swap OperaDriver object for OperaDriverMobile. For the most part, you should be able to do nearly all of your work against the normal driver.
Read more
  • 0
  • 0
  • 2110
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-building-control-m-infrastructure
Packt
21 Jan 2013
10 min read
Save for later

Building the Control-M Infrastructure

Packt
21 Jan 2013
10 min read
(For more resources related to this topic, see here.) Three ages to workload automation Enterprise-wide workload automation does not happen overnight. Converting nonstandardized batch processing into a centralized workload automation platform can be time consuming and risky. We need to gain full understanding of the existing running batch jobs before moving them into the new platform, that is, how the jobs are currently scheduled? What are the relationships between these jobs? Which are the higher priority ones? Then based on the amount of jobs and the complexity, we can decide the method of the migration process, which either can be performed automatically or manually. Once jobs are migrated, a series of test needs to be performed before moving into production. Each production cutover is preferably to be transparent to the business' normal operation as much as possible or to be performed within the agreed outage window. Apart from the technical challenges, "people issues" can be the next road block. First of all, users need to be educated about the tool. It can take a lot of time for users to accept and get used to the new way of operation. The bigger challenge is to let the application developers take in and apply the "centralized workload automation" concept. So during each IT project's development phase, they can utilize built-in features provided by the workload automation platform as much as possible rather than reinventing the wheel. Forcing users and developers to fully take in the centralized workload automation concept and change their ways of working with batch processing straightaway could lead the project to an ultimate failure. Instead, different areas of approach and actions should be taken in stages according to the actual IT environment condition. We can group these different approaches and actions into three "ages", that is, the stone age, iron age, and golden age. Stone age Unless we are building an IT infrastructure from scratch, for any reasonable size organization, they should have a noticeable amount of batch jobs running already to serve different business needs. Such processes are either running by OS/ application's inbuilt batch features or scheduled from homegrown scheduling tools (sometimes they can be manual tasks too). For example, these tasks can be: End of day (EOD) reporting jobs ERP application's overnight batch Housekeeping jobs, for example, database backup and log recycling jobs Depending on the organization's business requirements, the number of batch jobs required to achieve the outcome can start from a few hundred and go up to tens of thousands across a large number of different job execution hosts.In a heterogeneous environment it is extremely challenging to run cross-platform batch processing by using different tools, especially when the number of tasks is large and the batch window is small. Therefore, these batch processings are the most essential and critical ones to be consolidated into a centralized scheduling platform. On the other hand, these types of processing tasks are the "low hanging fruits" - relatively easy to identify and migrate, simply because they have already been clearly defined and scheduled by existing batch scheduling mechanisms, which means it is more likely that the job scheduling information can be extracted from these sources. At the end of the day, it all comes down to the question of how to migrate the jobs into a centralized scheduling platform and how are they going to be triggered in the new environment. "How to migrate", as in, how the jobs should be extracted from the existing batch scheduling mechanism and how they should be imported into the new environment. It can be done by using a job migration program, if it is available, or else someone has to manually redefine the jobs from scratch. "How jobs should be triggered", as in, should the job directly trigger the script/command or use scheduling tool's extended features (that is, Control-M Control Modules) for batch processing within a particular application? The bottom line is – this stage is all about standardizing the way the existing batch jobs are executed and managed by consolidating them into a centralized tool. The migration process should be relatively straightforward and should not require major modification to application codes as well as each application's architecture. However, this will change the way users manage and monitor batch jobs forever. It is the initial step for standardizing batch management and batch optimization, therefore we call it the "stone age". The successful implementation of "stone age" will benefit the organization without a doubt. After a while, users will realize how easy it is to manage cross-platform batch flows from a centralized GUI. They no longer need to look at different screens to trigger jobs or monitor a job's execution. Instead, batch jobs are triggered automatically and are to be managed by exceptions. Iron age A lot of organizations stop improving and stop extending their centralized batch environment once they have completed the stone age. As the business rules are becoming more and more complex, it is common to see silos of batch processing existing in different applications that are related but not linked together, that is, they do not know about other processing taking place and how they relate. Plus on top of that, we have business process steps that are being "patched up" by mechanisms outside the centralized scheduling tool. As a result, batch flows within the centralized scheduling tool are commonly unable to present an end-to-end business process. One possibility is that these organizations believe that they have already got everything that the centralized scheduling tool is capable of – triggering executables at a fixed time on a predefined day. Rather than someone taking the lead to discover and try other features within the batch scheduling tool, people in different parts of the organization always try to develop their own ways to handle more advanced requirements such as event triggering processing or inter-application file transfers. In late 2010, I was involved in an EAI development project. During my meeting with some JAVA developers, I noticed they still think batch processing (in Control-M) is all about triggering some program to run at a fixed time and nothing more. Unless they change their views on batch processing and understand what "workload automation" is about, they won't be able to fully utilize the features provided by a workload automation tool for the applications they develop. As a result, after the application goes live, there will be a large amount of processing to be done by inbuilt or self-coded scheduling mechanisms while the other half is running in Control-M. Iron age is about changing how batch processing is initially designed, that is, improving it by fully utilizing the capabilities of the batch scheduling tool. This requires ongoing education and letting application designers and developers accept and use features that are already available in a centralized scheduling tool rather than reinventing the wheel. This is a lot more challenging than simply extracting and importing batch-processing data from one tool to another during the stone age. Also, the benefits we get from the iron age are not as easy to measure as what we can directly see during the stone age. In the stone age, the users instantly get the benefits of managing batch from a centralized scheduling tool. In reality, application development teams may rather write their own code to meet the processing requirements so that they can have total control of the application they have developed. Application developers may think "Why should we learn a new tool when we can simply write a few lines of code to achieve event-driven triggering?" or "In the future, if we want to change the way my application works, we might have to log a change request for the scheduling team to modify the batch job in Control-M, whereas having everything done in the code, we will have full control, therefore saving us a lot of hassle." A certain degree of politics can also be involved. For example, the management of the application development team may think "If half of our work is done by the scheduling tool, where is our value?" or "We want to be more important to the organization's IT in front of the IT directors!" Another scenario with organizations is that they outsource their application development. Instead of building a new system from scratch for each project, the outsourcing companies try to modify what they have already implemented in other organizations for a similar project. In such cases, the outsourcing companies, most of the time, will refuse to do any major modifications to the application just to fit into the centralized scheduling tool. They might believe that by doing so, they can ensure that the project gets delivered with minimal time, cost, and risk. But in reality, the result always turns out the opposite. In order to avoid falling into one of the categories mentioned above, the person who is going to promote "iron age" within an organization should work on people rather than expecting everything to turn out fine by only focusing on the technology. At the same time, higher-level management in the organization should provide a level of assistance by enforcing an organization-wide scheduling standard so the organization's IT can get the most out of the centralized batch scheduling platform and therefore maximize the business' ROI on it. The definition of a successfully-implemented iron age is that the organization should see that batch flows are becoming more meaningful at the business service level (that is, presents the complete business process) and is optimized to process within a shorter batch window by using features provided with the batch scheduling tool (for example, percentage of processing is moved into the event triggered batch, which can happen outside the batch window). From an ongoing maintenance point of view, there are less homegrown tools to manage. The total time and effort required for application development may also reduce if the developers are familiar with the batch scheduling tool and know how to leverage the available features properly. Golden age Golden age refers to the full implementation of workload automation. It is not as easy to achieve as it sounds, because there are a number of prerequisites that need to be met before the organization even considers it. First of all, the centralized scheduling platform needs to be upgraded to a more up-todate version that provides the workload automation ability, such as Control-M version 7. Secondly, in order to get the true value from workload automation, the organization needs to have both the stone age and the iron age successfully implemented, that is, jobs in the centralized scheduling tool need to be well defined and presenting the actual business processes. Furthermore, it depends on how far the organization wants to go down this road in order to reach the pinnacle. The IT environment may look at providing the foundation to allow the batch environment to become more dynamic by using resource virtualization and cloud computing technologies. Once all prerequisites are met, implementing the golden age requires the batch environment designer to work closely with a system architect and application developers. They need to transform the existing bath to become more flexible (moving away from batch jobs' static nature), so the workload automation tool can schedule them according to business policies and route the workload according to runtime load to the best available virtual resource for execution. The batch job should also be designed by following the SOA design principles for reusability and should be loosely coupled. In the golden age, batch workloads are managed according to the business policies and service agreement. The limited machine resource bottleneck of batch processing is not much of a concern because resources can be acquired whenever needed. In this case, the system can handle a sudden spark of processing requests, while still ensuring the process to complete within its agreed batch window or SLA.
Read more
  • 0
  • 0
  • 2306

Packt
18 Jan 2013
7 min read
Save for later

New Connectivity APIs – Android Beam

Packt
18 Jan 2013
7 min read
(For more resources related to this topic, see here.) Android Beam Devices that have NFC hardware can share data by tapping them together. This could be done with the help of the Android Beam feature. It is similar to Bluetooth, as we get seamless discovery and pairing as in a Bluetooth connection. Devices connect when they are close to each other (not more than a few centimeters). Users can share pictures, videos, contacts, and so on, using the Android Beam feature. Beaming NdefMessages In this section, we are going to implement a simple Android Beam application. This application will send an image to another device when two devices are tapped together. There are three methods that are introduced with Android Ice Cream Sandwich that are used in sending NdefMessages. These methods are as follows: setNdefPushMessage() : This method takes an NdefMessage as a parameter and sends it to another device automatically when devices are tapped together. This is commonly used when the message is static and doesn't change. setNdefPushMessageCallback() : This method is used for creating dynamic NdefMessages. When two devices are tapped together, the createNdefMessage() method is called. setOnNdefPushCompleteCallback() : This method sets a callback which is called when the Android Beam is successful. We are going to use the second method in our sample application. Our sample application's user interface will contain a TextView component for displaying text messages and an ImageView component for displaying the received images sent from another device. The layout XML code will be as follows: <RelativeLayout android_layout_width="match_parent" android_layout_height="match_parent" > <TextView android_id="@+id/textView" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_centerHorizontal="true" android_layout_centerVertical="true" android_text="" /> <ImageView android_id="@+id/imageView" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_below="@+id/textView" android_layout_centerHorizontal="true" android_layout_marginTop="14dp" /> </RelativeLayout> Now, we are going to implement, step-by-step, the Activity class of the sample application. The code of the Activity class with the onCreate() method is as follows: public class Chapter9Activity extends Activity implementsCreateNdefMessageCallback{NfcAdapter mNfcAdapter;TextView mInfoText;ImageView imageView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);imageView = (ImageView) findViewById(R.id.imageView);mInfoText = (TextView) findViewById(R.id.textView);// Check for available NFC AdaptermNfcAdapter =NfcAdapter.getDefaultAdapter(getApplicationContext());if (mNfcAdapter == null){mInfoText = (TextView) findViewById(R.id.textView);mInfoText.setText("NFC is not available on this device.");finish();return;}// Register callback to set NDEF messagemNfcAdapter.setNdefPushMessageCallback(this, this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.main, menu);return true;}} As you can see in this code, we can check whether the device provides an NfcAdapter. If it does, we get an instance of NfcAdapter. Then, we call the setNdefPushMessageCallback() method to set the callback using the NfcAdapter instance. We send the Activity class as a callback parameter because the Activity class implements CreateNdefMessageCallback.In order to implement CreateNdefMessageCallback, we should override the createNdefMessage()method as shown in the following code block: @Overridepublic NdefMessage createNdefMessage(NfcEvent arg0) {Bitmap icon =BitmapFactory.decodeResource(this.getResources(),R.drawable.ic_launcher);ByteArrayOutputStream stream = new ByteArrayOutputStream();icon.compress(Bitmap.CompressFormat.PNG, 100, stream);byte[] byteArray = stream.toByteArray();NdefMessage msg = new NdefMessage(new NdefRecord[] {createMimeRecord("application/com.chapter9", byteArray), NdefRecord.createApplicationRecord("com.chapter9")});return msg;}public NdefRecord createMimeRecord(String mimeType, byte[]payload) {byte[] mimeBytes = mimeType.getBytes(Charset.forName("USASCII"));NdefRecord mimeRecord = newNdefRecord(NdefRecord.TNF_MIME_MEDIA,mimeBytes, new byte[0], payload);return mimeRecord;} As you can see in this code, we get a drawable, convert it to bitmap, and then to a byte array. Then we create an NdefMessage with two NdefRecords. The first record contains the mime type and the byte array. The first record is created by the createMimeRecord() method. The second record contains the Android Application Record ( AAR). The Android Application Record was introduced with Android Ice Cream Sandwich. This record contains the package name of the application and increases the certainty that your application will start when an NFC Tag is scanned. That is, the system firstly tries to match the intent filter and AAR together to start the activity. If they don't match, the activity that matches the AAR is started. When the activity is started by an Android Beam event, we need to handle the message that is sent by the Android Beam. We handle this message in the onResume() method of the Activity class as shown in the following code block: @Overridepublic void onResume() {super.onResume();// Check to see that the Activity started due to an AndroidBeamif (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {processIntent(getIntent());}}@Overridepublic void onNewIntent(Intent intent) {// onResume gets called after this to handle the intentsetIntent(intent);}void processIntent(Intent intent) {Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);// only one message sent during the beamNdefMessage msg = (NdefMessage) rawMsgs[0];// record 0 contains the MIME type, record 1 is the AARbyte[] bytes = msg.getRecords()[0].getPayload();Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0,bytes.length);imageView.setImageBitmap(bmp);} As you can see in this code, we firstly check whether the intent is ACTION_NDEF_DISCOVERED. This means the Activity class is started due to an Android Beam. If it is started due to an Android Beam, we process the intent with the processIntent() method. We firstly get NdefMessage from the intent. Then we get the first record and convert the byte array in the first record to bitmap using BitmapFactory . Remember that the second record is AAR, we do nothing with it. Finally, we set the bitmap of the ImageView component. The AndroidManifest.xml file of the application should be as follows: <manifest package="com.chapter9"android:versionCode="1"android:versionName="1.0" ><uses-permission android_name="android.permission.NFC"/><uses-feature android_name="android.hardware.nfc"android:required="false" /><uses-sdkandroid:minSdkVersion="14"android:targetSdkVersion="15" /><applicationandroid:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" ><activityandroid:name=".Chapter9Activity"android:label="@string/title_activity_chapter9" ><intent-filter><action android_name="android.intent.action.MAIN" /><categoryandroid:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter><actionandroid:name="android.nfc.action.NDEF_DISCOVERED" /><categoryandroid:name="android.intent.category.DEFAULT" /><data android_mimeType="application/com.chapter9" /></intent-filter></activity></application></manifest> As you can see in this code, we need to set the minimum SDK to API Level 14 or more in the AndroidManifest.xml file because these APIs are available in API Level 14 or more. Furthermore, we need to set the permissions to use NFC. We also set the uses feature in AndroidManifest.xml. The feature is set as not required. This means that our application would be available for devices that don't have NFC support. Finally, we create an intent filter for android.nfc.action.NDEF_DISCOVERED with mimeType of application/com.chapter9. When a device sends an image using our sample application, the screen will be as follows: Summary In this article, we firstly learned the Android Beam feature of Android. With this feature, devices can send data using the NFC hardware. We implemented a sample Android Beam application and learned how to use Android Beam APIs. Resources for Article : Further resources on this subject: Android 3.0 Application Development: Multimedia Management [Article] Animating Properties and Tweening Pages in Android 3-0 [Article] Android User Interface Development: Animating Widgets and Layouts [Article]
Read more
  • 0
  • 0
  • 4862

article-image-creating-and-configuring-basic-mobile-application
Packt
17 Jan 2013
3 min read
Save for later

Creating and configuring a basic mobile application

Packt
17 Jan 2013
3 min read
(For more resources related to this topic, see here.) How to do it... Follow these steps: Inside your Magento Admin Panel, navigate to Mobile | Manage Apps on the main menu. Click on the Add App button in the top-right corner. The New App screen will be shown. Since we have to create a separate application for each mobile device type, let's choose our first targeted platform. Under the Device Type list, we can choose iPad, iPhone, or Android. For the purpose of this recipe, since the procedure is almost the same for all device types, I will choose Android. After choosing the desired Device Type, click on the Continue button, and click on the General tab under Manage Mobile App. First we have to fill in the box named App Name. Choose an appropriate name for your mobile application and insert it there. Under the Store View list, make sure to choose our earlier defined Store View with updated mobile theme exceptions, our mobile copyright information, and category thumbnail images. Set the Catalog Only App option to No. Click on the Save and Continue Edit button in the top-right corner of the screen. Now you will notice a warning message from Magento that says something like the following: Please upload an image for "Logo in Header" field from Design Tab. Please upload an image for "Banner on Home Screen" field from Design Tab. Don't worry, Magento expects us to add some basic images that we prepared for our mobile app. So let's add them. Click on the Design tab on the left-hand side of the screen. Locate the Logo in Header label and click on the Browse... button on the right to upload the prepared small header logo image. Make sure to upload the image with proper dimensions for the selected device type (iPhone, iPad, or Android). In the same way, click on the Browse... button on the right of the Banner on Home Screen label and choose the appropriate prepared and resized banner image. Now, let's click on the Save and Continue Edit button in order to save our settings. How it works For each device type, we will have to create a new Magento Mobile application in our Magento Mobile Admin Panel. When we once select Device Type and click on the Save button, we are unable to change Device Type later for that application. If we have chosen the wrong Device Type, the only solution is to delete this app and to create a new one with the proper settings. The same applies with our chosen Store View when configuring new app. There's more... When our configuration is saved for the first time, auto-generated App Code will appear on the screen and that will be the code which will uniquely identify our Device Type—the assigned application to be properly recognized with Magento Mobile. For example, defand1 means that this application is the first defined application for the default Store View targeted on android (def = default store view, and=android). How to use mobile application as catalog only Under step 7 we set Catalog Only App to No, but sometimes, if we don't need checkout and payment in our mobile app, but we want to use it just as catalog to show products to our mobile customers, we just need to set the Catalog Only option to Yes. Summary So this is how we create the basic configuration for our mobile app Resources for Article : Further resources on this subject: Integrating Twitter with Magento [Article] Integrating Facebook with Magento [Article] Getting Started with Magento Development [Article]
Read more
  • 0
  • 0
  • 4028

article-image-team-foundation-server-2012
Packt
17 Jan 2013
9 min read
Save for later

Team Foundation Server 2012

Packt
17 Jan 2013
9 min read
(For more resources related to this topic, see here.) What is Team Foundation Server 2012? TFS 2012 governs all the aspects of software development, including requirement management, project management, development, testing, deployment and quality assurance. It has four major traits that make this very valuable: Traceability Visibility Automation Flexibility An important aspect is the multiple ways of accessing TFS 2012. It can be accessed using the brilliantly new Agile Web Access, using Visual Studio, or a multitude of other development IDEs, Microsoft Office products, or even through Java development platforms such as Eclipse. There is also a rich ecosystem of third-party tools available, which integrates into TFS 2012. Some examples include inteGREAT from eDevTech, TeamCompanion from Ekobit, and InRelease from InCycle Software. TFS 2012 has four major operational parts or stores, namely the work item system, the version control system, the build system, and the test system. In addition, it has a reporting data warehouse and a SharePoint project portal; the latter can be used for both document management and for accessing reports. It is a multi-role system, so that all the major roles in an organization can use TFS 2012 for their own purpose. TFS 2012's major advantage lies in its internal integration. All the stores are coupled together so that information is automatically linked together as the different operations take place. A typical case story for how different roles work together using TFS 2012 is as follows: A Stakeholder adds a requirement to the work item system using the Agile Web Access. An Architect sits down with the stakeholder and enters test cases as the acceptance criteria into the work item system using the test manager, and in doing so, connects the requirements with the test cases. The Product Owner moves the requirements into the upcoming sprint. The Scrum Master sits down with the team and breaks down the requirement in tasks using Excel. The Developer receives the task in Visual Studio and develops the source code. He/she checks in the source code to the version control system of TFS 2012, and in doing so connects the task work item with that source code. The TFS 2012 build system detects the check in and starts an automated build. The build is connected to both the source code and the task work item. The tester detects a new build in his test manager, and starts running a new test run based on the test cases entered earlier and the compiled code based on the new build. A Bug is detected, and the Test Runner collects information from the Test System with all its Test Results, connects this to the bug, and the bug to the test case and the build. The circle is now complete—all these artifacts are all stored in TFS 2012 and what is so great—the artifacts are all linked together! Anyone can now access this information from any point, and drill down into any other part of connected items. This story showed a scenario with many roles and many processes in place, but you can start much simpler. You can even start without nearly anything. For example just start with testing, run an exploratory test session with nothing else in place; just run the test and create bugs as you find them. From there you can, if you like, use the bugs to create test cases, and collect the test cases together to form user stories. These are just some examples of the flexibility you have. TFS 2012 can support nearly any process you have, and you can choose for yourself how much or how little you want to use. Then, as time goes on, you can add more features, without compromising anything you have done earlier. Installation TFS 2012 can be installed in multiple ways. We will cover the most common ones, which are as follows: Basic installation: This is an install on any local computer you have, and installs on Windows 7, Windows 8, or any of the server operating systems from Windows Server 2008 and upwards. It's a great way to try out TFS, but can be used in production for single developers or small teams, and is covered under the TFS Express license. The Basic installation gives you Source Control, Work Items, Build automation, and Test Management. There is no Data warehouse, reports, or SharePoint portals included. Standard/advanced installation: The solution for larger companies is the server installs or Advanced configurations. There are multiple ways to configure this, and we will cover a fundamental single server install with the build server separately. Team Foundation Service: This is the hosted version of TFS. You only need to sign up to the service. The offering is currently free, but Microsoft will charge for this service at some time in the future. Option A — Basic installation Basic installation is well suited when you want to try out the core functionality of TFS. You can install and configure TFS Basic on your local machine in less than ten minutes. Step 1 — Running the setup Start by running the installation (Setup.exe) from the TFS installation media. The installation itself just installs all the binaries; it doesn't require you to configure anything. It will install the .NET 4.5 Framework, which might require a machine restart as part of the installation. The interesting things happen afterwards when you run the Configuration Center. This wizard launches automatically and lets you choose what kind of installation of TFS 2012 you want. Step 2 — Configuration In the Configuration Center window, select the Basic option and press the Start Wizard button. On the Welcome page that follows, press Next. Step 3 — Setting up the database Now, you need to choose which database server you want to use. You have the following two options: Let TFS install SQL 2012 Express for you Point to an existing SQL Server instance, which must be SQL 2008 R2 or SQL Server 2012 If you already have an existing SQL Express instance on your machine, then the first option is disabled, as shown in the following screenshot: If you do point to an existing SQL instance, then you need to supply a name for SQL Server Instance. Note that the SQL server must be located on the same machine; the Basic installation does not support remote SQL servers. Also note that if the existing SQL instance Express is not a SQL 2012 Express instance, then it will be automatically upgraded to SQL 2012 Express. However, if your installation is not at least SQL 2008 SP2 Express, then you will be asked to upgrade to that level first. Step 4 — Reviewing The next page will run through a set of review steps, making sure that everything is configured correctly. If any errors are shown here, you need to correct them and then you can re-run the reviews. Press Next to start the configuration. Step 5 — Performing the configuration Now, the configuration wizard will perform the configuration to complete the installation of TFS 2012: That's it The installation is now complete. The information shown on the final screen includes the URL, which all users will use for connection, and some information about what changes were made by the configuration wizard on external resources, such as IIS and the Firewall: You are now ready to start using TFS 2012! Check out the Quick Start section for how to quickly get started on a new project. Option B — Standard server installation For the production server, a full TFS server install should be used. This includes, in addition to the core TFS components, SQL Reporting Services for the reports, SQL Analysis Services for the data warehouse, and Windows SharePoint Foundation for the team project portals. When using the Standard installation option, everything will be installed on the same machine. If you need to scale out your TFS installation from the beginning (for example, by using a separate server for the data tier) then you must choose the Advanced installation option. Note that you can always scale out your deployment later. The Standard installation and configuration has three main differences compared to the Basic installation: You need to supply a Windows account that is used as the service account for SharePoint Products and for the read-only account, for accessing the SQL Server Reporting Services reports. It does not need to have any special permission; a normal workgroup/domain user is enough: SQL Server will not be installed; there must be a SQL Server running on the machine before you configure the TFS installation. Note that the SQL Server must have been installed with both SQL Reporting Services and SQL Analysis Services. The Standard installation option will install Microsoft SharePoint Foundation 2010 as part of the installation process, unless already installed: Option C — Team Foundation Service Another great option, not only for quickly trying out TFS but also for use in production, is the hosted version of TFS. This is TFS running in Windows Azure. Team Foundation Service fully supports Source Control, Work Items, Test Management, and Build Automation. It does not (currently) include support for data warehouse, reports, and a SharePoint portal. Step 1 — What do I need All you need to sign up to TFS Service is a Live ID account. If you don't have one, sign up at https://signup.live.com . Step 2 — Signing up to the TFS service Go to http://tfs.visualstudio.com, and click on the get started for free link: Create your TFS account using your Live ID: There is currently only the Windows Live ID identity provider that works for TFS Service, so leave this screen as it is.   Summary This article will give you an overview of what Team Foundation Server 2012 is, and explain about its four main features: Traceability, Visibility, Automation, and Flexibility. Resources for Article : Further resources on this subject: Visual Studio 2008 Test Types [Article] Manual, Generic, and Ordered Tests using Visual Studio 2008 [Article] Visual Studio 2010 Test Types [Article]
Read more
  • 0
  • 0
  • 2464
article-image-creating-cartesian-based-graphs
Packt
17 Jan 2013
19 min read
Save for later

Creating Cartesian-based Graphs

Packt
17 Jan 2013
19 min read
(For more resources related to this topic, see here.) Introduction Our first graph/chart under the microscope is the most popular and simplest one to create. We can classify them all roughly under Cartesian-based graphs. Altogether this graph style is relatively simple; it opens the door to creating amazingly creative ways of exploring data. In this article we will lay down the foundations to building charts in general and hopefully motivate you to come up with your own ideas on how to create engaging data visualizations. Building a bar chart from scratch The simplest chart around is the one that holds only one dimensional data (only one value per type). There are many ways to showcase this type of data but the most popular, logical, and simple way is by creating a simple bar chart. The steps involved in creating this bar chart will be very similar even in very complex charts. The ideal usage of this type of chart is when the main goal is to showcase simple data, as follows: Getting ready Create a basic HTML file that contains a canvas and an onLoad event that will trigger the init function. Load the 03.01.bar.js script. We will create the content of the JavaScript file in our recipe as follows: <!DOCTYPE html> <html> <head> <title>Bar Chart</title> <meta charset="utf-8" /> <script src="03.01.bar.js"></script> </head> <body onLoad="init();" style="background:#fafafa"> <h1>How many cats do they have?</h1> <canvas id="bar" width="550" height="400"> </canvas> </body> </html> Creating a graph in general has three steps: defining the work area, defining the data sources, and then drawing in the data. How to do it... In our first case, we will compare a group of friends and how many cats they each own. We will be performing the following steps: Define your data set: var data = [{label:"David", value:3, style:"rgba(241, 178, 225, 0.5)"}, {label:"Ben", value:2, style:"#B1DDF3"}, {label:"Oren", value:9, style:"#FFDE89"}, {label:"Barbera", value:6, style:"#E3675C"}, {label:"Belann", value:10, style:"#C2D985"}]; For this example I've created an array that can contain an unlimited number of elements. Each element contains three values: a label, a value, and a style for its fill color. Define your graph outlines. Now that we have a data source, it's time to create our basic canvas information, which we create in each sample: var can = document.getElementById("bar"); var wid = can.width; var hei = can.height; var context = can.getContext("2d"); context.fillStyle = "#eeeeee"; context.strokeStyle = "#999999"; context.fillRect(0,0,wid,hei); The next step is to define our chart outlines: var CHART_PADDING = 20; context.font = "12pt Verdana, sans-serif"; context.fillStyle = "#999999"; context.moveTo(CHART_PADDING,CHART_PADDING); context.lineTo(CHART_PADDING,hei-CHART_PADDING); context.lineTo(wid-CHART_PADDING,hei-CHART_PADDING); var stepSize = (hei - CHART_PADDING*2)/10; for(var i=0; i<10; i++){ context.moveTo(CHART_PADDING, CHART_PADDING + i* stepSize); context.lineTo(CHART_PADDING*1.3,CHART_PADDING + i* stepSize); context.fillText(10-i, CHART_PADDING*1.5, CHART_PADDING + i* stepSize + 6); } context.stroke(); Our next and final step is to create the actual data bars: var elementWidth =(wid-CHART_PADDING*2)/ data.length; context.textAlign = "center"; for(i=0; i<data.length; i++){ context.fillStyle = data[i].style; context.fillRect(CHART_PADDING +elementWidth*i ,hei- CHART_PADDING - data[i].value*stepSize,elementWidth,data[i]. value*stepSize); context.fillStyle = "rgba(255, 255, 225, 0.8)"; context.fillText(data[i].label, CHART_PADDING +elementWidth*(i+.5), hei-CHART_PADDING*1.5); } That's it. Now, if you run the application in your browser, you will find a bar chart rendered. How it works... I've created a variable called CHART_PADDING that is used throughout the code to help me position elements (the variable is in uppercase because I want it to be a constant; so it's to remind myself that this is not a value that will change in the lifetime of the application). Let's delve deeper into the sample we created starting from our outline area: context.moveTo(CHART_PADDING,CHART_PADDING); context.lineTo(CHART_PADDING,hei-CHART_PADDING); context.lineTo(wid-CHART_PADDING,hei-CHART_PADDING); In these lines we are creating the L-shaped frame for our data; this is just to help and provide a visual aid. The next step is to define the number of steps that we will use to represent the numeric data visually. var stepSize = (hei - CHART_PADDING*2)/10; In our sample we are hardcoding all of the data. So in the step size we are finding the total height of our chart (the height of our canvas minus our padding at the top and bottom), which we then divide by the number of the steps that will be used in the following for loop: for(var i=0; i<10; i++){ context.moveTo(CHART_PADDING, CHART_PADDING + i* stepSize); context.lineTo(CHART_PADDING*1.3,CHART_PADDING + i* stepSize); context.fillText(10-i, CHART_PADDING*1.5, CHART_PADDING + i* stepSize + 6); } We loop through 10 times going through each step to draw a short line. We then add numeric information using the fillText method. Notice that we are sending in the value 10-i. This value works well for us as we want the top value to be 10. We are starting at the top value of the chart; we want the displayed value to be 10 and as the value of i increases, we want our value to get smaller as we move down the vertical line in each step of the loop. Next we want to define the width of each bar. In our case, we want the bars to touch each other and to do that we will take the total space available, and divide it by the number of data elements. var elementWidth =(wid-CHART_PADDING*2)/ data.length; At this stage we are ready to draw the bar but before we do that, we should calculate the width of the bars. We then loop through all the data we have and create the bars: context.fillStyle = data[i].style; context.fillRect(CHART_PADDING +elementWidth*i ,hei-CHART_PADDING - data[i].value*stepSize,elementWidth,data[i].value*stepSize); context.fillStyle = "rgba(255, 255, 225, 0.8)"; Notice that we are resetting the style twice each time the loop runs. If we didn't, we wouldn't get the colors we are hoping to get. We then place our text in the middle of the bar that was created. context.textAlign = "center"; There's more... In our example, we created a non-flexible bar chart, and if this is the way we create charts we will need to recreate them from scratch each time. Let's revisit our code and tweak it to make it more reusable. Revisiting the code Although everything is working exactly as we want it to work, if we played around with the values, it would stop working. For example, what if I only wanted to have five steps; if we go back to our code, we will locate the following lines: var stepSize = (hei - CHART_PADDING*2)/10; for(var i=0; i<10; i++){ We can tweak it to handle five steps: var stepSize = (hei - CHART_PADDING*2)5; for(var i=0; i<5; i++){ We would very quickly find out that our application is not working as expected. To solve this problem let's create a new function that will deal with creating the outlines of the chart. Before we do that, let's extract the data object and create a new object that will contain the steps. Let's move the data and format it in an accessible format: var data = [...];var chartYData = [{label:"10 cats", value:1}, {label:"5 cats", value:.5}, {label:"3 cats", value:.3}];var range = {min:0, max:10};var CHART_PADDING = 20;var wid;var hei;function init(){ Take a deep look into chartYData object as it enables us to put in as many steps as we want without a defined spacing rule and the range object that will store the minimum and maximum values of the overall graph. Before creating the new functions, let's add them into our init function (changes marked in bold). function init(){var can = document.getElementById("bar");wid = can.width;hei = can.height;var context = can.getContext("2d");context.fillStyle = "#eeeeee";context.strokeStyle = "#999999";context.fillRect(0,0,wid,hei);context.font = "12pt Verdana, sans-serif";context.fillStyle = "#999999";context.moveTo(CHART_PADDING,CHART_PADDING);context.lineTo(CHART_PADDING,hei-CHART_PADDING);context.lineTo(wid-CHART_PADDING,hei-CHART_PADDING);fillChart(context,chartYData);createBars(context,data);} All we did in this code is to extract the creation of the chart and its bars into two separate functions. Now that we have an external data source both for the chart data and the content, we can build up their logic. Using the fillChart function The fillChart function's main goal is to create the foundation of the chart. We are integrating our new stepData object information and building up the chart based on its information. function fillChart(context, stepsData){ var steps = stepsData.length; var startY = CHART_PADDING; var endY = hei-CHART_PADDING; var chartHeight = endY-startY; var currentY; var rangeLength = range.max-range.min; for(var i=0; i<steps; i++){ currentY = startY + (1-(stepsData[i].value/rangeLength)) * chartHeight; context.moveTo(CHART_PADDING, currentY ); context.lineTo(CHART_PADDING*1.3,currentY); context.fillText(stepsData[i].label, CHART_PADDING*1.5, currentY+6); } context.stroke(); } Our changes were not many, but with them we turned our function to be much more dynamic than it was before. This time around we are basing the positions on the stepsData objects and the range length that is based on that. Using the createBars function Our next step is to revisit the createBars area and update the information so it can be created dynamically using external objects. function createBars(context,data){ var elementWidth =(wid-CHART_PADDING*2)/ data.length; var startY = CHART_PADDING; var endY = hei-CHART_PADDING; var chartHeight = endY-startY; var rangeLength = range.max-range.min; var stepSize = chartHeight/rangeLength; context.textAlign = "center"; for(i=0; i<data.length; i++){ context.fillStyle = data[i].style; context.fillRect(CHART_PADDING +elementWidth*i ,hei- CHART_PADDING - data[i].value*stepSize,elementWidth,data[i].value*stepSize); context.fillStyle = "rgba(255, 255, 225, 0.8)"; context.fillText(data[i].label, CHART_PADDING +elementWidth*(i+.5), hei-CHART_PADDING*1.5); } } Almost nothing changed here apart from a few changes in the way we positioned the data and extracted hardcoded values. Spreading data in a scatter chart The scatter chart is a very powerful chart and is mainly used to get a bird's-eye view while comparing two data sets. For example, comparing the scores in an English class and the scores in a Math class to find a correlative relationship. This style of visual comparison can help find surprising relationships between unexpected data sets. This is ideal when the goal is to show a lot of details in a very visual way. Getting ready If you haven't had a chance yet to scan through the logic of our first section in this article, I recommend you take a peek at it as we are going to base a lot of our work on that while expanding and making it a bit more complex to accommodate two data sets. I've revisited our data source from the previous section and modified it to store three variables of students' exam scores in Math, English, and Art. var data = [{label:"David",math:50,english:80,art:92,style:"rgba(241, 178, 225, 0.5)"},{label:"Ben",math:80,english:60,art:43,style:"#B1DDF3"},{label:"Oren",math:70,english:20,art:92,style:"#FFDE89"},{label:"Barbera",math:90,english:55,art:81,style:"#E3675C"},{label:"Belann",math:50,english:50,art:50,style:"#C2D985"}]; Notice that this data is totally random so we can't learn anything from the data itself; but we can learn a lot about how to get our chart ready for real data. We removed the value attribute and instead replaced it with math, english, and art attributes. How to do it... Let's dive right into the JavaScript file and the changes we want to make: Define the y space and x space. To do that, we will create a helper object that will store the required information: var chartInfo= { y:{min:40, max:100, steps:5,label:"math"}, x:{min:40, max:100, steps:4,label:"english"} }; It's time for us to set up our other global variables and start up our init function: var CHART_PADDING = 30;var wid;var hei;function init(){var can = document.getElementById("bar");wid = can.width;hei = can.height;var context = can.getContext("2d");context.fillStyle = "#eeeeee";context.strokeStyle = "#999999";context.fillRect(0,0,wid,hei);context.font = "10pt Verdana, sans-serif";context.fillStyle = "#999999";context.moveTo(CHART_PADDING,CHART_PADDING);context.lineTo(CHART_PADDING,hei-CHART_PADDING);context.lineTo(wid-CHART_PADDING,hei-CHART_PADDING);fillChart(context,chartInfo);createDots(context,data);} Not much is new here. The major changes are highlighted. Let's get on and start creating our fillChart and createDots functions. If you worked on our previous section, you might notice that there are a lot of similarities between the functions in the previous section and this function. I've deliberately changed the way we create things just to make them more interesting. We are now dealing with two data points as well, so many details have changed. Let's review them: function fillChart(context, chartInfo){ var yData = chartInfo.y; var steps = yData.steps; var startY = CHART_PADDING; var endY = hei-CHART_PADDING; var chartHeight = endY-startY; var currentY; var rangeLength = yData.max-yData.min; var stepSize = rangeLength/steps; context.textAlign = "left"; for(var i=0; i<steps; i++){ currentY = startY + (i/steps) * chartHeight; context.moveTo(wid-CHART_PADDING, currentY ); context.lineTo(CHART_PADDING,currentY); context.fillText(yData.min+stepSize*(steps-i), 0, currentY+4); } currentY = startY + chartHeight; context.moveTo(CHART_PADDING, currentY ); context.lineTo(CHART_PADDING/2,currentY); context.fillText(yData.min, 0, currentY-3); var xData = chartInfo.x; steps = xData.steps; var startX = CHART_PADDING; var endX = wid-CHART_PADDING; var chartWidth = endX-startX; var currentX; rangeLength = xData.max-xData.min; stepSize = rangeLength/steps; context.textAlign = "left"; for(var i=0; i<steps; i++){ currentX = startX + (i/steps) * chartWidth; context.moveTo(currentX, startY ); context.lineTo(currentX,endY); context.fillText(xData.min+stepSize*(i), currentX-6, endY+CHART_PADDING/2); } currentX = startX + chartWidth; context.moveTo(currentX, startY ); context.lineTo(currentX,endY); context.fillText(xData.max, currentX-3, endY+CHART_PADDING/2); context.stroke(); } When you review this code you will notice that our logic is almost duplicated twice. While in the first loop and first batch of variables we are figuring out the positions of each element in the y space, we move on in the second half of this function to calculate the layout for the x area. The y axis in canvas grows from top to bottom (top lower, bottom higher) and as such we need to calculate the height of the full graph and then subtract the value to find positions. Our last function is to render the data points and to do that we create the createDots function: function createDots(context,data){ var yDataLabel = chartInfo.y.label; var xDataLabel = chartInfo.x.label; var yDataRange = chartInfo.y.max-chartInfo.y.min; var xDataRange = chartInfo.x.max-chartInfo.x.min; var chartHeight = hei- CHART_PADDING*2; var chartWidth = wid- CHART_PADDING*2; var yPos; var xPos; for(var i=0; i<data.length;i++){ xPos = CHART_PADDING + (data[i][xDataLabel]-chartInfo.x.min)/ xDataRange * chartWidth; yPos = (hei - CHART_PADDING) -(data[i][yDataLabel]- chartInfo.y.min)/yDataRange * chartHeight; context.fillStyle = data[i].style; context.fillRect(xPos-4 ,yPos-4,8,8); } } Here we are figuring out the same details for each point—both the y position and the x position—and then we draw a rectangle. Let's test our application now! How it works... We start by creating a new chartInfo object: var chartInfo= { y:{min:40, max:100, steps:5,label:"math"}, x:{min:40, max:100, steps:4,label:"english"} }; This very simple object encapsulates the rules that will define what our chart will actually output. Looking closely you will see that we set an object named chartInfo that has information on the y and x axes. We have a minimum value ( min property), maximum value ( max property), and the number of steps we want to have in our chart ( steps property), and we define a label. Let's look deeper into the way the fillChart function works. In essence we have two numeric values; one is the actual space on the screen and the other is the value the space represents. To match these values we need to know what our data range is and also what our view range is, so we first start by finding our startY point and our endY point followed by calculating the number of pixels between these two points: var startY = CHART_PADDING; var endY = hei-CHART_PADDING; var chartHeight = endY-startY; These values will be used when we try to figure out where to place the data from the chartInfo object. As we are already speaking about that object, let's look at what we do with it: var yData = chartInfo.y; var steps = yData.steps; var rangeLength = yData.max-yData.min; var stepSize = rangeLength/steps; As our focus right now is on the height, we are looking deeper into the y property and for the sake of comfort we will call it yData. Now that we are focused on this object, it's time to figure out what is the actual data range (rangeLength) of this value, which will be our converter number. In other words we want to take a visual space between the points startY and endY and based on the the range, position it in this space. When we do so we can convert any data into a range between 0-1 and then position them in a dynamic visible area. Last but not least, as our new data object contains the number of steps we want to add into the chart, we use that data to define the step value. In this example it would be 12. The way we get to this value is by taking our rangeLength (100 - 40 = 60) value and then dividing it by the number of steps (in our case 5). Now that we have got the critical variables out of the way, it's time to loop through the data and draw our chart: var currentY; context.textAlign = "left"; for(var i=0; i<steps; i++){ currentY = startY + (i/steps) * chartHeight; context.moveTo(wid-CHART_PADDING, currentY ); context.lineTo(CHART_PADDING,currentY); context.fillText(yData.min+stepSize*(steps-i), 0, currentY+4); } This is where the magic comes to life. We run through the number of steps and then calculate the new Y position again. If we break it down we will see: currentY = startY + (i/steps) * chartHeight; We start from the start position of our chart (upper area) and then we add to it the steps by taking the current i position and dividing it by the total possible steps (0/5, 1/5, 2/5 and so on). In our demo it's 5, but it can be any value and should be inserted into the chartInfo steps attribute. We multiply the returned value by the height of our chart calculated earlier. To compensate for the fact that we started from the top we need to reverse the actual text we put into the text field: yData.min+stepSize*(steps-i) This code takes our earlier variables and puts them to work. We start by taking the minimal value possible and then add into it stepSize times the total number of steps subtracted by the number of the current step. Let's dig into the createDots function and see how it works. We start with our setup variables: var yDataLabel = chartInfo.y.label; var xDataLabel = chartInfo.x.label; This is one of my favorite parts of this section. We are grabbing the label from our chartInfo object and using that as our ID; this ID will be used to grab information from our data object. If you wish to change the values, all you need to do is switch the labels in the chartInfo object. Again it's time for us to figure out our ranges as we've done earlier in the fillChart function. This time around we want to get the actual ranges for both the x and y axes and the actual width and height of the area we have to work with: var yDataRange = chartInfo.y.max-chartInfo.y.min; var xDataRange = chartInfo.x.max-chartInfo.x.min; var chartHeight = hei- CHART_PADDING*2; var chartWidth = wid- CHART_PADDING*2; We also need to get a few variables to help us keep track of our current x and y positions within loops: var yPos; var xPos; Let's go deeper into our loop, mainly into the highlighted code snippets: for(var i=0; i<data.length;i++){xPos = CHART_PADDING + (data[i][xDataLabel]-chartInfo.x.min)/xDataRange * chartWidth;yPos = (hei - CHART_PADDING) -(data[i][yDataLabel]-chartInfo.y.min)/yDataRange * chartHeight;context.fillStyle = data[i].style;context.fillRect(xPos-4 ,yPos-4,8,8);} The heart of everything here is discovering where our elements need to be. The logic is almost identical for both the xPos and yPos variables with a few variations. The first thing we need to do to calculate the xPos variable is: (data[i][xDataLabel]-chartInfo.x.min) In this part we are using the label, xDataLabel, we created earlier to get the current student score in that subject. We then subtract from it the lowest possible score. As our chart doesn't start from 0, we don't want the values between 0 and our minimum value to affect the position on the screen. For example, let's say we are focused on math and our student has a score of 80; we subtract 40 out of that (80 - 40 = 40) and then apply the following formula: (data[i][xDataLabel] - chartInfo.x.min) / xDataRange We divide that value by our data range; in our case that would be (100 - 40)/60. The returned result will always be between 0 and 1. We can use the returned number and multiply it by the actual space in pixels to know exactly where to position our element on the screen. We do so by multiplying the value we got, that is between 0 and 1, by the total available space (in this case, width). Once we know where it needs to be located we add the starting point on our chart (the padding): xPos = CHART_PADDING + (data[i][xDataLabel]-chartInfo.x.min)/xDataRange * chartWidth; The yPos variable has the same logic as that of the xPos variable, but here we focus only on the height.
Read more
  • 0
  • 0
  • 2612

article-image-application-packaging-vmware-thinapp-47-essentials
Packt
15 Jan 2013
19 min read
Save for later

Application Packaging in VMware ThinApp 4.7 Essentials

Packt
15 Jan 2013
19 min read
(For more resources related to this topic, see here.) The capture and build environment You cannot write a book about a packaging format without discussing the environment used to create the packages. The environment you use to capture an installation is of great importance. ThinApp uses a method of snapshotting when capturing an application installation. This means you create a snapshot (Pre-Installation Snapshot) of the current state of the machine. After modifying the environment, you create another snapshot, the Post-Installation Snapshot. The differences between the two snapshots represent the changes made by the installer. This should be all the information you need in order to run the application. Many packaging products use snapshotting as a method of capturing changes. The alternative would be to try to hook into the installer itself. Both methods have their pros and cons. Using snapshot is much more flexible. You don't even have to run an installer. You can copy files and create registry keys manually and it will all be captured. But, your starting point will decide the outcome. If your machine already contains the Java Runtime Environment ( JRE ) and the application you are capturing requires Java, then you will not be able to capture the JRE. Since it was already there when you ran the pre-install snapshot, it will not be a part of the captured differences. This means your package would require Java installed or it will fail to run. The package will not be self-contained. The other method, monitoring the installer, will be more independent of the capturing environment but will not support all the installer formats and will not support manual tweaking during capture. Nothing is black or white. Snapshotting can be made a little more independent of the capture environment. When an installer discovers components already installed, it can register itself to the same components. ThinApp will recognize this, investigate which files are related to a component, and mark them as needed to be included in the package. But this is not a bulletproof method. So the general rule is to make sure your environment allows ThinApp to capture all required dependencies of the application. ThinApp packages are able to support multiple operating systems with one single package. This is a great feature and really helps in lowering the overall administration of maintaining an application. The possibility of running the same package on your Windows XP clients, Windows 7 machines, and your XenApp servers is unique. Most other packaging formats require you to maintain one package per environment. The easiest method to package an application is to capture it on the platform where it will run. Normally you can achieve an out of the box success rate of 60 — 80 percent. This means you have not tweaked the project in any way. The package might not be ready for production but it will run on a clean machine not having the application locally installed. If you want to support multiple operating systems you should choose the lowest platform you need to support. Most of the time this would be Windows XP. From ThinApp's point of view, Windows XP and Windows Server 2003 are of the same generation and Windows 7 and Windows 2008 R2 are of the same generation. Most installers are environment aware. They will investigate the targeting platform and if it discovers a Windows 7 operating system, it knows that some files are already present in the same or newer version than required. Installing on a Windows XP with no service pack would force those required files to be installed locally, and therefore captured by the capturing process. Having these files captured from and installation made on Windows XP rarely conflicts the running of the application on Windows 7 and helps you achieve multiple OS support. Creating a package for one single operating system is of course the easiest task. Creating a package supporting multiple operating systems, all being 32-bit systems is a little harder. How hard depends on the application. Creating a package supporting many different OS and both 32-bit and 64-bit versions is the hardest. But it is doable. It just requires a little extra packaging effort. Some applications cannot run on a 64-bit OS, but most applications offer some kind of work around. If the application contains 16-bit code, then it's impossible to make it run on a 64-bit environment. 64-bit environments cannot handle 16-bit code. Your only workaround for those scenarios is whole machine virtualization technologies. VMware Workstation, VMware View, Citrix XenDesktop, Microsoft Med-V, and many others offer you the capability to access a virtualized 32-bit operating system on your 64-bit machine. In general, you should use an environment that is as clean as possible. This will guarantee that all your packages include as many dependencies as possible, making them portable and robust. But it's not written in stone. If you are capturing an add-on to Microsoft Office, then Microsoft Office has to be locally installed in your capturing environment or the add-on installer would fail to run. You must design your capture environment to allow flexibility. Sometimes you capture on Windows XP, the next application might be captured on Windows 7 64-bit. The next day you'll capture on a machine having JRE installed, or Microsoft Office. The use of virtual machines is a must. Physical machines are supported but the hours spent on reverting to a clean state to start the capture of the next application makes it virtually useless. My capture environment is my MacBook Pro running VMware Fusion and several virtual machines such as Windows XP, Windows Vista, Windows 7, Windows 2003 Server, and of course Windows Server 2008. All VMs have several snapshots (states of the virtual machine) so I can easily jump back and forth between clean, Microsoft Office-installed and different service packs and browsers. Yes, it will require some serious disk space. I'm always low on free disk space. No matter how big the disks you buy are, your project folders and virtual machines will eat it all. I have two disks in my MacBook. One SSD disk, where I keep most of my virtual machines, and one traditional hard disk where I keep all my project folders. The best capture environments I've ever seen have been hosted on VMware vSphere and ESX. Using server hardware to run client operating systems make them fast as lightning. Snapshotting of your VMs take seconds, as well as reverting snapshots. Access to the virtual machines hosted on VMware ESX can be achieved using a console within the vSphere client or basic RDP. The only downside I can see to using an ESX environment is that you cannot do packaging offline, while traveling. The next logical question is if my capture machine should be a domain member or standalone, this depends, I always prefer to capture on standalone machines. This way I know that group policies will not mess with my capture process. No restrictions will be blocking me from doing what I need to do. But again, sometimes you can simply not capture an application without having access to a backend infrastructure. Then your capture machine must be on the corporate network and most of the time it means that it has to be a domain member. If possible, try putting the machine in a special Organizational Unit ( OU) where you limit the amount of restrictions. If at all possible, make sure you don't have antivirus installed on your capturing environment. I know that some enterprises have strict policies forcing even packaging machines to be protected by antivirus. But be careful. There is no way of telling what your antivirus may decide to do to your application's installation and the whole capture process. Most installer manuals clearly state to disable any antivirus during installation. They do that for a reason. Antivirus scanning logs and all that follows will also pollute your project folder. It will probably not break your package but I am a strong believer in delivering clean and optimized packages. So having an antivirus means you will have to spend some time cleaning up your project folders. Alternatively, you can include areas where the antivirus changes content in snapshot.ini, the Setup Capture exclusion list. Entry points and the data container An entry point is the doorway into the virtual environment for the end users. An entry point specifies what will be launched within the virtual environment. Mostly an entry point points to an executable, for example, winword.exe. But an entry point doesn't have to point to an executable. You can point an entry point to whatever kind of file you want, as long as the file type has a file association made to it. Whatever is associated to the file type will be launched within the virtual environment. If no file type association exists, you will get the standard operating system dialog box, asking you which application to open the file with. The name of the entry point must use an .exe extension. When the user double-clicks on an entry point, we are asking the operating system to execute the ThinApp runtime. Entry points are managed in Package.ini. You'll find them at the end of the Package.ini file. The data container is the file where ThinApp stores the whole virtual environment and the ThinApp runtime. There can only be one data container per project. The content in the data container is an exact copy of the representation of the virtual environment found in your project folder. The data in the data container is in read-only format. It's the packagers who change the content by rebuilding the project. An end user cannot change the content of the data container. An entry point can be a data container. Setup Capture will recommend not using an entry point as a data container if Setup Capture believes that the package will be large (200 MB-300 MB). The reason for this is that the icon of the entry point may take up to 20 minutes to be displayed. This is a feature of the operating system and there's nothing you can do about it. It's therefore better to store the data container in a separate file and keep your entry points small. Make sure the icons are displayed quickly. Setup Capture will force you to use a separate data container when the size is calculated to be larger than 1.5 GB. Windows has a size limitation for xecutable files. Windows will deny executing a .exe file larger than 2 GB. The name of the data container can be anything. You will not have to name it with the .dat extension. It doesn't have to have a file extension at all. If you're using a separate data container, you must keep the data container in the same folder as your entry points. Let's take a closer look at the data container and entry point section of Package.ini. You'll find the data container and entry points at the end of the Package.ini file. The following is an example Package.ini file from a virtualized Mozilla Firefox: [Mozilla Firefox.exe] Source=%ProgramFilesDir%Mozilla Firefoxfirefox.exe ;ChangeReadOnlyData to binPackage.ro.tvr to build with old versions(4.6.0 or earlier) of tools ReadOnlyData=Package.ro.tvr WorkingDirectory=%ProgramFilesDir%Mozilla Firefox FileTypes=.htm.html.shtml.xht.xhtml Protocols=FirefoxURL;ftp;http;https Shortcuts=%Desktop%;%Programs%Mozilla Firefox;%AppData%Microsoft Internet ExplorerQuick Launch [Mozilla Firefox (Safe Mode).exe] Disabled=1 Source=%ProgramFilesDir%Mozilla Firefoxfirefox.exe Shortcut=Mozilla Firefox.exe WorkingDirectory=%ProgramFilesDir%Mozilla Firefox CommandLine="%ProgramFilesDir%Mozilla Firefoxfirefox.exe"-safe-mode Shortcuts=%Programs%Mozilla Firefox A step-by-step explanation for the parameters is given as follows: [Mozilla Firefox.exe]   Within [] is the name of the entry point. This is the name the end user will see. Make sure to use .exe as your file extension. Source=%ProgramFilesDir%Mozilla Firefoxfirefox.exe The source parameter points to the target of the entry point, that is, what will be launched when the user clicks on the entry point. The source can either be a virtualized or physical file. The target will be launched within the virtual environment no matter where it lives. ReadOnlyData=Package.ro.tvr The ReadOnlyData indicates this entry point is in fact a data container as well. WorkingDirectory=%ProgramFilesDir%Mozilla Firefox This specifies the working directory for the executable launched. This is often a very important parameter. If you do not specify a working directory, the active working directory will be the location of your package. A lot of software depends on having their working directory set to the application's own folder in the program files directory. FileTypes=.htm.html.shtml.xht.xhtml This is used when registering the entry point. It specifies which file extensions should be associated with this entry point. The previous example registers .htm, .html, and so on to the virtualized Mozilla Firefox. Protocols=FirefoxURL;ftp;http;https This is used when registering the entry point. It specifies which protocols should be associated with this entry point. The previous example registers http, https, and so on to the virtualized Mozilla Firefox. Shortcuts=%Desktop%;%Programs%Mozilla Firefox The parameter Shortcuts is also used when registering your entry points. The Shortcuts parameter decides where shortcuts will be created. The previous example creates shortcuts to virtualized Mozilla Firefox on the Start menu in a folder called Mozilla Firefox, as well as a shortcut on the user's desktop. [Mozilla Firefox (Safe Mode).exe] Disabled=1 Disabled means this entry point will not be created during the build of your project. Source=%ProgramFilesDir%Mozilla Firefoxfirefox.exe Shortcut=Mozilla Firefox.exe Shortcut tells this ent;ry point what its data container is named. If you change the data container's name you will have to change the Shortcut parameter on all entry points using the data container. WorkingDirectory=%ProgramFilesDir%Mozilla Firefox CommandLine="%ProgramFilesDir%Mozilla Firefoxfirefox.exe"-safe-mode CommandLine will allow you to specify hardcoded parameters to the executable. It's the native parameters supported by the virtualized application that you use. Shortcuts=%Programs%Mozilla Firefox There are many more parameters related to entry points. The following are some more examples with descriptions: [Microsoft Office Enterprise 2007.dat] Source=%ProgramFilesDir%Microsoft OfficeOffice12OSA.EXE ;ChangeReadOnlyData to binPackage.ro.tvr to build with old versions(4.6.0 or earlier) of tools ReadOnlyData=Package.ro.tvr MetaDataContainerOnly=1 MetaDataContainer indicates that this is a separate data container. [Microsoft Office Excel 2007.exe] Source=%ProgramFilesDir%Microsoft OfficeOffice12EXCEL.EXE Shortcut=Microsoft Office Enterprise 2007.dat FileTypes=.csv.dqy.iqy.slk.xla.xlam.xlk.xll.xlm.xls.xlsb.xlshtml.xlsm. xlsx.xlt.xlthtml.xltm.xltx.xlw Comment=Perform calculations, analyze information, and visualize data in spreadsheets by using Microsoft Office Excel. Comment allows you to specify text to be displayed when hovering your mouse over the shortcut to the entry point. ObjectTypes=Excel.Addin;Excel.AddInMacroEnabled;Excel. Application;Excel.Application.12;Excel.Backup;Excel.Chart;Excel. Chart.8;Excel.CSV;Excel.Macrosheet;Excel.Sheet;Excel.Sheet.12;Excel. Sheet.8;Excel.SheetBinaryMacroEnabled;Excel.SheetBinaryMacroEnab led.12;Excel.SheetMacroEnabled;Excel.SheetMacroEnabled.12;Excel. SLK;Excel.Template;Excel.Template.8;Excel.TemplateMacroEnabled;Excel. Workspace;Excel.XLL This specifies the object types which will be registered to the entry point when registered. Shortcuts=%Programs%Microsoft Office StatusBarDisplayName=WordProcessor Users can change the name displayed in the ThinApp splash screen. In this example, WordProcessor will be displayed as the title. Icon=%ProgramFilesDir%Microsoft OfficeOffice12EXCEL.ico Icon allows you to specify an icon for your entry point. Most of the times ThinApp will display the correct icon without this parameter. You can point to an executable to use its built-in icons as well. You can specify a different icon set by applying 1 or 2 and so on to the icon path, for example, Icon=%ProgramFilesDir%Microsoft OfficeOffice12EXCEL.EXE,1 The most common entry points should be either cmd.exe or regedit.exe. You'll find them in all Package.ini files but they are disabled by default. Since cmd.exe and regedit.exe most likely weren't modified during Setup Capture, they are not part of the virtual environment. So the source will be the native cmd.exe and regedit.exe. These two entry points are the packagers' best friends. Using these entry points allows a packager to investigate the environment known to the virtualized application. What you can see using cmd.exe or regedit.exe is what the application sees. This is a great help when troubleshooting. If you package an add-on to a natively installed application, the typical example is packaging JRE and you want the local Internet Explorer to be able to use it. Creating an entry point within your Java package using native Internet Explorer as a source, is a perfect method of dealing with it. Now you can offer a separate shortcut to the user, allowing users to choose when to use native Java or when to use virtualized Java. ThinApp's isolation will allow virtualization of one Java version running on a machine with another version natively installed. The only problem with this approach is how you educate your users when to use which shortcut. ThinDirect, discussed later in this article, in the Virtualizing Internet Explorer 6 section, will allow you to automatically point the user to the right browser. There are many use cases for launching something natively within a virtualized environment. You may face troublesome Excel add-ons. Virtualizing them will protect against conflicts, but you must launch native Excel within the environment of the add-on for it to work. Here you could use the fact that many Excel add-ons use .xla files as the typical entry point to the add-on. Create your entry point using the .xla file as source and you will be able to launch any Excel version that is natively installed. When you use a non executable as your entry point source, remember that the name of your entry point must still be .exe. The following is an example of an entry point using a text file as source: [ReadMe.exe] Source=%Drive_C%Tempreadme.txt ReadOnlyData=Package.ro.tvr Running ReadMe.exe will launch whatever is associated to handle .txt files. The application will run within the virtualized environment of the package.   The project folder The project folder is where the packager spends most of his or her time. The capturing process is just a means to create the project folder. You could just as easily create your own project folder from scratch. I admit, to manually create a project folder representing a Microsoft Office installation would be far from easy but in theory it can be done. There is some default content in all project folders. Let's capture nothing and investigate what these are. During Setup Capture, to speed things up, disable the majority of the search locations. This way pre and post scans will take close to no time at all. Run Setup Capture. In the Ready to Prescan step, click on Advanced Scan Locations.... Exclude all but one location from the scanning, as shown in the following screenshot: Since we want to capture nothing, there is no point in scanning all locations. Normally you don't have to modify the advanced scan locations. After pressing Prescan, wait for Postscan to become available and click on it when possible, without modifying anything in your capturing environment. Accept CMD.EXE as your entry point and accept all defaults throughout the wizard. Your project folder will look like the following screenshot: The project folder of a capturing, bearing no changes, will still create folder macros and default isolation modes. Let's explore the defaults prepopulated by the Setup Capture wizard. This is the minimum project folder content that the Setup Capture will ever generate. As a packager you are expected to clean up unnecessary folders from the project folder, so your final project folder may very well contain a smaller number of folder macros. Folder macros are ThinApp's variables. %ProgramFilesDir% will be translated to C:Program Files on an English Windows installation but the same package running on a Swedish OS the %ProgramFilesDir% will point to C:Program. Folder macros are the key to ThinApp packages' portability. If we explore the filesystem part of the project folder, we'll see the default isolation modes prepopulated by Setup Capture. These are applied as defaults no matter what default filesystem isolation mode you choose during the Setup Capture wizard. This confuses some people. I'm often told that a certain package is using WriteCopy or Merged as the isolation mode. Well that's just the default used when no other isolation mode is specified. A proper project folder should have isolation modes specified on all locations of importance, basically making the default isolation mode of no importance. The prepopulated isolation modes are there to make sure most applications run out of the box ThinApped. You are expected to change these to suit your application and environment. Let's look at some examples of default isolation modes. %AppData%, the location where most applications store user settings, is by default using WriteCopy. This is to make sure that you sandbox all user settings. %SystemRoot% and %SystemSystem% have WriteCopy as their default isolation modes, allowing a virtualized application to see the operating system files without allowing it to modify C:Windows and C:WindowsSystem32. %SystemSystem%spool representing C:WindowsSystem32Spool has Merged as its default. This way print jobs will be spooled to the native location, allowing the printer to pick up the print job. %Desktop% (user's desktop folder) and %Personal% (user's document folder) have Merged by default. When ThinApp generates the project folder, it uses the following logic to decide which isolation mode to prepopulate other locations with. The same logic is used within the registry as well. Modified locations will get WriteCopy as the isolation mode New locations will get Full as their isolation mode
Read more
  • 0
  • 0
  • 5000

article-image-creating-bar-charts
Packt
14 Jan 2013
10 min read
Save for later

Creating Bar Charts

Packt
14 Jan 2013
10 min read
(For more resources related to this topic, see here.) Drawing a bar chart with Flex The Flex framework offers some charting components that are fairly easy to use. It is not ActionScript per say, but it still compiles to the SWF format. Because the resulting charts look good and are pretty customizable, we decided to cover it in one recipe. There is a downside though to using this: the Flex framework will be included in your SWF, which will increase its size. Future recipes will explain how to do the same thing using just ActionScript. Getting ready Open FlashDevelop and create a new Flex Project. How to do it... The following are the steps required to build a bar chart using the Flex framework. Copy and paste the following code in the Main.mxml file. When you run it, it will show you a bar chart. <?xml version="1.0" encoding="utf-8"?> <s:Application minWidth="955" minHeight="600"> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] private var monthsAmount:ArrayCollection = new ArrayCollection( [ { Month: "January", Amount: 35}, { Month: "February", Amount: 32 }, { Month: "March", Amount: 27 } ]); ]]> </fx:Script> <mx:BarChart id="barchart" x="30" y="30" dataProvider="{monthsAmount}"> <mx:verticalAxis> <mx:CategoryAxis categoryField="Month"/> </mx:verticalAxis> <mx:horizontalAxis> <mx:LinearAxis minimum="10"/> </mx:horizontalAxis> <mx:series> <mx:BarSeries yField="Month" xField="Amount" /> </mx:series> </mx:BarChart> </s:Application> How it works... When you create a new Flex project, Flash Builder will generate for you the XML file and the Application tag. After that, in the script tag we created the data we will need to show in the chart. We do so by creating an ArrayCollection data structure, which is an array encapsulated to be used as DataProvider for multiple components of the Flex framework, in this case mx:BarChart. Once we have the data part done, we can start creating the chart. Everything is done in the BarChart tag. Inside that tag you can see we linked it with ArrayCollection, which we previously created using this code: dataProvider = "{monthsAmount}". Inside the BarChart tag we added the verticalAxis tag. This tag is used to associate values in the ArrayCollection to an axis. In this case we say that the values of the month will be displayed on the vertical axis. Next comes the horizontalAxis tag, we added it to tell the chart to use 10 as a minimum value for the horizontal axis. It's optional, but if you were to remove the tag it would use the smallest value in ArrayCollection as the minimum for the axis, so one month, in this case, March, would have no bar and the bar chart wouldn't look as good. Finally, the series tag will tell for a column, what data to use in ArrayCollection. You can basically think of the series as representing the bars in the chart. There's more... As we mentioned earlier, this component of the Flex framework is pretty customizable and you can use it to display multiple kinds of bar charts. Showing data tips Multiple options are available using this component; if you want to display the numbers that the bar represents in the chart while the user moves the mouse over the bar, simply add showDataTips = "true" inside the BarChart tag and it is done. Displaying vertical bars If you would like to use vertical bars instead of horizontal bars in the graph, Flex provides the ColumnChart charts to do so. In the previous code, change the BarChart tag to ColumnChart, and change BarSeries to ColumnSeries. Also, since the vertical axis and horizontal axis will be inverted, you will need verticalAxis by horizontalAxis and horizontalAxis by verticalAxis (switch them, but keep their internal tags) and in the ColumnSeries tag, xField should be Month and yField should be Amount. When you run that code it will show vertical bars. Adding more bars By adding more data in the ArrayCollection data structure and by adding another BarSeries tag, you can display multiple bars for each month. See the Adobe documentation at the following link to learn how to do it: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/charts/BarChart.html. Building vertical bar charts Now that we have built a bar chart using Flex, we are ready to do the same in pure ActionScript. This bar chart version will allow you to expand it in multiple ways and will remove the weight that the Flex framework adds to the file size. Now a bit about bar charts; Bar charts are good when you don't have too much data (more than 20 bars starts to make a big chart), or when you've averaged it. It is a quick way to compare data visually. Getting ready All we will need for this is to start a new project in FlashDevelop. Also, it would help to read about preparing data and about axes in the book ActionScript Graphing Cookbook. How to do it... This section will refer a lot to the code provided with the book. You will notice that we divided all the elements in the charts into their own classes. It all starts in the Main.as file, where we create the data that we will use to display in the chart after that we just create the chart and add it to the display list. var data:Vector.<BarData> = new Vector.<BarData>(); data.push(new BarData("January", 60)); data.push(new BarData("February", 100)); data.push(new BarData("March", 30)); var chart:BarChart = new BarChart(data, 400, 410); chart.x = 30; chart.y = 30; addChild(chart); From here you can look into the BarData class, which it is just two variables, a string and a number that represents the data that we are going to show. We now need to create a class for all the elements that comprise a bar chart. They are: the bars, the vertical axis, and the horizontal axis. Now this recipe is building a vertical bar chart so the vertical axis is the one that will have numerical marks and the horizontal axis will have labels on the marks. First the Bar class: This class will only draw a rectangle with the height representing the data for a certain label. The following is its constructor: public function Bar(width:int, height:int) { graphics.beginFill(0xfca25a); graphics.drawRect(-width/2, 0, width, -height); graphics.endFill(); } The horizontal axis will take the x coordinate of the created bars and will place a label under it. public function HorizontalAxis(listOfMark:Vector.<Number>, data:Vector.<BarData>, width:Number) { drawAxisLine(new Point(0, 0), new Point(width, 0)); for (var i:int = 0; i < listOfMark.length; i++) { drawAxisLine(new Point(listOfMark[i], -3), new Point(listOfMark[i], 3)); var textField:TextField = new TextField(); textField.text = data[i].label; textField.width = textField.textWidth + 5; textField.height = textField.textHeight + 3; textField.x = listOfMark[i] - textField.width / 2; textField.y = 5; addChild(textField); } } Now the vertical axis will make 10 marks at regular interval and will add a label with the associated value in it: for (var i:int = 0; i < _numberOfMarks; i++) { drawAxisLine(new Point( -3, (i + 1) * -heightOfAxis / _ numberOfMarks ), new Point(3, (i + 1) * -heightOfAxis / _ numberOfMarks)); var textField:TextField = new TextField(); textField.text = String(((i + 1) / (_numberOfMarks)) * maximumValue ); textField.width = textField.textWidth + 5; textField.height = textField.textHeight + 3; textField.x = -textField.width - 3; textField.y = (i + 1) * -heightOfAxis / _numberOfMarks - textField.height / 2; addChild(textField); } Finally, the BarChart class will take the three classes we just created and put it all together. By iterating through all the data, it will find the maximum value, so that we know what range of values to put on the vertical axis. var i:int; var maximumValue:Number = data[0].data; for (i = 1; i < data.length; i++) { if (data[i].data > maximumValue) { maximumValue = data[i].data; } } After that we create each bar, notice that we also keep the position of each bar to give it to the horizontal axis thereafter: var listOfMarks:Vector.<Number> = new Vector.<Number>(); var bar:Bar; for (i = 0; i < data.length; i++) { bar = new Bar(_barWidth, data[i].data * scaleHeight); bar.x = MARGIN + _barSpacing + _barWidth / 2 + i * (_barWidth + _barSpacing); listOfMarks.push(bar.x - MARGIN); bar.y = height - MARGIN; addChild(bar); } Now all we have left to do is create the axes and then we are done; this is done really easily as shown in the following code: _horizontalAxis = new HorizontalAxis(listOfMarks, data, width - MARGIN); _horizontalAxis.x = MARGIN; _horizontalAxis.y = height - MARGIN; addChild(_horizontalAxis); _verticalAxis = new VerticalAxis(height - MARGIN, maximumValue); _verticalAxis.x = MARGIN; _verticalAxis.y = height -MARGIN; addChild(_verticalAxis); How it works... So we divided all the elements into their own classes because this will permit us to extend and modify them more easily in the future. So let's begin where it all starts, the data. Well, our BarChart class accepts a vector of BarData as an argument. We did this so that you could add as many bars as you want and the chart would still work. Be aware that if you add many bars, you might have to give more width to the chart so that it can accommodate them. You can see in the code, that the width of the bar of determined by the width of the graph divided by the number bars. We decided that 85 percent of that value would be given to the bars and 15 percent would be given to the space between the bars. Those values are arbitrary and you can play with them to give different styles to the chart. Also the other important step is to determine what our data range is. We do so by finding what the maximum value is. For simplicity, we assume that the values will start at 0, but the validity of a chart is always relative to the data, so if there are negative values it wouldn't work, but you could always fix this. So when we found our maximum value, we can decide for a scale for the rest of the values. You can use the following formula for it: var scaleHeight:Number = (height - 10) / maximumValue; Here, height is the height of the chart and 10 is just a margin we leave to the graph to place the labels. After that, if we multiply that scale by the value of the data, it will give us the height of each bar and there you have it, a completed bar chart. There's more... We created a very simple version of a bar chart but there are numerous things we could do to improve it. Styling, interactivity, and the possibility of accommodating a wider range of data are just some examples. Styling This basic chart could use a little bit of styling. By modifying the color of the bars, the font of the labels, and by adding a drop shadow to the bars, it could be greatly enhanced. You could also make all of them dynamic so that you could specify them when you create a new chart. Interactivity It would be really good to show the values for the bars when you move the mouse over them. Right now you can kind of get an idea of which one is the biggest bar but that is all. If this feature is implemented, you can get the exact value. Accommodating a wider data range As we explained earlier, we didn't account for all the data range. Values could be very different; some could be negative, some could be very small (between 0 and 1), or you would want to set the minimum and maximum value of the vertical axes. The good thing here is that you can modify the code to better fit your data.
Read more
  • 0
  • 0
  • 4602
article-image-delving-deep-application-design
Packt
11 Jan 2013
14 min read
Save for later

Delving Deep into Application Design

Packt
11 Jan 2013
14 min read
(For more resources related to this topic, see here.) Before we get started, please note the folder structure that we'll be using. This will help you quickly find the files referred to in each recipe. Executables for every sample project will be output in the bin/debug or bin/release folders depending on the project's build configuration. These folders also contain the following required DLLs and configuration files: File name   Description   OgreMain.dll   Main Ogre DLL.   RenderSystem_Direct3D9.dll   DirectX 9 Ogre render system DLL. This is necessary only if you want Ogre to use the DirectX 9 graphics library.   RenderSystem_GL.dll   OpenGL Ogre render system DLL. This is necessary only if you want Ogre to use the OpenGL graphics library.   Plugin_OctreeSceneManager.dll   Octree scene manager Ogre plugin DLL.   Plugin_ParticleFX.dll   Particle effects Ogre plugin DLL.   ogre.cfg   Ogre main configuration file that includes render system settings.   resources.cfg   Ogre resource configuration file that contains paths to all resource locations. Resources include graphics files, shaders, material files, mesh files, and so on.   plugins.cfg   Ogre plugin configuration file that contains a list of all the plugins we want Ogre to use. Typical plugins include the Plugin_OctreeSceneManager, RenderSystem_Direct3D9, RenderSystem_ GL, and so on.   In the bin/debug folder, you'll notice that the debug versions of the Ogre plugin DLLs all have a _d appended to the filename. For example, the debug version of OgreMain.dll is OgreMain_d.dll. This is the standard method for naming debug versions of Ogre DLLs. The media folder contains all the Ogre resource files, and the OgreSDK_vc10_v1-7-1 folder contains the Ogre header and library files. Creating a Win32 Ogre application The Win32 application is the leanest and meanest of windowed applications, which makes it a good candidate for graphics. In this recipe, we will create a simple Win32 application that displays a 3D robot model that comes with Ogre, in a window. Because these steps are identical for all Win32 Ogre applications, you can use the completed project as a starting point for new Win32 applications. Getting ready To follow along with this recipe, open the solution located in the Recipes/Chapter01/OgreInWin32 folder in the code bundle available on the Packt website. How to do it... We'll start off by creating a new Win32 application using the Visual C++ Win32 application wizard. Create a new project by clicking on File | New | Project. In the New Project dialog-box, expand Visual C++, and click on Win32 Project. Name the project OgreInWin32. For Location, browse to the Recipes folder and append Chapter_01_Examples, then click on OK. In the Win32 Application Wizard that appears, click on Next. For Application type, select Windows application, and then click on Finish to create the project. At this point, we have everything we need for a bare-bones Win32 application without Ogre. Next, we need to adjust our project properties, so that the compiler and linker know where to put our executable and find the Ogre header and library files. Open the Property Pages dialog-box, by selecting the Project menu and clicking on Properties. Expand Configuration Properties and click on General. Set Character Set to Not Set. Next, click on Debugging. Select the Local Windows Debugger as the Debugger to launch, then specify the Command for starting the application as ......bindebug$(TargetName)$(TargetExt). Each project property setting is automatically written to a per-user file with the extension .vcxproj.user, whenever you save the solution. Next we'll specify our VC++ Directories, so they match our Cookbook folder structure. Select VC++ Directories to bring up the property page where we'll specify general Include Directories and Library Directories. Click on Include Directories, then click on the down arrow button that appears on the right of the property value, and click on <edit>. In the Include Directories dialog-box that appears, click on the first line of the text area, and enter the relative path to the Boost header files: ......OgreSDK_vc10_v1-7-1boost_1_42. Click on the second line, and enter the relative path to the Ogre header files ......OgreSDK_vc10_v1-7-1includeOGRE, and click OK. Edit the Library Directories property in the same way. Add the library directory ......OgreSDK_vc10_v1-7-1boost_1_42lib for Boost, and ......OgreSDK_vc10_v1-7-1libdebug for Ogre, then click OK. Next, expand the Linker section, and select General. Change the Output File property to ......bindebug$(TargetName)$(TargetExt). Then, change the Additional Library Directories property to ......OgreOgreSDK_vc10_v1-7-1libdebug. Finally, provide the linker with the location of the main Ogre code library. Select the Input properties section, and prepend OgreMain_d.lib; at the beginning of the line. Note that if we were setting properties for the release configuration, we would use OgreMain.lib instead of OgreMain_d.lib. Now that the project properties are set, let's add the code necessary to integrate Ogre in our Win32 application. Copy the Engine.cpp and Engine.h files from the Cookbook sample files to your new project folder, and add them to the project. These files contain the CEngine wrapper class that we'll be using to interface with Ogre. Open the OgreInWin32.cpp file, and include Engine.h, then declare a global instance of the CEngine class, and a forward declaration of our InitEngine() function with the other globals at the top of the file. CEngine *m_Engine = NULL; void InitEngine(HWND hWnd); Next, create a utility function to instantiate our CEngine class, called void InitEngine(HWND hWnd){ m_Engine = new CEngine(hWnd); } Then, call InitEngine() from inside the InitInstance() function, just after the window handle has been created successfully, as follows: hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd){ return FALSE; } InitEngine(hWnd); Our last task is to render the 3D scene and display it in the window when we receive a WM_PAINT message. Add a call to renderOneFrame() to the WndProc() function, as follows: case WM_PAINT: hdc = BeginPaint(hWnd, &ps); m_Engine->m_Root->renderOneFrame(); EndPaint(hWnd, &ps); break; And that's it! How it works... Let's look at the CEngine class to see how we create and initialize an instance of the Ogre engine, and add a camera and robot model to the scene. Open Engine.cpp, and look at the constructor for CEngine. In the constructor, we create an instance of the Ogre engine, and store it in the m_Root class member variable. m_Root = new Ogre::Root("", "", Ogre::String(ApplicationPath + Ogre::String("OgreInWin32.log"))); An instance of Ogre::Root must exist before any other Ogre functions are called. The first parameter to the constructor is the plugins configuration filename, which defaults to plugins.cfg, but we pass it an empty string because we are going to load that file manually later. The second parameter is the main configuration filename, which defaults to ogre.cfg, but we pass it an empty string, also because we'll be loading that file manually as well. The third parameter is the name of the log file where Ogre will write the debugging and the hardware information. Once the Ogre::Root instance has been created, it can be globally accessed by Root::getSingleton(), which returns a reference or Root::getSingletonPtr(), which returns a pointer. Next, we manually load the configuration file ogre.cfg, which resides in the same directory as our application executable. OgreConfigFile.load(Ogre::String(ApplicationPath + Ogre::String("ogre.cfg")), "t:=", false); The ogre.cfg configuration file contains Ogre 3D engine graphics settings and typically looks as follows: # Render System indicates which of the render systems # in this configuration file we'll be using. Render System=Direct3D9 Rendering Subsystem [Direct3D9 Rendering Subsystem] Allow NVPerfHUD=No Anti aliasing=None Floating-point mode=Fastest Full Screen=Yes Rendering Device=NVIDIA GeForce 7600 GS (Microsoft Corporation - WDDM) VSync=No Video Mode=800 x 600 @ 32-bit colour [OpenGL Rendering Subsystem] Colour Depth=32 Display Frequency=60 FSAA=0 Full Screen=Yes RTT Preferred Mode=FBO VSync=No Video Mode=1024 x 768 Once the main configuration file is loaded, we manually load the correct render system plugin and tell Ogre which render system to use. Ogre::String RenderSystemName; RenderSystemName = OgreConfigFile.getSetting("Render System"); m_Root->loadPlugin("RenderSystem_Direct3D9_d); Ogre::RenderSystemList RendersList = m_Root->getAvailableRenderers(); m_Root->setRenderSystem(RendersList[0]); There's actually a little more code in Engine.cpp for selecting the correct render system plugin to load, but for our render system settings the RenderSystem_Direct3D9_d plugin is all we need. Next, we load the resources.cfg configuration file. Ogre::ConfigFile cf; Ogre::String ResourcePath = ApplicationPath + Ogre::String("resources. cfg"); cf.load(ResourcePath); The resources.cfg file contains a list of all the paths where Ogre should search for graphic resources. Then, we go through all the sections and settings in the resource configuration file, and add every location to the Ogre resource manager. Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); Ogre::String secName, typeName, archName; while (seci.hasMoreElements()){ secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iterator i; for(i = settings->begin(); i != settings->end(); ++i){ typeName = i->first; archName = i->second; archName = ApplicationPath + archName; Ogre::ResourceGroupManager::getSingleton(). addResourceLocation(archName, typeName, secName); } } Now, we are ready to initialize the engine. m_Root->initialise(false); We pass in false to the initialize() function, to indicate that we don't want Ogre to create a render window for us. We'll be manually creating a render window later, using the hWnd window handle from our Win32 Application. Every graphics object in the scene including all meshes, lights, and cameras are managed by the Ogre scene manager. There are several scene managers to choose from, and each specializes in managing certain types of scenes of varying sizes. Some scene managers support rendering vast landscapes, while others are best for enclosed spaces. We'll use the generic scene manager for this recipe, because we don't need any extra features. m_SceneManager = m_Root->createSceneManager(Ogre::ST_GENERIC, "Win32Ogre"); Remember when we initialized Ogre::Root, and specifically told it not to auto-create a render window? We did that because we create a render window manually using the externalWindowHandle parameter. Ogre::NameValuePairList params; params["externalWindowHandle"] = Ogre::StringConverter::toString((long)hWnd); params["vsync"] = "true"; RECT rect; GetClientRect(hWnd, &rect); Ogre::RenderTarget *RenderWindow = NULL; try{ m_RenderWindow = m_Root->createRenderWindow("Ogre in Win32", rect. right - rect.left, rect.bottom - rect.top, false, &params); } catch(...){ MessageBox(hWnd, "Failed to create the Ogre::RenderWindownCheck that your graphics card driver is up-to-date", "Initialize Render System", MB_OK | MB_ICONSTOP); exit(EXIT_SUCCESS); } As you have probably guessed, the createRenderWindow() method creates a new RenderWindow instance. The first parameter is the name of the window. The second and third parameters are the width and height of the window, respectively. The fourth parameter is set to false to indicate that we don't want to run in full-screen mode. The last parameter is our NameValuePair list, in which we provide the external window handle for embedding the Ogre renderer in our application window. If we want to see anything, we need to create a camera, and add it to our scene. The next bit of code does just that. m_Camera = m_SceneManager->createCamera("Camera"); m_Camera->setNearClipDistance(0.5); m_Camera->setFarClipDistance(5000); m_Camera->setCastShadows(false); m_Camera->setUseRenderingDistance(true); m_Camera->setPosition(Ogre::Vector3(200.0, 50.0, 100.0)); Ogre::SceneNode *CameraNode = NULL; CameraNode = m_SceneManager->getRootSceneNode()- >createChildSceneNode("CameraNode"); First, we tell the scene manager to create a camera, and give it the highly controversial name Camera. Next, we set some basic camera properties, such as the near and far clip distances, whether to cast shadows or not, and where to put the camera in the scene. Now that the camera is created and configured, we still have to attach it to a scene node for Ogre to consider it a part of the scene graph, so we create a new child scene node named CameraNode, and attach our camera to that node. The last bit of the camera-related code involves us telling Ogre that we want the content for our camera to end up in our render window. We do this by defining a viewport that gets its content from the camera, and displays it in the render window. Ogre::Viewport* Viewport = NULL; if (0 == m_RenderWindow->getNumViewports()){ Viewport = m_RenderWindow->addViewport(m_Camera); Viewport->setBackgroundColour(Ogre::ColourValue(0.8f, 1.0f, 0.8f)); } m_Camera->setAspectRatio(Ogre::Real(rect.right - rect.left) / Ogre::Real(rect.bottom - rect.top)); The first line of code checks whether we have already created a viewport for our render window or not; if not, it creates one with a greenish background color. We also set the aspect ratio of the camera to match the aspect ratio of our viewport. Without setting the aspect ratio, we could end up with some really squashed or stretched-looking scenes. You may wonder why you might want to have multiple viewports for a single render window. Consider a car racing game where you want to display the rear view mirror in the top portion of your render window. One way to accomplish, this would be to define a viewport that draws to the entire render window, and gets its content from a camera facing out the front windshield of the car, and another viewport that draws to a small subsection of the render window and gets its content from a camera facing out the back windshield. The last lines of code in the CEngine constructor are for loading and creating the 3D robot model that comes with the Ogre SDK. Ogre::Entity *RobotEntity = m_SceneManager->createEntity("Robot", "robot.mesh"); Ogre::SceneNode *RobotNode = m_SceneManager->getRootSceneNode()- >createChildSceneNode(); RobotNode->attachObject(RobotEntity); Ogre::AxisAlignedBox RobotBox = RobotEntity->getBoundingBox(); Ogre::Vector3 RobotCenter = RobotBox.getCenter(); m_Camera->lookAt(RobotCenter); We tell the scene manager to create a new entity named Robot, and to load the robot.mesh resource file for this new entity. The robot.mesh file is a model file in the Ogre .mesh format that describes the triangles, textures, and texture mappings for the robot model. We then create a new scene node just like we did for the camera, and attach our robot entity to this new scene node, making our killer robot visible in our scene graph. Finally, we tell the camera to look at the center of our robot's bounding box. Finally, we tell Ogre to render the scene. m_Root->renderOneFrame(); We also tell Ogre to render the scene in OgreInWin32.cpp whenever our application receives a WM_PAINT message. The WM_PAINT message is sent when the operating system, or another application, makes a request that our application paints a portion of its window. Let's take a look at the WM_PAINT specific code in the WndProc() function again. case WM_PAINT: hdc = BeginPaint(hWnd, &ps); m_Engine->m_Root->renderOneFrame(); EndPaint(hWnd, &ps); break; The BeginPaint() function prepares the window for painting, and the corresponding EndPaint() function denotes the end of painting. In between those two calls is the Ogre function call to renderOneFrame(), which will draw the contents of our viewport in our application window. During the renderOneFrame() function call, Ogre gathers all the objects, lights, and materials that are to be drawn from the scene manager based on the camera's frustum or visible bounds. It then passes that information to the render system, which executes the 3D library function calls that run on your system's graphics hardware, to do the actual drawing on a render surface. In our case, the 3D library is Direct X and the render surface is the hdc, or Handle to the device context, of our application window. The result of all our hard work can be seen in the following screenshot: Flee in terror earthling! There's more... If you want to use the release configuration instead of debug, change the Configuration type to Release in the project properties, substitute the word release where you see the word debug in this recipe, and link the OgreMain.lib instead of OgreMain_d.lib in the linker settings. It is likely that at some point you will want to use a newer version of the Ogre SDK. If you download a newer version and extract it to the Recipes folder, you will need to change the paths in the project settings so that they match the paths for the version of the SDK you downloaded.
Read more
  • 0
  • 0
  • 1496

article-image-building-your-first-application
Packt
10 Jan 2013
12 min read
Save for later

Building Your First Application

Packt
10 Jan 2013
12 min read
(For more resources related to this topic, see here.) Improving the scaffolding application In this recipe, we discuss how to create your own scaffolding application and add your own configuration file. The scaffolding application is the collection of files that come with any new web2py application. How to do it... The scaffolding app includes several files. One of them is models/db.py, which imports four classes from gluon.tools (Mail, Auth, Crud, and Service), and defines the following global objects: db, mail, auth, crud, and service. The scaffolding application also defines tables required by the auth object, such as db.auth_user. The default scaffolding application is designed to minimize the number of files, not to be modular. In particular, the model file, db.py, contains the configuration, which in a production environment, is best kept in separate files. Here, we suggest creating a configuration file, models/0.py, that contains something like the following: from gluon.storage import Storage settings = Storage() settings.production = False if settings.production: settings.db_uri = 'sqlite://production.sqlite' settings.migrate = False else: settings.db_uri = 'sqlite://development.sqlite' settings.migrate = True settings.title = request.application settings.subtitle = 'write something here' settings.author = 'you' settings.author_email = 'you@example.come' settings.keywords = '' settings.description = '' settings.layout_theme = 'Default' settings.security_key = 'a098c897-724b-4e05-b2d8-8ee993385ae6' settings.email_server = 'localhost' settings.email_sender = 'you@example.com' settings.email_login = '' settings.login_method = 'local' settings.login_config = '' We also modify models/db.py, so that it uses the information from the configuration file, and it defines the auth_user table explicitly (this makes it easier to add custom fields): from gluon.tools import * db = DAL(settings.db_uri) if settings.db_uri.startswith('gae'): session.connect(request, response, db = db) mail = Mail() # mailer auth = Auth(db) # authentication/authorization crud = Crud(db) # for CRUD helpers using auth service = Service() # for json, xml, jsonrpc, xmlrpc, amfrpc plugins = PluginManager() # enable generic views for all actions for testing purpose response.generic_patterns = ['*'] mail.settings.server = settings.email_server mail.settings.sender = settings.email_sender mail.settings.login = settings.email_login auth.settings.hmac_key = settings.security_key # add any extra fields you may want to add to auth_user auth.settings.extra_fields['auth_user'] = [] # user username as well as email auth.define_tables(migrate=settings.migrate,username=True) auth.settings.mailer = mail auth.settings.registration_requires_verification = False auth.settings.registration_requires_approval = False auth.messages.verify_email = 'Click on the link http://' + request.env.http_host + URL('default','user', args=['verify_email']) + '/%(key)s to verify your email' auth.settings.reset_password_requires_verification = True auth.messages.reset_password = 'Click on the link http://' + request.env.http_host + URL('default','user', args=['reset_password']) + '/%(key)s to reset your password' if settings.login_method=='janrain': from gluon.contrib.login_methods.rpx_account import RPXAccount auth.settings.actions_disabled=['register', 'change_password', 'request_reset_password'] auth.settings.login_form = RPXAccount(request, api_key = settings.login_config.split(':')[-1], domain = settings.login_config.split(':')[0], url = "http://%s/%s/default/user/login" % (request.env.http_host, request.application)) Normally, after a web2py installation or upgrade, the welcome application is tar-gzipped into welcome.w2p, and is used as the scaffolding application. You can create your own scaffolding application from an existing application using the following commands from a bash shell: cd applications/app tar zcvf ../../welcome.w2p * There's more... The web2py wizard uses a similar approach, and creates a similar 0.py configuration file. You can add more settings to the 0.py file as needed. The 0.py file may contain sensitive information, such as the security_key used to encrypt passwords, the email_login containing the password of your smtp account, and the login_config with your Janrain password (http://www.janrain.com/). You may want to write this sensitive information in a read-only file outside the web2py tree, and read them from your 0.py instead of hardcoding them. In this way, if you choose to commit your application to a version-control system, you will not be committing the sensitive information The scaffolding application includes other files that you may want to customize, including views/layout.html and views/default/users.html. Some of them are the subject of upcoming recipes. Building a simple contacts application When you start designing a new web2py application, you go through three phases that are characterized by looking for the answer to the following three questions: What data should the application store? Which pages should be presented to the visitors? How should the page content, for each page, be presented? The answer to these three questions is implemented in the models, the controllers, and the views respectively. It is important for a good application design to try answering those questions exactly in this order, and as accurately as possible. Such answers can later be revised, and more tables, more pages, and more bells and whistles can be added in an iterative fashion. A good web2py application is designed in such a way that you can change the table definitions (add and remove fields), add pages, and change page views, without breaking the application. A distinctive feature of web2py is that everything has a default. This means you can work on the first of those three steps without the need to write code for the second and third step. Similarly, you can work on the second step without the need to code for the third. At each step, you will be able to immediately see the result of your work; thanks to appadmin (the default database administrative interface) and generic views (every action has a view by default, until you write a custom one). Here we consider, as a first example, an application to manage our business contacts, a CRM. We will call it Contacts. The application needs to maintain a list of companies, and a list of people who work at those companies. How to do it... First of all we create the model. In this step we identify which tables are needed and their fields. For each field, we determine whether they: Must contain unique values (unique=True) Contain empty values (notnull=True) Are references (contain a list of a record in another table) Are used to represent a record (format attribute) From now on, we will assume we are working with a copy of the default scaffolding application, and we only describe the code that needs to be added or replaced. In particular, we will assume the default views/layout.html and models/db.py. Here is a possible model representing the data we need to store in models/db_contacts.py: # in file: models/db_custom.py db.define_table('company', Field('name', notnull=True, unique=True), format='%(name)s') db.define_table('contact', Field('name', notnull=True), Field('company', 'reference company'), Field('picture', 'upload'), Field('email', requires=IS_EMAIL()), Field('phone_number', requires=IS_MATCH('[d-() ]+')), Field('address'), format='%(name)s') db.define_table('log', Field('body', 'text',notnull=True), Field('posted_on', 'datetime'), Field('contact', 'reference contact')) Of course, a more complex data representation is possible. You may want to allow, for example, multiple users for the system, allow the same person to work for multiple companies, and keep track of changes in time. Here, we will keep it simple. The name of this file is important. In particular, models are executed in alphabetical order, and this one must follow db.py. After this file has been created, you can try it by visiting the following url: http://127.0.0.1:8000/contacts/appadmin, to access the web2py database administrative interface, appadmin. Without any controller or view, it provides a way to insert, select, update, and delete records. Now we are ready to build the controller. We need to identify which pages are required by the application. This depends on the required workflow. At a minimum we need the following pages: An index page (the home page) A page to list all companies A page that lists all contacts for one selected company A page to create a company A page to edit/delete a company A page to create a contact A page to edit/delete a contact A page that allows to read the information about one contact and the communication logs, as well as add a new communication log Such pages can be implemented as follows: # in file: controllers/default.py def index(): return locals() def companies(): companies = db(db.company).select(orderby=db.company.name) return locals() def contacts(): company = db.company(request.args(0)) or redirect(URL('companies')) contacts = db(db.contact.company==company.id).select( orderby=db.contact.name) return locals() @auth.requires_login() def company_create(): form = crud.create(db.company, next='companies') return locals() @auth.requires_login() def company_edit(): company = db.company(request.args(0)) or redirect(URL('companies')) form = crud.update(db.company, company, next='companies') return locals() @auth.requires_login() def contact_create(): db.contact.company.default = request.args(0) form = crud.create(db.contact, next='companies') return locals() @auth.requires_login() def contact_edit(): contact = db.contact(request.args(0)) or redirect(URL('companies')) form = crud.update(db.contact, contact, next='companies') return locals() @auth.requires_login() def contact_logs(): contact = db.contact(request.args(0)) or redirect(URL('companies')) db.log.contact.default = contact.id db.log.contact.readable = False db.log.contact.writable = False db.log.posted_on.default = request.now db.log.posted_on.readable = False db.log.posted_on.writable = False form = crud.create(db.log) logs = db( db.log.contact==contact.id).select(orderby=db.log.posted_on) return locals() def download(): return response.download(request, db) def user(): return dict(form=auth()) Make sure that you do not delete the existing user, download, and service functions in the scaffolding default.py. Notice how all pages are built using the same ingredients: select queries and crud forms. You rarely need anything else. Also notice the following: Some pages require a request.args(0) argument (a company ID for contacts and company_edit, a contact ID for contact_edit, and contact_logs). All selects have an orderby argument. All crud forms have a next argument that determines the redirection after form submission. All actions return locals(), which is a Python dictionary containing the local variables defined in the function. This is a shortcut. It is of course possible to return a dictionary with any subset of locals(). contact_create sets a default value for the new contact company to the value passed as args(0). The contacts_logs retrieves past logs after processing crud.create for a new log entry. This avoid unnecessarily reloading of the page, when a new log is inserted. At this point our application is fully functional, although the look-and-feel and navigation can be improved.: You can create a new company at: http://127.0.0.1:8000/contacts/default/company_create You can list all companies at: http://127.0.0.1:8000/contacts/default/companies You can edit company #1 at: http://127.0.0.1:8000/contacts/default/company_edit/1 You can create a new contact at: http://127.0.0.1:8000/contacts/default/contact_create You can list all contacts for company #1 at: http://127.0.0.1:8000/contacts/default/contacts/1 You can edit contact #1 at: http://127.0.0.1:8000/contacts/default/contact_edit/1 And you can access the communication log for contact #1 at: http://127.0.0.1:8000/contacts/default/contact_logs/1 You should also edit the models/menu.py file, and replace the content with the following: response.menu = [['Companies', False, URL('default', 'companies')]] The application now works, but we can improve it by designing a better look and feel for the actions. That's done in the views. Create and edit file views/default/companies.html: {{extend 'layout.html'}} <h2>Companies</h2> <table> {{for company in companies:}} <tr> <td>{{=A(company.name, _href=URL('contacts', args=company.id))}}</td> <td>{{=A('edit', _href=URL('company_edit', args=company.id))}}</td> </tr> {{pass}} <tr> <td>{{=A('add company', _href=URL('company_create'))}}</td> </tr> </table> response.menu = [['Companies', False, URL('default', 'companies')]] Here is how this page looks: Create and edit file views/default/contacts.html: {{extend 'layout.html'}} <h2>Contacts at {{=company.name}}</h2> <table> {{for contact in contacts:}} <tr> <td>{{=A(contact.name, _href=URL('contact_logs', args=contact.id))}}</td> <td>{{=A('edit', _href=URL('contact_edit', args=contact.id))}}</td> </tr> {{pass}} <tr> <td>{{=A('add contact', _href=URL('contact_create', args=company.id))}}</td> </tr> </table> Here is how this page looks: Create and edit file views/default/company_create.html: {{extend 'layout.html'}} <h2>New company</h2> {{=form}} Create and edit file views/default/contact_create.html: {{extend 'layout.html'}} <h2>New contact</h2> {{=form}} Create and edit file: views/default/company_edit.html: {{extend 'layout.html'}} <h2>Edit company</h2> {{=form}} Create and edit file views/default/contact_edit.html: {{extend 'layout.html'}} <h2>Edit contact</h2> {{=form}} Create and edit file views/default/contact_logs.html: {{extend 'layout.html'}} <h2>Logs for contact {{=contact.name}}</h2> <table> {{for log in logs:}} <tr> <td>{{=log.posted_on}}</td> <td>{{=MARKMIN(log.body)}}</td> </tr> {{pass}} <tr> <td></td> <td>{{=form}}</td> </tr> </table> Here is how this page looks: Notice that in the last view, we used the function MARKMIN to render the content of the db.log.body, using the MARKMIN markup. This allows embedding links, images, anchors, font formatting information, and tables in the logs. For details about the MARKMIN syntax we refer to: http://web2py.com/examples/static/markmin.html.
Read more
  • 0
  • 0
  • 7105
Modal Close icon
Modal Close icon