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

7019 Articles
article-image-business-processes-bpel
Packt
08 Sep 2010
10 min read
Save for later

Business Processes with BPEL

Packt
08 Sep 2010
10 min read
(For more resources on BPEL, SOA and Oracle see here.) BPEL business process example We describe an oversimplified scenario, where the client invokes the business process, specifying the name of the employee, the destination, the departure date, and the return date. The BPEL business process first checks the employee travel status. We will assume that a service exists through which such a check can be made. Then the BPEL process will check the price for the flight ticket with two airlines—American Airlines and Delta Airlines. Again we will suppose that both airline companies provide a service through which such checks can be made. Finally, the BPEL process will select the lower price and return the travel plan to the client. For the purpose of this example, we first build a synchronous BPEL process, to maintain simplicity. This means that the client will wait for the response. Later in this article, we modify the example and make the BPEL process asynchronous. We will assume that the service for checking the employee travel status is synchronous. This is reasonable because such data can be obtained immediately and returned to the caller. To acquire the plane ticket prices we use asynchronous invocations. Again, this is reasonable because it might take a little longer to confirm the plane travel schedule. We assume that both airlines offer a service and that both Web Services are identical(provide equal port types and operations). This assumption simplifies our example. In real-world scenarios, you will usually not have the choice about the services but will have to use whatever services are provided by your partners. If you have the luxury of designing the Web Services along with the BPEL process, consider which is the best interface. Usually we use asynchronous services for long-lasting operations and synchronous services for operations that return a result in a relatively short time. If we use asynchronous services, the BPEL process is usually asynchronous as well. In our example, we first develop a synchronous BPEL process that invokes two asynchronous airline Web Services. This is legal, but not recommended in real-world scenarios since the client may have to wait for an arbitrarily long time. In the real world, the solution would be to develop an asynchronous BPEL process, which we will cover later in this article. We invoke Web Services of both airlines concurrently and asynchronously. This means that our BPEL process will have to implement the callback operation (and a port type), through which the airlines will return the flight ticket confirmation. Finally, the BPEL process returns the best airline ticket to the client. In this example, to maintain simplicity, we will not implement any fault handling, which is crucial in real-world scenarios. Let's start by presenting the BPEL process activities using a UML activity diagram. In each activity, we have used the stereotype to indicate the BPEL operation used. Although the presented process might seem very simple, it will offer a good start for learning BPEL. To develop the BPEL process, we will go through the following steps: Get familiar with the involved services Define the WSDL for the BPEL process Define partner link types Define partner links Declare variables Write the process logic definition Involved services Before we can start writing the BPEL process definition, we have to get familiar with all services invoked from our business process. These services are sometimes called partner services. In our example, three services are involved: The Employee Travel Status service The American Airlines service The Delta Airlines service The two airline services share equal WSDL descriptions. The services used in this example are not real, so we will have to write WSDLs and even implement them to run the example. In real-world scenarios we would obviously use real Web Services exposed by partners involved in the business process. The services and the BPEL process example can be downloaded from http://www.packtpub.com The example runs on the Oracle SOA Suite. Web Service descriptions are available through WSDL. WSDL specifies the operations and port types Web Services offer, the messages they accept, and the types they define. We will now look at both Web Services. Employee Travel Status service Understanding the services that a business process interacts with is crucial to writing the BPEL process definition. Let's look into the details of our Employee Travel Status service. It provides the EmployeeTravelStatusPT port type through which the employee travel status can be checked using the EmployeeTravelStatus operation. The operation will return the travel class an employee can use—economy, business, or first. This is shown in the following figure: The operation is a synchronous request/response operation as we can see from the WSDL: <?xml version="1.0" encoding="utf-8" ?> <definitions targetNamespace="http://packtpub.com/service/employee/" > ... <portType name="EmployeeTravelStatusPT"> <operation name="EmployeeTravelStatus"> <input message="tns:EmployeeTravelStatusRequestMessage" /> <output message="tns:EmployeeTravelStatusResponseMessage" /> </operation> </portType> ... The EmployeeTravelStatus operation consists of an input and an output message. To maintain simplicity, the fault is not declared. The definitions of input and output messages are also a part of the WSDL: ... <message name="EmployeeTravelStatusRequestMessage"> <part name="employee" element="tns:Employee" /> </message> <message name="EmployeeTravelStatusResponseMessage"> <part name="travelClass" element="tns:TravelClass" /> </message> ... The EmployeeTravelStatusRequestMessage message has a single part—employee of element Employee with type EmployeeType, while the EmployeeTravelStatusResponseMessage has a part called travelClass, of element TravelClass and type TravelClassType. The EmployeeType and the TravelClassType types are defined within the WSDL under the &lttypes> element: ... <types> <xs:schema elementFormDefault="qualified" targetNamespace="http://packtpub.com/service/employee/"> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="FirstName" type="xs:string" /> <xs:element name="LastName" type="xs:string" /> <xs:element name="Department" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:element name="Employee" type="EmployeeType"/> ... EmployeeType is a complex type and has three elements: first name, last name, and department name. TravelClassType is a simple type that uses the enumeration to list the possible classes: ... <xs:simpleType name="TravelClassType"> <xs:restriction base="xs:string"> <xs:enumeration value="Economy"/> <xs:enumeration value="Business"/> <xs:enumeration value="First"/> </xs:restriction> </xs:simpleType> <xs:element name="TravelClass" type="TravelClassType"/> </xs:schema> </types> ... Now let us look at the airline service. Airline service The Airline Service is an asynchronous web service. Therefore, it specifies two port types. The first, FlightAvailabilityPT, is used to check the flight availability using the FlightAvailability operation. To return the result, the service specifies the second port type, FlightCallbackPT. This port type specifies the FlightTicketCallback operation. Although the Airline Service defines two port types, it only implements the FlightAvailabilityPT. FlightCallbackPT is implemented by the BPEL process, which is the client of the web service. The architecture of the service is schematically shown as follows: Flight Availability port type FlightAvailability is an asynchronous operation, containing only the input message. <?xml version="1.0" encoding="utf-8" ?> <definitions targetNamespace="http://packtpub.com/service/airline/" > ... <portType name="FlightAvailabilityPT"> <operation name="FlightAvailability"> <input message="tns:FlightTicketRequestMessage" /> </operation> </portType> ... The definition of the input message is shown as follows. It consists of two parts—the flightData part and the travelClass part: <message name="FlightTicketRequestMessage"> <part name="flightData" element="tns:FlightRequest" /> <part name="travelClass" element="emp:TravelClass" /> </message> The travelClass part is the same as that used in the Employee Travel Status service. The flightData part is of element FlightRequest, which is defined as follows: ... <types> <xs:schema elementFormDefault="qualified" targetNamespace="http://packtpub.com/service/airline/"> <xs:complexType name="FlightRequestType"> <xs:sequence> <xs:element name="OriginFrom" type="xs:string" /> <xs:element name="DestinationTo" type="xs:string" /> <xs:element name="DesiredDepartureDate" type="xs:date" /> <xs:element name="DesiredReturnDate" type="xs:date" /> </xs:sequence> </xs:complexType> <xs:element name="FlightRequest" type="FlightRequestType"/> ... FlightRequestType is a complex type and has four elements through which we specify the flight origin and destination, the desired departure data, and the desired return date. Flight Callback port type The Airline Service needs to specify another port type for the callback operation through which the BPEL process receives the flight ticket response messages. The service will only specify this port type, which is implemented by the BPEL process. We define the FlightCallbackPT port type with the FlightTicketCallback operation, which has the TravelResponseMessage input message: ... <portType name="FlightCallbackPT"> <operation name="FlightTicketCallback"> <input message="tns:TravelResponseMessage" /> </operation> </portType> ... TravelResponseMessage consists of a single part called confirmationData: ... <message name="TravelResponseMessage"> <part name="confirmationData" element="tns:FlightConfirmation" /> </message> ... Now that we are familiar with both services, we can define the BPEL process. Remember that our BPEL process is an actual web service. Therefore, we first have to write the WSDL for the BPEL process. <xs:complexType name="FlightConfirmationType"> <xs:sequence> <xs:element name="FlightNo" type="xs:string" /> <xs:element name="TravelClass" type="tns:TravelClassType" /> <xs:element name="Price" type="xs:float" /> <xs:element name="DepartureDateTime" type="xs:dateTime" /> <xs:element name="ReturnDateTime" type="xs:dateTime" /> <xs:element name="Approved" type="xs:boolean" /> </xs:sequence> </xs:complexType> <xs:element name="FlightConfirmation" type="FlightConfirmationType"/> </xs:schema> </types> WSDL for the BPEL process The business travel BPEL process is exposed as a service. We need to define the WSDL for it. The process will have to receive messages from its clients and return results. So it has to expose a port type that will be used by the client to start the process and get the reply. We define the TravelApprovalPT port type with the TravelApproval operation, as shown in the following figure: We have already said that the BPEL process is synchronous. The TravelApproval operation will be of synchronous request/response type. <?xml version="1.0" encoding="utf-8" ?> <definitions targetNamespace="http://packtpub.com/bpel/travel/" > ... <portType name="TravelApprovalPT"> <operation name="TravelApproval"> <input message="tns:TravelRequestMessage" /> <output message="aln:TravelResponseMessage" /> </operation> </portType> ... We also have to define messages. The TravelRequestMessage consists of two parts: employee: The employee data, which we reuse from the Employee Travel Status service definition flightData: The flight data, which we reuse from the airline service definition ... <import namespace="http://packtpub.com/service/employee/" location="./Employee.wsdl"/> <import namespace="http://packtpub.com/service/airline/" location="./Airline.wsdl"/> ... <message name="TravelRequestMessage"> <part name="employee" element="emp:Employee" /> <part name="flightData" element="aln:FlightRequest" /> </message> ... For the output message, we use the same message used to return the flight information from the airline service: the TravelResponseMessage defined in the aln namespace. This is reasonable because the BPEL process will get the TravelResponseMessage from both airlines, select the most appropriate (the cheapest), and return the same message to the client. As we have already imported the Airline WSDL, we are done. When writing the WSDL for the BPEL process, we usually do not define the binding (&ltbinding>) and the service (&ltservice>) sections. These are usually generated by the BPEL execution environment (BPEL server). Before we can start writing the BPEL process, we still need to define partner link types.
Read more
  • 0
  • 0
  • 2363

article-image-drools-jboss-rules-50complex-event-processing
Packt
08 Sep 2010
15 min read
Save for later

Drools JBoss Rules 5.0:Complex Event Processing

Packt
08 Sep 2010
15 min read
(For more resources on JBoss see here.) CEP and ESP CEP and ESP are styles of processing in an Event Driven Architecture (General introduction to Event Driven Architecture can be found at: http://elementallinks.typepad.com/bmichelson/2006/02/eventdriven_arc.html). One of the core benefits of such an architecture is that it provides loose coupling of its components. A component simply publishes events about actions that it is executing and other components can subscribe/listen to these events. The producer and the subscriber are completely unaware of each other. A subscriber listens for events and doesn't care where they come from. Similarly, a publisher generates events and doesn't know anything about who is listening to those events. Some orchestration layer then deals with the actual wiring of subscribers to publishers. An event represents a significant change of state. It usually consists of a header and a body. The header contains meta information such as its name, time of occurrence, duration, and so on. The body describes what happened. For example, if a transaction has been processed, the event body would contain the transaction ID, the amount transferred, source account number, destination account number, and so on. CEP deals with complex events. A complex event is a set of simple events. For example, a sequence of large withdrawals may raise a suspicious transaction event. The simple events are considered to infer that a complex event has occurred. ESP is more about real-time processing of huge volume of events. For example, calculating the real-time average transaction volume over time. More information about CEP and ESP can be found on the web site, http://complexevents.com/ or in a book written by Prof. David Luckham, The Power of Events. This book is considered the milestone for the modern research and development of CEP. There are many existing pure CEP/ESP engines, both commercial and open source. Drools Fusion enhances the rule based programming with event support. It makes use of its Rete algorithm and provides an alternative to existing engines. Drools Fusion Drools Fusion is a Drools module that is a part of the Business Logic Integration Platform. It is the Drools event processing engine covering both CEP and ESP. Each event has a type, a time of occurrence, and possibly, a duration. Both point in time (zero duration) and interval-based events are supported. Events can also contain other data like any other facts—properties with a name and type. All events are facts but not all facts are events. An event's state should not be changed. However, it is valid to populate the unpopulated values. Events have clear life cycle windows and may be transparently garbage collected after the life cycle window expires (for example, we may be interested only in transactions that happened in the last 24 hours). Rules can deal with time relationships between events. Fraud detection It will be easier to explain these concepts by using an example—a fraud detection system. Fraud in banking systems is becoming a major concern. The amount of online transactions is increasing every day. An automatic system for fraud detection is needed. The system should analyze various events happening in a bank and, based on a set of rules, raise an appropriate alarm. This problem cannot be solved by the standard Drools rule engine. The volume of events is huge and it happens asynchronously. If we simply inserted them into the knowledge session, we would soon run out of memory. While the Rete algorithm behind Drools doesn't have any theoretical limitation on number of objects in the session, we could use the processing power more wisely. Drools Fusion is the right candidate for this kind of task. Problem description Let's consider the following set of business requirements for the fraud detection system: If a notification is received from a customer about a stolen card, block this account and any withdrawals from this account. Check each transaction against a blacklist of account numbers. If the transaction is transferring money from/to such an account, then flag this transaction as suspicious with the maximum severity. If there are two large debit transactions from the same account within a ninety second period and each transaction is withdrawing more than 300% of the average monthly (30 days) withdrawal amount, flag these transactions as suspicious with minor severity. If there is a sequence of three consecutive, increasing, debit transactions originating from a same account within a three minute period and these transactions are together withdrawing more than 90% of the account's average balance over 30 days, then flag those transactions as suspicious with major severity and suspend the account. If the number of withdrawals over a day is 500% higher than the average number of withdrawals over a 30 day period and the account is left with less than 10% of the average balance over a month (30 days), then flag the account as suspicious with minor severity. Duplicate transactions check—if two transactions occur in a time window of 15 seconds that have the same source/destination account number, are of the same amount, and just differ in the transaction ID, then flag those transactions as duplicates. Monitoring: Monitor the average withdrawal amount over all of the accounts for 30 days. Monitor the average balance across all of the accounts. Design and modeling Looking at the requirements, we'll need a way of flagging a transaction as suspicious. This state can be added to an existing Transaction type, or we can externalize this state to a new event type. We'll do the latter. The following new events will be defined: TransactionCreatedEvent—An event that is triggered when a new transaction is created. It contains a transaction identifier, source account number, destination account number, and the actual amount transferred. TransactionCompletedEvent—An event that is triggered when an existing transaction has been processed. It contains the same fields as the TransactionCreatedEvent class. AccountUpdatedEvent—An event that is triggered when an account has been updated. It contains the account number, current balance, and the transaction identifier of a transaction that initiated this update. SuspiciousAccount—An event triggered when there is some sort of a suspicion around the account. It contains the account number and severity of the suspicion. It is an enumeration that can have two values MINOR and MAJOR. This event's implementation is shown in the following code. SuspiciousTransaction—Similar to SuspiciousAccount, this is an event that flags a transaction as suspicious. It contains a transaction identifier and severity level. LostCardEvent—An event indicating that a card was lost. It contains an account number. One of events described—SuspiciousAccount—is shown in the following code. It also defines SuspiciousAccountSeverity enumeration that encapsulates various severity levels that the event can represent. The event will define two properties. One of them is already mentioned severity and the other one will identify the account—accountNumber. /*** marks an account as suspicious*/public class SuspiciousAccount implements Serializable { public enum SuspiciousAccountSeverity { MINOR, MAJOR}private final Long accountNumber;private final SuspiciousAccountSeverity severity;public SuspiciousAccount(Long accountNumber, SuspiciousAccountSeverity severity) { this.accountNumber = accountNumber; this.severity = severity; } private transient String toString; @Override public String toString() { if (toString == null) { toString = new ToStringBuilder(this).appendSuper( super.toString()).append("accountNumber", accountNumber).append("severity", severity) .toString(); } return toString;} Code listing 1: Implementation of SuspiciousAccount event. Please note that the equals and hashCode methods in SuspiciousAccount from the preceding code listing are not overridden. This is because an event represents an active entity, which means that each instance is unique. The toString method is implemented using org.apache.commons.lang.builder.ToStringBuilder. All of these event classes are lightweight, and they have no references to other domain classes (no object reference; only a number—accountNumber—in this case). They are also implementing the Serializable interface. This makes them easier to transfer between JVMs. As best practice, this event is immutable. The two properties above (accountNumber and severity) are marked as final. They can be set only through a constructor (there are no set methods—only two get methods). The get methods were excluded from this code listing. The events themselves don't carry a time of occurrence—a time stamp (they easily could, if we needed it; we'll see how in the next set of code listings). When the event is inserted into the knowledge session, the rule engine assigns such a time stamp to FactHandle that is returned. (Remember? session.insert(..) returns a FactHandle). In fact, there is a special implementation of FactHandle called EventFactHandle. It extends the DefaultFactHandle (which is used for normal facts) and adds few additional fields, for example—startTimestamp and duration. Both contain millisecond values and are of type long. Ok, we now have the event classes and we know that there is a special FactHandle for events. However, we still don't know how to tell Drools that our class represents an event. Drools type declarations provide this missing link. It can define new types and enhance existing types. For example, to specify that the class TransactionCreatedEvent is an event, we have to write: declare TransactionCreatedEvent @role( event )end Code listing 2: Event role declaration (cep.drl file). This code can reside inside a normal .drl file. If our event had a time stamp property or a duration property, we could map it into startTimestamp or duration properties of EventFactHandle by using the following mapping: @duration( durationProperty ) Code listing 3: Duration property mapping. The name in brackets is the actual name of the property of our event that will be mapped to the duration property of EventFactHandle. This can be done similarly for startTimestamp property. As an event's state should not be changed (only unpopulated values can be populated), think twice before declaring existing beans as events. Modification to a property may result in an unpredictable behavior. In the following examples, we'll also see how to define a new type declaration Fraud detection rules Let's imagine that the system processes thousands of transactions at any given time. It is clear that this is challenging in terms of time and memory consumption. It is simply not possible to keep all of the data (transactions, accounts, and so on) in memory. A possible solution would be to keep all of the accounts in memory as there won't be that many of them (in comparison to transactions) and keep only transactions for a certain period. With Drools Fusion, we can do this very easily by declaring that a Transaction is an event. The transaction will then be inserted into the knowledge session through an entry-point. Each entry point defines a partition in the input data storage, reducing the match space and allowing patterns to target specific partitions. Matching data from a partition requires explicit reference at the pattern declaration. This makes sense, especially if there are large quantities of data and only some rules are interested in them. We'll look at entry points in the following example. If you are still concerned about the volume of objects in memory, this solution can be easily partitioned, for example, by account number. There might be more servers, each processing only a subset of accounts (simple routing strategy might be: accountNumber module totalNumberOfServersInCluster). Then each server would receive only appropriate events. Notification The requirement we're going to implement here is essentially to block an account whenever a LostCardEvent is received. This rule will match two facts: one of type Account and one of type LostCardEvent. The rule will then set the the status of this account to blocked. The implementation of the rule is as follows: rule notification when $account : Account( status != Account.Status.BLOCKED ) LostCardEvent( accountNumber == $account.number ) from entry-point LostCardStream then modify($account) { setStatus(Account.Status.BLOCKED) };end Code listing 4: Notification rule that blocks an account (cep.drl file). As we already know, Account is an ordinary fact from the knowledge session. The second fact—LostCardEvent—is an event from an entry point called LostCardStream. Whenever a new event is created and goes through the entry point, LostCardStream, this rule tries to match (checks if its conditions can be satisfied). If there is an Account in the knowledge session that didn't match with this event yet, and all conditions are met, the rule is activated. The consequence sets the status of the account to blocked in a modify block. As we're updating the account in the consequence and also matching on it in the condition, we have to add a constraint that matches only the non-blocked accounts to prevent looping (see above: status != Account.Status.BLOCKED). Test configuration setup Following the best practice that every code/rule needs to be tested, we'll now set up a class for writing unit tests. All of the rules will be written in a file called cep.drl. When creating this file, just make sure it is on the classpath. The creation of knowledgeBase won't be shown. It is similar to the previous tests that we've written. We just need to change the default knowledge base configuration slightly: KnowledgeBaseConfiguration config = KnowledgeBaseFactory .newKnowledgeBaseConfiguration();config.setOption( EventProcessingOption.STREAM ); Code listing 5: Enabling STREAM event processing mode on knowledge base configuration. This will enable the STREAM event processing mode. KnowledgeBaseConfiguration from the preceding code is then used when creating the knowledge base—KnowledgeBaseFactory.newKnowledgeBase(config). Part of the setup is also clock initialization. We already know that every event has a time stamp. This time stamp comes from a clock which is inside the knowledge session. Drools supports several clock types, for example, a real-time clock or a pseudo clock. The real-time clock is the default and should be used in normal circumstances. The pseudo clock is especially useful for testing as we have complete control over the time. The following initialize method sets up a pseudo clock. This is done by setting the clock type on KnowledgeSessionConfiguration and passing this object to the newStatefulKnowledgeSession method of knowledgeBase. The initialize method then makes this clock available as a test instance variable called clock when calling session.getSessionClock(), as we can see in the following code: public class CepTest { static KnowledgeBase knowledgeBase; StatefulKnowledgeSession session; Account account; FactHandle accountHandle; SessionPseudoClock clock; TrackingAgendaEventListener trackingAgendaEventListener; WorkingMemoryEntryPoint entry; @Before public void initialize() throws Exception { KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); conf.setOption( ClockTypeOption.get( "pseudo" ) ); session = knowledgeBase.newStatefulKnowledgeSession(conf, null); clock = (SessionPseudoClock) session.getSessionClock(); trackingAgendaEventListener = new TrackingAgendaEventListener(); session.addEventListener(trackingAgendaEventListener); account = new Account(); account.setNumber(123456l); account.setBalance(BigDecimal.valueOf(1000.00)); accountHandle = session.insert(account); Code listing 6: Unit tests setup (CepTest.java file). The preceding initialize method also creates an event listener and passes it into the session. The event listener is called TrackingAgendaEventListener. It simply tracks all of the rule executions. It is useful for unit testing to verify whether a rule fired or not. Its implementation is as follows: public class TrackingAgendaEventListener extends DefaultAgendaEventListener { List<String> rulesFiredList = new ArrayList<String>(); @Override public void afterActivationFired( AfterActivationFiredEvent event) { rulesFiredList.add(event.getActivation().getRule() .getName()); } public boolean isRuleFired(String ruleName) { for (String firedRuleName : rulesFiredList) { if (firedRuleName.equals(ruleName)) { return true; } } return false; } public void reset() { rulesFiredList.clear(); } } Code listing 7: Agenda event listener that tracks all rules that have been fired. DefaultAgendaEventListener comes from the org.drools.event.rule package that is part of drools-api.jar file as opposed to the org.drools.event package that is part of the old API in drools-core.jar. All of the Drools agenda event listeners must implement the AgendaEventListener interface. TrackingAgendaEventListener in the preceding code extends DefaultAgendaEventListener so that we don't have to implement all of the methods defined in the AgendaEventListener interface. Our listener just overrides the afterActivationFired method that will be called by Drools every time a rule's consequence has been executed. Our implementation of this method adds the fired rule name into a list of fired rules—rulesFiredList. Then the convenience method isRuleFired takes a ruleName as a parameter and checks if this rule has been executed/fired. The reset method is useful for clearing out the state of this listener, for example, after session.fireAllRules is called. Now, back to the test configuration setup. The last part of the initialize method from code listing 6 is account object creation (account = new Account(); ...). This is for convenience purposes so that every test does not have to create one. The account balance is set to 1000. The account is inserted into the knowledge session and its FactHandle is stored so that the account object can be easily updated. Testing the notification rule The test infrastructure is now fully set up and we can write a test for the notification rule from code listing 4 as follows: @Test public void notification() throws Exception { session.fireAllRules(); assertNotSame(Account.Status.BLOCKED,account.getStatus()); entry = session .getWorkingMemoryEntryPoint("LostCardStream"); entry.insert(new LostCardEvent(account.getNumber())); session.fireAllRules(); assertSame(Account.Status.BLOCKED, account.getStatus()); } Code listing 8: Notification rule's unit test (CepTest.java file). The test verifies that the account is not blocked by accident first. Then it gets the LostCardStream entry point from the session by calling: session.getWorkingMemoryEntryPoint("LostCardStream"). Then the code listing demonstrates how an event can be inserted into the knowledge session through an entry-point—entry.insert(new LostCardEvent(...)). In a real application, you'll probably want to use Drools Pipeline for inserting events into the knowledge session. It can be easily connected to an existing ESB (Enterprise Service Bus) or a JMS topic or queue. Drools entry points are thread-safe. Each entry point can be used by a different thread; however, no two threads can concurrently use the same entry point. In this case, it makes sense to start the engine in fireUntilHalt mode in a separate thread like this: new Thread(new Runnable() { public void run() { session.fireUntilHalt(); } }).start(); Code listing 9: Continuous execution of rules. The engine will then continuously execute activations until the session.halt() method is called. The test then verifies that the status of the account is blocked. If we simply executed session.insert(new LostCardEvent(..)); the test would fail because the rule wouldn't see the event.
Read more
  • 0
  • 0
  • 4739

article-image-creating-and-styling-container-images-using-dotnetnuke
Packt
08 Sep 2010
4 min read
Save for later

Creating and Styling Container Images using DotNetNuke

Packt
08 Sep 2010
4 min read
Creating custom container images Containers can do more than just act as placeholders for module content. By expanding the container skin to include images we can create more elaborate containers limited only by our creativity. Styling a DNN container using images is a two-part process. First we must create the images we need and save them as a set of graphic files. Then we must design the container to position the images around the container. In this recipe we will see how to create the custom images for styling a container. In the next recipe Styling a container with images we'll see how to position these images to create the final container. Getting ready Here is a diagram that shows what the final container will look like. We need to create the five images highlighted in red. As you can see in this diagram, we need only create images for the distinctive elements. CSS can fill in the gaps by repeating the background image as necessary to fit the size of the container. Creating images for a container is a four-step process: Decide which sides of the container will use images. In this example we will create images for the top and bottom container edges and leave the left and right sides as a simple border line. Decide the size of the images. A container border can be very narrow or very wide. In this example we will create a 27 pixel wide border with the images. Decide which images will tile to support variable-sized containers. As containers can stretch or shrink, we need to use an image that can tile. That is, the image can be repeated as many times as necessary to fill the width of the container. In this example, we will pick one image to tile across the top and another to tile along the bottom. As the sides will be just a border line we won't need to worry about tiling vertically. Choose a drawing application (such as MS Paint) to create the images and save them to a temporary folder. You can use a variety of formats—in this example we will save the images as GIF files to save space. How to do it... Start by launching your favorite drawing application (such as MS Paint). Set the image dimensions to 98 pixels wide and 27 pixels high. There's nothing special about these dimensions, they just look good for this particular design. Draw the image for the upper-left corner of the container. This image will be the background for the title of the module, so it needs to be an image that can tile horizontally as the length of a container title varies. Save the image as a GIF file called ContainerUL.gif. Next, create a new image with dimensions 80 by 27 pixels. This image marks the end of the module title and the beginning of the top container border. It does not tile and is used in this example to connect the upper-left corner to the border image that runs across the top of the container. Save the image as a GIF file called ContainerUC1.gif. The next image is the top border of the container and will tile horizontally as the width of the container changes. Set the attributes to be 12 by 27 pixels. Save the image as a GIF file called ContainerUC2.gif. For the border along the bottom of the container, we need two more images. The first is the lower-left corner. Make this image 90 by 27. This image will tile across the bottom for the width of the container. Save the image as a GIF file called ContainerLL.gif. The last image is for the lower-right corner. This image doesn't tile. Make it 100 by 27 pixels. 12. Save the image as a GIF file called ContainerLR.gif. At this point you should have five images in a temporary folder that look something like this: (Move the mouse over the image to enlarge.) There's more... In this recipe we saw how to create images for a container and a little bit on how the images can form the borders of a container. In the next recipe we will see how to create the container that will use these images.
Read more
  • 0
  • 0
  • 2918

article-image-human-interactions-bpel
Packt
08 Sep 2010
13 min read
Save for later

Human Interactions in BPEL

Packt
08 Sep 2010
13 min read
(For more resources on BPEL, SOA and Oracle see here.) Human interactions in business processes The main objective of BPEL has been to standardize the process automation. BPEL business processes make use of services and externalize their functionality as services. BPEL processes are defined as a collection of activities through which services are invoked. BPEL does not make a distinction between services provided by applications and other interactions, such as human interactions, which are particularly important. Real-world business processes namely often integrate not only systems and services, but also humans. Human interactions in business processes can be very simple, such as approval of certain tasks or decisions, or complex, such as delegation, renewal, escalation, nomination, chained execution, and so on. Human interactions are not limited to approvals and can include data entries, process monitoring and management, process initiation, exception handling, and so on. Task approval is the simplest and probably the most common human interaction. In a business process for opening a new account, a human interaction might be required to decide whether the user is allowed to open the account. In a travel approval process, a human might approve the decision from which airline to buy the ticket (as shown in the following figure). If the situation is more complex, a business process might require several users to make approvals, either in sequence or in parallel. In sequential scenarios, the next user often wants to see the decision made by the previous user. Sometimes, particularly in parallel human interactions, users are not allowed to see the decisions taken by other users. This improves the decision potential. Sometimes one user does not even know which other users are involved, or whether any other users are involved at all. A common scenario for involving more than one user is workflow with escalation. Escalation is typically used in situations where an activity does not fulfill a time constraint. In such a case, a notification is sent to one or more users. Escalations can be chained, going first to the first-line employees and advancing to senior staff if the activity is not fulfilled. Sometimes it is difficult or impossible to define in advance which user should perform an interaction. In this case, a supervisor might manually nominate the task to other employees; the nomination can also be made by a group of users or by a decision-support system. In other scenarios, a business process may require a single user to perform several steps that can be defined in advance or during the execution of the process instance. Even more complex processes might require that one workflow is continued with another workflow. Human interactions are not limited to only approvals; they may also include data entries or process management issues, such as process initiation, suspension, and exception management. This is particularly true for long-running business processes, where, for example, user exception handling can prevent costly process termination and related compensation for those activities that have already been successfully completed. As a best practice for human workflows, it is usually not wise to associate human interactions directly to specific users; it is better to connect tasks to roles and then associate those roles with individual users. This gives business processes greater flexibility, allowing any user with a certain role to interact with the process and enabling changes to users and roles to be made dynamically. To achieve this, the process has to gain access to users and roles, stored in the enterprise directory, such as LDAP (Lightweight Directory Access Protocol). Workflow theory has defined several workflow patterns, which specify the abovedescribed scenarios in detail. Examples of workflow patterns include sequential workflow, parallel workflow, workflow with escalation, workflow with nomination, ad-hoc workflow, workflow continuation, and so on. Human Tasks in BPEL So far we have seen that human interaction in business processes can get quite complex. Although BPEL specification does not specifically cover human interactions, BPEL is appropriate for human workflows. BPEL business processes are defined as collections of activities that invoke services. BPEL does not make a distinction between services provided by applications and other interactions, such as human interactions. There are mainly two approaches to support human interactions in BPEL. The first approach is to use a human workflow service. Several vendors today have created workflow services that leverage the rich BPEL support for asynchronous services. In this fashion, people and manual tasks become just another asynchronous service from the perspective of the orchestrating process and the BPEL processes stay 100% standard. The other approach has been to standardize the human interactions and go beyond the service invocations. This approach resulted in the workflow specifications emerging around BPEL with the objective to standardize the explicit inclusion of human tasks in BPEL processes. The BPEL4People specification has emerged, which was originally put forth by IBM and SAP in July 2005. Other companies, such as Oracle, Active Endpoints, and Adobe joined later. Finally, this specification is now being advanced within the OASIS BPEL4People Technical Committee. The BPEL4People specification contains two parts: BPEL4People version 1.0, which introduces BPEL extensions to address human interactions in BPEL as a first-class citizen. It defines a new type of basic activity, which uses human tasks as an implementation, and allows specifying tasks local to a process or use tasks defined outside of the process definition. BPEL4People is based on the WS-HumanTask specification that it uses for the actual specification of human tasks. Web Services Human Task (WS-HumanTask) version 1.0 introduces the definition of human tasks, including their properties, behavior, and a set of operations used to manipulate human tasks. It also introduces a coordination protocol in order to control autonomy and lifecycle of service-enabled human tasks in an interoperable manner. The most important extensions introduced in BPEL4People are people activities and people links. People activity is a new BPEL activity used to define user interactions; in other words, tasks that a user has to perform. For each people activity, the BPEL server must create work items and distribute them to users eligible to execute them. People activities can have input and output variables and can specify deadlines. To specify the implementation of people activities, BPEL4People introduced tasks. Tasks specify actions that users must perform. Tasks can have descriptions, priorities, deadlines, and other properties. To represent tasks to users, we need a client application that provides a user interface and interacts with tasks. It can query available tasks, claim and revoke them, and complete or fail them. To associate people activities and the related tasks with users or groups of users, BPEL4People introduced people links. People links are somewhat similar to partner links; they associate users with one or more people activities. People links are usually associated with generic human roles, such as process initiator, process stakeholders, owners, and administrators. The actual users that are associated with people activities can be determined at design time, deployment time, or runtime. BPEL4People anticipates the use of directories such as LDAP to select users. However, it doesn't define the query language used to select users. Rather, it foresees the use of LDAP filters, SQL, XQuery, or other methods. BPEL4People proposes complex extensions to the BPEL specification. However, so far it is still quite high level and doesn't yet specify the exact syntax of the new activities mentioned above. Until the specification becomes more concrete, we don't expect vendors to implement the proposed extensions. But while BPEL4People is early in the standardization process, it shows a great deal of promise. The BPEL4People proposal raises an important question: Is it necessary to introduce such complex extensions to BPEL to cover user interactions? Some vendor solutions model user interactions as just another web service, with well-defined interfaces for both BPEL processes and client applications. This approach does not require any changes to BPEL. To become portable, it would only need an industry-wide agreement on the two interfaces. And, of course, both interfaces can be specified with WSDL, which gives developers great flexibility and lets them use practically any environment, language, or platform that supports Web Services. Clearly, a single standard approach has not yet been adopted for extending BPEL to include Human Tasks and workflow services. However, this does not mean that developers cannot use BPEL to develop business processes with user interactions. Human Task integration with BPEL To interleave user interactions with service invocations in BPEL processes we can use a workflow service, which interacts with BPEL using standard WSDL interfaces. This way, the BPEL process can assign user tasks and wait for responses by invoking the workflow service using the same syntax as for any other service. The BPEL process can also perform more complex operations such as updating, completing, renewing, routing, and escalating tasks. After the BPEL process has assigned tasks to users, users can act on the tasks by using the appropriate applications. The applications communicate with the workflow service by using WSDL interfaces or another API (such as Java) to acquire the list of tasks for selected users, render appropriate user interfaces, and return results to the workflow service, which forwards them to the BPEL process. User applications can also perform other tasks such as reassign, escalate, route, suspend, resume, and withdraw. Finally, the workflow service may allow other communication channels, such as e-mail and SMS, as shown in the following figure: Oracle Human Workflow concepts Oracle SOA Suite 11g provides the Human Workflow component, which enables including human interaction in BPEL processes in a relatively easy way. The Human Workflow component consists of different services that handle various aspects of human interaction with business process and expose their interfaces through WSDL; therefore, BPEL processes invoke them just like any other service. The following figure shows the overall architecture of the Oracle Workflow services: As we can see in the previous figure, the Workflow consists of the following services: Task Service exposes operations for task state management, such as operations to update a task, complete a task, escalate a task, reassign a task, and so on. When we add a human task to the BPEL process, the corresponding partner link for the Task Service is automatically created. Task Assignment Service provides functionality to route, escalate, reassign tasks, and more. Task Query Service enables retrieving the task list for a user based on a search criterion. Task Metadata Service enables retrieving the task metadata. Identity Service provides authentication and authorization of users and lookup of user properties and privileges. Notification Service enables sending of notifications to users using various channels (e-mail, voice message, IM, SMS, and so on). User Metadata Service manages metadata, related to workflow users, such as user work queues, preferences, and so on. Runtime Configuration Service provides functionality for managing metadata used in the task service runtime environment. Evidence Store Service supports management of digitally-signed workflow tasks. BPEL processes use the Task Service to assign tasks to users. More specifically, tasks can be assigned to: Users: Users are defined in an identity store configured with the SOA infrastructure. Groups: Groups contain individual users, which can claim a task and act upon it. Application roles: Used to logically group users and other roles. These roles are application specific and are not stored in the identity store. Assigning tasks to groups or roles is more flexible, as every user in a certain group (role) can review the task to complete it. Oracle SOA Suite 11g provides three methods for assigning users, groups, and application roles to tasks: Static assignment: Static users, groups, or application roles can be assigned at design time. Dynamic assignment: We can define an XPath expression to determine the task participant at runtime. Rule-based assignment: We can create a list of participants with complex expressions. Once the user has completed the task, the BPEL process receives a callback from the Task Service with the result of the user action. The BPEL process continues to execute. The Oracle Workflow component provides several possibilities regarding how users can review the tasks that have been assigned to them, and take the corresponding actions. The most straightforward approach is to use the Oracle BPM Worklist application. This application comes with Oracle SOA Suite 11g and allows users to review the tasks, to see the task details, and to select the decision to complete the task. If the Oracle BPM Worklist application is not appropriate, we can develop our own user interface in Java (using JSP, JSF, Swing, and so on) or almost any other environment that supports Web Services (such as .NET for example). In this respect, the Workflow service is very flexible and we can use a portal, such as Oracle Portal, a web application, or almost any other application to review the tasks. The third possibility is to use e-mail for task reviews. We use e-mails over the Notification service. Workflow patterns To simplify the development of workflows, Oracle SOA Suite 11g provides a library of workflow patterns (participant types). Workflow patterns define typical scenarios of human interactions with BPEL processes. The following participant types are supported: Single approver: Used when a participant maps to a user, group, or role. Parallel: Used if multiple users have to act in parallel (for example, if multiple users have to provide their opinion or vote). The percentage of required user responses can be specified. Serial: Used if multiple users have to act in a sequence. A management chain or a list of users can be specified. FYI (For Your Information): Used if a user only needs to be notified about a task, but a user response is not required. With these, we can realize various workflow patterns, such as: Simple workflow: Used if a single user action is required, such as confirmation, decision, and so on. A timeout can also be specified. Simple workflow has two extension patterns: Escalation: Provides the ability to escalate the task to another user or role if the original user does not complete the task in the specified amount of time. Renewal: Provides the ability to extend the timeout if the user does not complete the task in the specified time. Sequential workflow: Used if multiple users have to act in a sequence. A management chain or a list of users can be specified. Sequential workflow has one extension pattern: Escalation: Same functionality as above. Parallel workflow: Used if multiple users have to act in parallel (for example, if multiple users have to provide their opinion or vote). The percentage of required user responses can be specified. This pattern has an extension pattern: Final reviewer: Is used when the final review has to act after parallel users have provided feedback. Ad-hoc (dynamic) workflow: Used to assign the task to one user, who can then route the task to other user. The task is completed when the user does not route it forward. FYI workflow: Used if a user only needs to be notified about a task, but a user response is not required. Task continuation: Used to build complex workflow patterns as a chain of simple patterns (those described above).
Read more
  • 0
  • 0
  • 3481

article-image-deploying-and-exploring-skin-objects-using-dotnetnuke
Packt
08 Sep 2010
7 min read
Save for later

Deploying and Exploring Skin Objects using DotNetNuke

Packt
08 Sep 2010
7 min read
Deploying your skins and containers Like a module, skins must be packaged and deployed to your DNN server. Once deployed, they will appear on the Skins page under the Admin menu for preview and applying to the portal. In previous article, we saw how to create new skins directly on a test DNN portal. This allows you to edit the files and immediately see the results just by refreshing your browser. But once a skin has been fully developed, this recipe will show you how to package your skin into a portable ZIP file using the package wizard. Once created, you can install the skin package on the DNN site by logging in as SuperUser then uploading the ZIP file and installing the skin package as an extension. The skin is installed in the Portals_defaultSkins folder. As of DNN 5.x, only the SuperUser can deploy skins. This was done as a security precaution as skins can now contain user-written code. Getting ready For this recipe you will need skin files to deploy. We will use the skin files from the previous article, from the recipe: Creating a simple ASCX skin. How to do it... Begin by logging in as the SuperUser. Look under the Host menu and select Extensions. The first step is to look under the installed extensions and find the SampleASCXSkin under the Skins section. If you see it, skip to step 8, otherwise you must create the extension first. Start by selecting Create Extension from the action menu (or click on the link at the bottom of the page). Choose Skin from Extension Type drop down and fill out the form as shown: (Move the mouse over the image to enlarge.) Click on Next. Provide the owner information and click on Next. The SampleASCXSkin skin will now appear in the list of extensions. Click on the Edit icon next to the name. Scroll to the bottom and click on the link Create Package. The Create Package screen will display. Click on Next. On the next screen you will see the files of the skin. Click on Next. Next, the manifest is displayed. Click on Next. The wizard will now generate a file name for the ZIP file using the owner name and the skin name. Click on Next. The wizard will now complete with a summary page. You will now find a ZIP file in the /Install/Skin folder. Click on Return to close the wizard. Once the file is packaged in a ZIP file you can upload and install it. Exploring Skin Objects In this recipe we will expand the sample ASCX skin and demonstrate additional Skin Objects you can use. Getting ready To follow along with this recipe you must have completed the following recipe: Creating a simple ASCX skin Here is a diagram of the new page layout we will create: How to do it... Start by locating the default skin folder within your test DNN portal (Portals_defaultSkins). Next, create a new folder to hold the skin files. Call this folder DetailedASCXSkin. Launch the Development Tool. Open the SampleASCXSkin.ascx file from the Creating a simple ASCX skin recipe. Add the following register directive after the existing directives: <%@ Register TagPrefix="dnn" TagName="BREADCRUMB" Src="~/Admin/Skins/BreadCrumb.ascx" %> <%@ Register TagPrefix="dnn" TagName="TEXT" Src="~/Admin/Skins/Text.ascx" %> <%@ Register TagPrefix="dnn" TagName="LINKS" Src="~/Admin/Skins/Links.ascx" %> <%@ Register TagPrefix="dnn" TagName="COPYRIGHT" Src="~/Admin/Skins/Copyright.ascx" %> <%@ Register TagPrefix="dnn" TagName="CURRENTDATE" Src="~/Admin/Skins/CurrentDate.ascx" %> Next, replace the existing Login_Style DIV with a new DIV encompassing both Login and User links: <%--By placing the SkinObjects inside a parent DIV they share the same style--%> <div class="bread_bg"> <div id="bread_style"> <dnn:TEXT runat="server" id="dnnTEXT" CssClass="breadcrumb_text" Text="You are here" ResourceKey="Breadcrumb" /> &nbsp;<span> <dnn:BREADCRUMB runat="server" id="dnnBREADCRUMB" CssClass="Breadcrumb" RootLevel="0" Separator="&nbsp;>&nbsp;" /> </span> </div> <div id="login_style" class="user"> <dnn:USER runat="server" id="dnnUSER" CssClass="user" /> &nbsp;&nbsp;|&nbsp;&nbsp; <dnn:LOGIN runat="server" id="dnnLOGIN" CssClass="user" /> </div> </div> Lastly, leave the ContentPane DIV as it is and add these new DIVs right below it: <div class="bot_bg links"> <dnn:LINKS runat="server" id="dnnLINKS" CssClass="links" Level="Root" Separator="&nbsp;&nbsp;&nbsp;| &nbsp;&nbsp;&nbsp;" /> </div> <div class="bot_pad"> <div id="copy_style" class="footer"> <dnn:COPYRIGHT runat="server" id="dnnCOPYRIGHT" Align CssClass="footer" /> </div> <div id="date_style" class="footer"> <dnn:CURRENTDATE runat="server" id="dnnCURRENTDATE" Align CssClass="footer" /> </div> </div> Separating the Copyright and Date into separate DIVs allow us to put one in the lower-left corner and one in the lower-right corner of the page. Select Save SampleASCXSkin.ascx As… from the File menu. Browse to the DetailedASCXSkin folder and save the file as DetailedASCXSkin.ascx. These new Skin Objects will need styles. Open the skin.css file from the Creating a simple ASCX skin recipe. Add the following styles to the bottom of the skin.css file: /*breadcrumbs*/ .Breadcrumb, a.Breadcrumb:link, a.Breadcrumb:active, a.Breadcrumb:visited {color: #800000; font-size: 13px;} a.Breadcrumb:hover {color: #C00;} .bread_bg {background-color: #EAEAEA; fontfamily: Verdana, Arial, Helvetica, sans-serif; border-left: solid 1px #EAEAEA; border-right: solid 1px #EAEAEA; margin: 0 5px 0 5px; height: 30px;} #bread_style {float: left; padding: 10px 0px 0px 17px; color: #000000; font-size: 13px;} /* for the bottom of the page */ .bot_bg {background-color: #EAEAEA; border-left: solid 1px #EAEAEA;border-right: solid 1px #EAEAEA; border-bottom: solid 1px #EAEAEA; padding: 10px; margin: 0 5px 0 5px;} .bot_pad {font-family: Verdana, Arial, Helvetica, sans-serif; margin-bottom: 20px; padding: 0 30px 0 20px;} #terms_style {float: left;} #copy_style {float: left;} #date_style {float: right; padding: 0px 0px 0px 10px; } /*links*/ .links {text-align: center;} .links, a.links:link, a.links:active, a.links:visited {fontweight: bold; color: #800000; font-size: 11px;} a.links:hover {color: #C00;} /* Sets the font size and text color of the footer at the bottom of the page */ .footer, a.footer:link, a.footer:active, a.footer:visited {color: #800000; font-size: 12px;} a.footer:hover {color: #C00;} With these styles we are positioning the Breadcrumb links just below the menu on the left. The Login and User links remain on the right side, ContentPane and RightPane stay the same, and the new Links, Copyright, and CurrentDate controls are arranged along the bottom of the page. Select Save skin.css As… from the File menu. Browse to the DetailedASCXSkin folder and save the file as skin.css. To see how the new skin will look on the site, log in as the SuperUser. Look under the Admin menu and select Skins. The new DetailedASCXSkin will now appear: Click on Preview to see how the skin looks: See how the breadcrumbs (You are here:) appear on the left under the menu, user and login are on the right, and links along the bottom and the copyright message and current date appear in the bottom corners. There's more... If you're curious to see which Skin Objects are available on your DNN site, you can log in as the SuperUser and select Extensions under the Host menu. This will list all the skin objects and their version number. Some skin objects may not be installed by default and will not appear in this list until installed.
Read more
  • 0
  • 0
  • 1746

article-image-creating-simple-skin-using-dotnetnuke
Packt
08 Sep 2010
5 min read
Save for later

Creating a Simple Skin using DotNetNuke

Packt
08 Sep 2010
5 min read
Introduction In DNN, skinning is a term that refers to the process of customizing the look and feel of the DNN portal. One of the powerful features of DNN is that the functionality of the portal is separated from the presentation of the portal. This means we can change the appearance of the portal without affecting how the portal works. To create a skin in DNN we will work with three kinds of files: HTML, ASCX, and CSS. The HTML or ASCX file describes the layout of the page and the CSS file provides the styling. If you have worked with HTML and CSS before than you will be able to immediately get started. However, if you are familiar with ASCX (and as a DNN developer that is likely) you can achieve the same results faster than HTML. In the recipes, we will show primarily ASCX skinning with some brief examples of HTML skinning. Skin Objects Before we start looking at the recipes, we need a quick word about Skin Objects. Skin Objects are used in both HTML and ASCX skin files as placeholders for different kinds of dynamic functionality. In HTML skins, you place text tokens such as [CURRENTDATE] in your code and when the code is parsed by the skin engine it will insert the matching skin object. If you are working in ASCX, you register skin objects as controls that you place directly in your code. DNN offers many different skin objects such as CurrentDate, Logo, Login link, and others and we'll see many of these in action in the recipes of this article. Downloading and installing a skin Often the easiest way to start skinning is to download an existing skin package and see the different files used for skinning. In this recipe we will download an excellent skin created by Jon Henning from a site called CodePlex that demonstrates the most common skin objects and layouts. Another reason for starting with an existing skin is that it allows incremental development. We can start with a fully functional skin, deploy it to our DNN portal and then edit the source files right on the server. In this way the changes we make are immediately displayed and problems are easily spotted and fixed. However, as applying a skin can affect the entire site, it is best to create and test skins on a development DNN site before using them on a production site. Finally, it should also be noted that as a skin is really just another type of extension in DNN, you are already familiar with some of these steps. How to do it... Open your favorite web browser and go to the site http://codeendeavortemplate.codeplex.com/. Click on Downloads in the toolbar. Scroll down a little and click on the CodeEndeavors Template Skin link. When prompted with the License Agreement, click I Agree. The File download dialog will ask if you want to Open or Save. Click on Save and select a temporary folder to hold the ZIP file. That's all we need from the CodePlex site, so close the browser. To install the skin on the DNN site, begin by logging in as the SuperUser. Look at the Control Panel and make sure you're in Edit mode. Look under the Host menu and select Extensions. Scroll to the bottom and click on the link Install Extension Wizard. The wizard will prompt for the ZIP file (called the extension package). Click on the Browse button and select the file you just downloaded (for example CodeEndeavors.TemplateSkin.install.v01.01.07.00.zip). Click on Open then click on Next. The wizard will display the Extension information. Click on Next. The wizard will display the Release Notes. Click on Next. On the license page, check Accept License? and click on Next. Now the install script will run, creating the skin. At the end you should see the message "Installation successful". Click on Return. To make the skin active, select Skins under the Admin menu. (Move the mouse over the image to enlarge.) From the Skins drop-down lists, select CodeEndeavors.TemplateSkin. For this article, we will use the Index skin for our examples. Click on the Apply link under the index skin to make it active. To see the skin files, you can look in the root folder of the DNN instance under Portals_defaultSkinsCodeEndeavors.TemplateSkin. Here is a summary of the key files you are likely to see in a skin like this: File name Description animated.ascx An ASCX skin file. container.ascx An ASCX container file. index.html An HTML skin file. skin.css The stylesheet for the skin. container.css The stylesheet for the container. TemplateSkin.dnn The manifest file for the skin package. thumbnail_animated.jpg A preview image of the ASCX skin. thumbnail_container.jpg A preview image of the ASCX container. thumbnail_index.jpg A preview image of the HTML skin. license.txt The text of the license agreement. releasenotes.txt The text of the release notes. version.txt The version number. Images folder A folder holding the graphic images supporting a skin or container.
Read more
  • 0
  • 0
  • 3143
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 $19.99/month. Cancel anytime
article-image-debugging-mechanisms-oracle-sql-developer-21-using-plsql
Packt
08 Sep 2010
7 min read
Save for later

Debugging mechanisms in Oracle SQL Developer 2.1 using Pl/SQL

Packt
08 Sep 2010
7 min read
Once your PL/SQL code has successfully compiled, it is important to review it to make sure it does what is required and that it performs well. You can consider a number of approaches when tuning and testing code. These approaches include: Debugging—run the code and add break points to stop and inspect areas of concern. SQL performance—use Explain Plan results to review the performance. PL/SQL performance—use the PL/SQL Hierarchical Profiler to identify bottlenecks. Unit testing—review edge cases and general function testing. Does the code do what you intended it to do? In this article by Sue Harper, author of Oracle SQL Developer 2.1, we'll review the debugger. We will see how to debug PL/SQL packages, procedures, and functions. Debugging PL/SQL code SQL and PL/SQL code may execute cleanly, and even produce an output. PL/SQL code may compile and produce results, but this is part of the task. Does it do what you are expecting it to do? Are the results accurate? Does it behave as expected for high and low values, odd dates or names? Does it behave the same way when it's called from within a program as it does when tested in isolation? Does it perform as well for massive sets of data as it does for a small test case? All of these are aspects to consider when testing code, and many can been tracked by debugging the code. Using the debugging mechanism in SQL Developer You will need a piece of compiled, working code. For this exercise, we will use the following piece of code: PROCEDURE EMP_DEPTS(P_MAXROWS VARCHAR2)ASCURSOR EMPDEPT_CURSOR ISSELECT D.DEPARTMENT_NAME, E.LAST_NAME, J.JOB_TITLEFROM DEPARTMENTS D, EMPLOYEES E, JOBS JWHERE D.DEPARTMENT_ID = E.DEPARTMENT_IDAND E.JOB_ID = J.JOB_ID;EMP_RECORD EMPDEPT_CURSOR % ROWTYPE;TYPE EMP_TAB_TYPE IS TABLE OF EMPDEPT_CURSOR % ROWTYPE INDEX BYBINARY_INTEGER;EMP_TAB EMP_TAB_TYPE;I NUMBER := 1;BEGINOPEN EMPDEPT_CURSOR;FETCH EMPDEPT_CURSORINTO EMP_RECORD;EMP_TAB(I) := EMP_RECORD;WHILE((EMPDEPT_CURSOR % FOUND) AND(I <= P_MAXROWS))LOOP I := I + 1;FETCH EMPDEPT_CURSORINTO EMP_RECORD;EMP_TAB(I) := EMP_RECORD;END LOOP;CLOSE EMPDEPT_CURSOR; FOR J IN REVERSE 1 .. ILOOP DBMS_OUTPUT.PUT_LINE('THE EMPLOYEE '|| EMP_TAB(J).LAST_NAME ||' WORKS IN DEPARTMENT '|| EMP_TAB(J).DEPARTMENT_NAME);END LOOP;END; Before you can debug code, you need to have the following privileges: EXECUTE and DEBUG—you need to be able to execute the required procedure DEBUG CONNECT SESSION—to be able to debug procedures you execute in the session Note, when granting the system privilege DEBUG ANY PROCEDURE, you are granting access to debug any procedure that you have execute privilege for and has been compiled for debug. Using the Oracle debugging packages Oracle provides two packages for debugging PL/SQL code. The first, DBMS_DEBUG, was introduced in Oracle 8i and is not used by newer IDEs. The second, DBMS_DEBUG_JWP, was introduced in Oracle 9i Release 2, and is used in SQL Developer when debugging sub-programs. Debugging When preparing to debug any code, you need to set at least one break point, and then you should select Compile for Debug. In the following screenshot, the breakpoint is set at the opening of the cursor, and the Compile for Debug option is shown in the drop-down list: Instead of using the drop-down list to select the Compile or Compile for Debug options, just click the Compile button. This compiles the PL/SQL code using the optimization level set in the Preferences. Select Database PL/SQL Compiler|. By setting the Optimization Level preference to 0 or 1 the PL/SQL is compiled with debugging information. Any PL/SQL code that has been compiled for debugging will show the little green bug overlaying the regular icon in the Connections navigator. The next screenshot shows the EMP_DEPTS procedure and the GET_SALARY function have both been compiled for debug: Compile for debug Once you have completed a debugging session, be sure to compile again afterwards to remove any debug compiler directives. While negligible, omitting this step can have a performance impact on the PL/SQL program. You are now ready to debug. To debug, click the Debug button in the toolbar. SQL Developer then sets the sessions to a debug session and issues the command DBMS_DEBUG_JDWP.CONNECT_TCP (hostname, port); and sets up the debug windows as shown in the following screenshot: This connects you to a debugger session in the database. In some instances, the port selected is not open, due to firewall or other restrictions. In this case, you can have SQL Developer prompt you for the port. To set this option, open the Preferences dialog, and select the Debugger node. You can also specify the port range available for SQL Developer to use. These options mean that you can have more control over the ports used. Navigating through the code The PL/SQL debugger provides a selection of buttons (or menu items) to step through individual lines of code, or to step over blocks of code. You can step through or over procedures, navigating to the point of contention or the area you wish to inspect. Once you start stepping into the code, you can track the data as it changes. The data is displayed in a second set of tabbed dialogs. In this example, we are looping through a set of records in order for you to see how each of the windows behaves. As you start stepping into the code, the Data tab starts to display the values: This Data tab continues to collect all of the variables as you continue to step through the code. Even if you step over and skip blocks of code, all of the code is executed and the results are gathered here. The Smart Data tab keeps track of the same detail, but only the values immediately related to the area you are working in. This is more useful in a large procedure than in a small one like the example shown. The context menu provides you with a set of options while debugging. These include: Run to Cursor—allows you to start debugging and then to quickly move to another part of the code. The code in-between is quickly executed and you can continue debugging. Watch—allows you to watch an expression or code while you are debugging. Inspect—allows you to watch values as you debug. In the following screenshot, the current execution point is at the start of the WHILE loop. If the loop is required to loop multiple times, you can skip that and have the code execute to a point further down in the code, in this case after the cursor has been completed and closed: The Watch and Inspect options remain set up if you stop and restart the debug session. This allows you to stop, change the input values, and start debugging and these will change according to the new parameters. You do not need to set up watch or inspector values each time you debug the procedure. The values appear in dockable windows, so you can dock or float them near the code as required: You can modify values that you are watching. In the following example, 'i' is the counter that we're using in the loop. You can modify this value to skip over chunks of the loop, and then continue from a particular point. Modifying values in the middle of the code can be useful, as you might want to test how the program reacts in certain circumstances. For example, before the millennium, testers may have wanted to see how code behaved, or output changed once the date switched over to the year 2000.
Read more
  • 0
  • 1
  • 17274

article-image-blender-3d-249-working-textures
Packt
03 Sep 2010
9 min read
Save for later

Blender 3D 2.49: Working with Textures

Packt
03 Sep 2010
9 min read
(For more resources on Blender, see here.) Before we start to dig into textures, let me say that the biggest problem of working with them is actually finding or creating a good texture. That's why it's highly recommended that you start to create your own textures library as soon as possible. Textures are mostly image files, which represent some kind of surface, such as wood or stone, based on the photography. They work like wallpaper, which we can place on a surface or object. For instance, if we place an image of wood on a plane, it will give the impression that the plane is made of wood. That's the main principle of using textures; to make an object look like something in the real world. For some projects, we may need a special kind of texture, which won't be found in a common library, so we will have to take a picture ourselves or buy an image from someone. But don't worry, because often we deal with common surfaces that have common textures as well. Procedural textures vs. Non-procedural textures Blender basically has two types of textures, which are procedural textures and bitmap textures. Each one has positive and negative points; which one is the best? It will depend on your project needs: Procedural: This kind of texture is generated by the software at rendering time, just like vector lines in software, such as Inkscape or Illustrator. This means that it won't depend of any type of image file. The best thing about this type of texture is that being resolution-independent, we can set the texture to be rendered at high resolutions with minimum loss of quality. The negative point of this kind of texture is that it's harder to get realistic textures with it. The advantage of using procedural textures is that because they are all based on algorithms, they don't depend on a fixed number of pixels. Non-Procedural: To use this kind of texture, we will need an image file, such as a JPEG, PNG, or TGA file. The good thing about these textures is that we can quickly achieve a very realistic look and feel with it. On the other hand, we must find the texture file before using it. And what's more, if you are creating a high-resolution render, the texture file size must be as well. Texture library Do you remember the way we organized materials? We can do the exact same thing with textures. Besides setting names and storing the Blender files to import and use again later, collecting bitmap textures is another important point. Even if you don't start right away, it's important to know where to look for textures. So, here is a small list of websites, which provide free texture downloads: http://www.cgtextures.com http://blender-archi.tuxfamily.org/textures Applying textures To use a texture, we must apply a material to an object, and then use the texture with this material. We always use the texture inside a material. For instance, to make a plane that simulates a marble floor, we have to use a texture, and set up how the surface will react to light to give the surface a proper look of marble. To do that, we use the Texture panel, which is located right next to the Materials button. We can use a keyboard shortcut to open this panel; just hit F6 to open it: There is a way to add a texture in the Material panel also, with a menu named Texture: To get all the options, the best way to add a texture is with the Texture panel. In this panel, we will be able to see buttons, which represent the texture channels. Each one of these channels can hold a texture. The final texture will be a mix of all the channels. If we have a texture in channel 1 and another texture in channel 2, these textures will be blended and represented on the material. Before adding a new texture, we must select a channel by clicking on one of them. Usually the first channel will be selected, but if you want to use another one, just click on the channel. When the channel is selected, just click on the Add New button to add a new texture: The texture controls are very similar to the materials controls. We can give a name to the texture at the top and erase the texture if we don't want it anymore. With the selector, we can choose a previously created texture also. Just click and select it: Now, here comes the fun part. With a texture added, we have to choose a texture type. To do that, we click on the Texture Type combo box: There are a lot of textures, but most of them are procedural textures, and we won't use them frequently. The only texture that isn't procedural is the Image type. We see an example of a procedural Wood texture in the following screenshot: We can use textures such as Clouds and Wood to create effects and give surfaces a more complex look, or even create a grass texture with dirt on it. But most of the time, the texture type which we will be using will be the Image type: Each texture has it's own set of parameters to determine how it will look on the object. If we add a Wood texture, it will show the configuration parameters to the right: (Move the mouse over the image to enlarge.) If we choose texture type as Clouds, the parameters shown on the right will be completely different. With the Image texture type, it's not different. This kind of texture has its own type of setup. Following is the control panel: To show how to set up a texture, let's use an image file that represents a wood floor and a plane. We can apply the texture to this plane and set up how it's going to look, testing all parameters: The first thing to do is to assign a material to the plane, and then add a texture to this material. We choose the Image option as texture type. Blender will show the configuration options for this kind of texture. To apply the image as a texture to the plane, just click on the Load button, located in the Image menu. When we hit this button, we will be able to select the image file: Locate the image file, and the texture will be applied. If we want to have more control on how this texture is organized and placed on the plane, we need to learn how the controls work. Every time you make any changes to the setup of a texture, these changes will be shown in the preview window. Use it to make the required changes. Here is a list of what some of the buttons can do for the texture: UseAlpha: If the texture has an alpha channel, we have to press this button for Blender to calculate the channel. An image has an alpha channel when some kind of transparency is stored in the image. For instance, a PNG file with a transparent background has an alpha channel. We can use this to create a texture with a logo, for a bottle, or to add an image of a tree or person, to a plane. Rot90: With this option, we can rotate the texture by 90 degrees. Repeat: Every texture must be distributed on the object surface; repeating the texture in lines and columns is the default way to do that. Extend: If this button is pressed, the texture will be adjusted to fit the entire object surface area. Clip: With this option, the texture will be cropped, and we will be able to show only a part of it. To adjust which parts of the texture will be displayed, use the Min/Max X/Y options. Xrepeat / Yrepeat: This option determines how many times a texture is repeated with the repeat option turned on. Normal Map: If the texture will be used to create Normal Maps, press this button. These are textures used to change the face normals of an object. Still: With this button selected, we will specify that the image used as texture is a still image. This option is marked by default. Movie: If you want to use a movie file as texture, press this button. This is very useful if we need to make something similar to a theater projection screen or a tv screen. Sequence: We can use a sequence of images as a texture too. Just press this button. It works the same way as with a movie file. There are a few more parameters, such as the Reload button. If your texture file is updated outside of Blender, you must press this button to make Blender update the texture in your project. The X button can erase this texture; use it if you need to select another image file. When we add a texture to any material, an external link is created to this file. This link can be absolute or relative. Suppose we add a texture named wood.png, which is located in the root of your primary hard disk, such as C:. A link to this texture will be created like this — c:wood.png. So every time you open this file, the software will look for that file at that exact place. This is an absolute link, but we can use a relative link as well. For instance, when we add a texture located in the same folder as our scene, a relative link will be created. Every time we use an absolute link and we have to move the .blend file to another computer, the texture file must go with it. To imbue the image file with .blend, just press the icon for gift package: To save all the textures used in a scene, just access the File menu and use the Pack Data option. It will cause all the texture files to get embedded with the source .blend file.
Read more
  • 0
  • 0
  • 3825

article-image-examples-mysql-daemon-plugin
Packt
01 Sep 2010
8 min read
Save for later

Examples of MySQL Daemon Plugin

Packt
01 Sep 2010
8 min read
(For more resources on MySQL, see here.) A Hello World! Daemon plugin Now, let's look at our first complete plugin example. This plugin is probably the most basic plugin we can have. It simply prints a message into the MySQL error log when loaded: #include <stdio.h> #include <mysql/plugin.h> #include <mysql_version.h> These are the basic includes required for most Daemon plugins. The most important being mysql/plugin.h, which contains macros and data structures necessary for a MySQL plugin. static int hello_world_plugin_init(void *p) { fprintf(stderr, "Hello World: " "This is a static text daemon example plugin!n"); return 0; } In the plugin initialization function we simply write a message to stderr. MySQL redirects stderr to the error log (if there is one) so our message will end up there. We then return 0 to indicate that the initialization was successful. struct st_mysql_daemon hello_world_info = { MYSQL_DAEMON_INTERFACE_VERSION }; This structure is used for the info part of the plugin declaration. In Daemon plugins it simply contains the API version that this plugin was compiled against. The Daemon plugin API version matches the MySQL server version, which means MySQL Daemon plugins can only be used with a MySQL server version they have been compiled against. Indeed, for a Daemon plugin to do something non-trivial it will invariably need access to the server's internal functions and data structures that change with every MySQL version. Other plugins that are implemented according to a certain functionality API are separated from the server internals and are binary compatible with a wide range of server releases. Having defined all of the functions and auxiliary structures, we can declare a plugin: mysql_declare_plugin(hello_world) { This is a Daemon plugin so we need to specify it as such with this defined constant: MYSQL_DAEMON_PLUGIN, info points to the structure declared earlier. With other plugin types this may contain additional information valuable to the plugin functionality: &hello_world_info, We are calling this plugin "hello_world". This is its name for the INSTALL PLUGIN command and any plugin status: "hello_world", The author string, is useful for providing contact information about the author of the plugin: "Andrew Hutchings (<a href="Andrew.Hutchings@Sun.COM" target="_blank">Andrew.Hutchings@Sun.COM)", A Simple line of text that gives a basic description of what our plugin does: "Daemon hello world example, outputs some static text", This plugin is licensed under GPL so we set the license type to this: PLUGIN_LICENSE_GPL, This is our initialization function that has been defined earlier in the code: hello_world_plugin_init, As our simple plugin does not need a de-initialization function, we put NULL here: NULL, This plugin is given version 1.0 because it is our first GA release of the plugin. In future versions we can increment this: 0x0100, There are no status or system variables in this example. Hence, everything below the version is set to NULL: NULL, NULL, NULL } mysql_declare_plugin_end; We can now install this plugin using the INSTALL PLUGIN syntax Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 2 Server version: 5.1.47 Source distribution Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. mysql> INSTALL PLUGIN hello_world SONAME 'hello_world.so'; Query OK, 0 rows affected (0.00 sec) Going to the error log we see: 090801 22:18:00 [Note] /home/linuxjedi/Programming/Builds/mysql-5.1.47/ libexec/mysqld: ready for connections. Version: '5.1.47' socket: '/tmp/mysql.sock' port: 3306 Source distribution Hello World: This is a static text daemon example plugin! A system and status variables demo plugin Let's see a more complex example. This plugin shows how to create system and status variables. It has one global system variable and one status variable, both defined as long long. When you set the global system variable, its value is copied into the status variable. #include <stdio.h> #include <mysql/plugin.h> #include <mysql_version.h> long long system_var = 0; long long status_var = 0; struct st_mysql_show_var vars_status_var[] = { {"vars_status_var", (char *) &status_var, SHOW_LONGLONG}, {0, 0, 0} }; We have one status variable in this plugin called vars_status_var which is bound to the status_var variable defined near the top of this source code. We are defining this variable as long long so we use the SHOW_LONGLONG type. int sysvar_check(MYSQL_THD thd, struct st_mysql_sys_var *var, void *save, struct st_mysql_value *value) { This function is to be called before our system variable is updated. A plugin is not required to provide it but it can be used to check if the data entered is valid and, as an example, we will only allow values that are not too close to status_var. long long buf; value->val_int(value, &buf); First we retrieve the new value-to-be and store it in buf. *(longlong*) save = buf; We then set save to the contents of buf, so that the update function could access it and store the value in our system_var variable. If we do not implement our own sysvar_check() function for our system variable, MySQL will provide a default one that performs all of the above (but nothing of the following). if (buf * 2 < status_var || buf > status_var * 3) return 0; else return 1; } This is our special condition. In this example we allow an update only if the new value is either less than a half of or three times bigger than the value of status_var. We return 0 when the new value is valid, and an update should be allowed, and 1 when an update should be canceled. In our update function we copy the value of the system_var to a status_var, to see how its value changes in SHOW STATUS and to get a different range on valid values for the system_var on every update. Note that the update function cannot return a value. It is not supposed to fail! void sysvar_update(MYSQL_THD thd, struct st_mysql_sys_var *var, void *var_ptr, const void *save) { system_var = *(long long *)save; status_var = system_var; } We update our system_var variable without any mutex protection, even though many threads may try to execute the SET statement at the same time. Nevertheless, it is safe. MySQL internally guards all accesses to global system variables with a mutex, which means we do not have to. MYSQL_SYSVAR_LONGLONG(vars_system, system_var, 0, "A demo system var", sysvar_check, sysvar_update, 0, 0, 123456789, 0); This is the declaration for our system variable. It is a long long and is called vars_system. In fact as this is a variable for the vars plugin, the full name will be vars_vars_system in SHOW VARIABLES. It is associated with the system_var variable in the code, has the check function sysvar_check() and an update function sysvar_update() as defined above, and it can only take values between 0 and 123456789. struct st_mysql_sys_var* vars_system_var[] = { MYSQL_SYSVAR(vars_system), NULL }; This is the structure which stores all system variables to be passed to the declaration for this plugin. As we only have one variable we shall only include that. struct st_mysql_daemon vars_plugin_info= { MYSQL_DAEMON_INTERFACE_VERSION }; mysql_declare_plugin(vars) { MYSQL_DAEMON_PLUGIN, &vars_plugin_info, "vars", "Andrew Hutchings", "A system and status variables example", PLUGIN_LICENSE_GPL, NULL, NULL, 0x0100, vars_status_var, vars_system_var, NULL } mysql_declare_plugin_end; This is very similar to the declaration of our first plugin, but this one has structures for the status variables and system variable listed. When putting our new plugin into action we should see the following: mysql> INSTALL PLUGIN vars SONAME 'vars.so'; Query OK, 0 rows affected (0.00 sec) mysql> SHOW STATUS LIKE 'vars_%'; +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | vars_status_var | 0 | +-----------------+-------+ 1 row in set (0.00 sec) mysql> SHOW VARIABLES LIKE 'vars_%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | vars_vars_system | 0 | +------------------+-------+ 1 row in set (0.00 sec) Our status and system variables are both set to 0 by default. mysql> SET GLOBAL vars_vars_system=2384; Query OK, 0 rows affected (0.00 sec) mysql> SHOW STATUS LIKE 'vars_%'; +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | vars_status_var | 2384 | +-----------------+-------+ 1 row in set (0.00 sec) mysql> SHOW VARIABLES LIKE 'vars_%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | vars_vars_system | 2384 | +------------------+-------+ 1 row in set (0.00 sec) Setting our system variable to 2384 has altered both the system variable and the status variable, so we have success! mysql> SET GLOBAL vars_vars_system=2383; ERROR 1210 (HY000): Incorrect arguments to SET Our special check function works too. The variable cannot be updated to a value that is too close to its old value!
Read more
  • 0
  • 0
  • 2925

article-image-python-multimedia-fun-animations-using-pyglet
Packt
31 Aug 2010
8 min read
Save for later

Python Multimedia: Fun with Animations using Pyglet

Packt
31 Aug 2010
8 min read
(For more resources on Python, see here.) So let's get on with it. Installation prerequisites We will cover the prerequisites for the installation of Pyglet in this section. Pyglet Pyglet provides an API for multimedia application development using Python. It is an OpenGL-based library, which works on multiple platforms. It is primarily used for developing gaming applications and other graphically-rich applications. Pyglet can be downloaded from http://www.pyglet.org/download.html. Install Pyglet version 1.1.4 or later. The Pyglet installation is pretty straightforward. Windows platform For Windows users, the Pyglet installation is straightforward—use the binary distribution Pyglet 1.1.4.msi or later. You should have Python 2.6 installed. For Python 2.4, there are some more dependencies. We won't discuss them in this article, because we are using Python 2.6 to build multimedia applications. If you install Pyglet from the source, see the instructions under the next sub-section, Other platforms. Other platforms The Pyglet website provides a binary distribution file for Mac OS X. Download and install pyglet-1.1.4.dmg or later. On Linux, install Pyglet 1.1.4 or later if it is available in the package repository of your operating system. Otherwise, it can be installed from source tarball as follows: Download and extractthetarballextractthetarball the tarball pyglet-1.1.4.tar.gz or a later version. Make sure that python is a recognizable command in shell. Otherwise, set the PYTHONPATH environment variable to the correct Python executable path. In a shell window, change to the mentioned extracted directory and then run the following command: python setup.py install Review the succeeding installation instructions using the readme/install instruction files in the Pyglet source tarball. If you have the package setuptools (http://pypi.python.org/pypi/setuptools) the Pyglet installation should be very easy. However, for this, you will need a runtime egg of Pyglet. But the egg file for Pyglet is not available at http://pypi.python.org. If you get hold of a Pyglet egg file, it can be installed by running the following command on Linux or Mac OS X. You will need administrator access to install the package: $sudo easy_install -U pyglet Summary of installation prerequisites Package Download location Version Windows platform Linux/Unix/OS X platforms Python http://python.org/download/releases/ 2.6.4 (or any 2.6.x) Install using binary distribution Install from binary; also install additional developer packages (For example, with python-devel in the package name in a rpm-based Linux distribution).   Build and install from the source tarball. Pyglet http://www.pyglet.org/download.html 1.1.4 or later Install using binary distribution (the .msi file) Mac: Install using disk image file (.dmg file). Linux: Build and install using the source tarball. Testing the installation Before proceeding further, ensure that Pyglet is installed properly. To test this, just start Python from the command line and type the following: >>>import pyglet If this import is successful, we are all set to go! A primer on Pyglet Pyglet provides an API for multimedia application development using Python. It is an OpenGL-based library that works on multiple platforms. It is primarily used for developing gaming and other graphically-rich applications. We will cover some important aspects of Pyglet framework. Important components We will briefly discuss some of the important modules and packages of Pyglet that we will use. Note that this is just a tiny chunk of the Pyglet framework. Please review the Pyglet documentation to know more about its capabilities, as this is beyond the scope of this article. Window The pyglet.window.Window module provides the user interface. It is used to create a window with an OpenGL context. The Window class has API methods to handle various events such as mouse and keyboard events. The window can be viewed in normal or full screen mode. Here is a simple example of creating a Window instance. You can define a size by specifying width and height arguments in the constructor. win = pyglet.window.Window() The background color for the image can be set using OpenGL call glClearColor, as follows: pyglet.gl.glClearColor(1, 1, 1, 1) This sets a white background color. The first three arguments are the red, green, and blue color values. Whereas, the last value represents the alpha. The following code will set up a gray background color. pyglet.gl.glClearColor(0.5, 0.5, 0.5, 1) The following illustration shows a screenshot of an empty window with a gray background color. Image The pyglet.image module enables the drawing of images on the screen. The following code snippet shows a way to create an image and display it at a specified position within the Pyglet window. img = pyglet.image.load('my_image.bmp')x, y, z = 0, 0, 0img.blit(x, y, z) A later section will cover some important operations supported by the pyglet.image module. Sprite This is another important module. It is used to display an image or an animation frame within a Pyglet window discussed earlier. It is an image instance that allows us to position an image anywhere within the Pyglet window. A sprite can also be rotated and scaled. It is possible to create multiple sprites of the same image and place them at different locations and with different orientations inside the window. Animation Animation module is a part of pyglet.image package. As the name indicates, pyglet.image.Animation is used to create an animation from one or more image frames. There are different ways to create an animation. For example, it can be created from a sequence of images or using AnimationFrame objects. An animation sprite can be created and displayed within the Pyglet window. AnimationFrame This creates a single frame of an animation from a given image. An animation can be created from such AnimationFrame objects. The following line of code shows an example. animation = pyglet.image.Animation(anim_frames) anim_frames is a list containing instances of AnimationFrame. Clock Among many other things, this module is used for scheduling functions to be called at a specified time. For example, the following code calls a method moveObjects ten times every second. pyglet.clock.schedule_interval(moveObjects, 1.0/10) Displaying an image In the Image sub-section, we learned how to load an image using image.blit. However, image blitting is a less efficient way of drawing images. There is a better and preferred way to display the image by creating an instance of Sprite. Multiple Sprite objects can be created for drawing the same image. For example, the same image might need to be displayed at various locations within the window. Each of these images should be represented by separate Sprite instances. The following simple program just loads an image and displays the Sprite instance representing this image on the screen. 1 import pyglet23 car_img= pyglet.image.load('images/car.png')4 carSprite = pyglet.sprite.Sprite(car_img)5 window = pyglet.window.Window()6 pyglet.gl.glClearColor(1, 1, 1, 1)78 @window.event9 def on_draw():10 window.clear()11 carSprite.draw()1213 pyglet.app.run() On line 3, the image is opened using pyglet.image.load call. A Sprite instance corresponding to this image is created on line 4. The code on line 6 sets white background for the window. The on_draw is an API method that is called when the window needs to be redrawn. Here, the image sprite is drawn on the screen. The next illustration shows a loaded image within a Pyglet window. In various examples in this article, the file path strings are hardcoded. We have used forward slashes for the file path. Although this works on Windows platform, the convention is to use backward slashes. For example, images/car.png is represented as imagescar.png. Additionally, you can also specify a complete path to the file by using the os.path.join method in Python. Regardless of what slashes you use, the os.path.normpath will make sure it modifies the slashes to fit to the ones used for the platform. The use of oos.path.normpath is illustrated in the following snippet: import osoriginal_path = 'C:/images/car.png"new_path = os.path.normpath(original_path) The preceding image illustrates Pyglet window showing a still image. Mouse and keyboard controls The Window module of Pyglet implements some API methods that enable user input to a playing animation. The API methods such as on_mouse_press and on_key_press are used to capture mouse and keyboard events during the animation. These methods can be overridden to perform a specific operation. Adding sound effects The media module of Pyglet supports audio and video playback. The following code loads a media file and plays it during the animation. 1 background_sound = pyglet.media.load(2 'C:/AudioFiles/background.mp3',3 streaming=False)4 background_sound.play() The second optional argument provided on line 3 decodes the media file completely in the memory at the time the media is loaded. This is important if the media needs to be played several times during the animation. The API method play() starts streaming the specified media file.
Read more
  • 0
  • 0
  • 12514
article-image-python-multimedia-animation-examples-using-pyglet
Packt
31 Aug 2010
7 min read
Save for later

Python Multimedia: Animation Examples using Pyglet

Packt
31 Aug 2010
7 min read
(For more resources on Python, see here.) Single image animation Imagine that you are creating a cartoon movie where you want to animate the motion of an arrow or a bullet hitting a target. In such cases, typically it is just a single image. The desired animation effect is accomplished by performing appropriate translation or rotation of the image. Time for action – bouncing ball animation Lets create a simple animation of a 'bouncing ball'. We will use a single image file, ball.png, which can be downloaded from the Packt website. The dimensions of this image in pixels are 200x200, created on a transparent background. The following screenshot shows this image opened in GIMP image editor. The three dots on the ball identify its side. We will see why this is needed. Imagine this as a ball used in a bowling game. The image of a ball opened in GIMP appears as shown in the preceding image. The ball size in pixels is 200x200. Download the files SingleImageAnimation.py and ball.png from the Packt website. Place the ball.png file in a sub-directory 'images' within the directory in which SingleImageAnimation.py is saved. The following code snippet shows the overall structure of the code. 1 import pyglet2 import time34 class SingleImageAnimation(pyglet.window.Window):5 def __init__(self, width=600, height=600):6 pass7 def createDrawableObjects(self):8 pass9 def adjustWindowSize(self):10 pass11 def moveObjects(self, t):12 pass13 def on_draw(self):14 pass15 win = SingleImageAnimation()16 # Set window background color to gray.17 pyglet.gl.glClearColor(0.5, 0.5, 0.5, 1)1819 pyglet.clock.schedule_interval(win.moveObjects, 1.0/20)2021 pyglet.app.run() Although it is not required, we will encapsulate event handling and other functionality within a class SingleImageAnimation. The program to be developed is short, but in general, it is a good coding practice. It will also be good for any future extension to the code. An instance of SingleImageAnimation is created on line 14. This class is inherited from pyglet.window.Window. It encapsulates the functionality we need here. The API method on_draw is overridden by the class. on_draw is called when the window needs to be redrawn. Note that we no longer need a decorator statement such as @win.event above the on_draw method because the window API method is simply overridden by this inherited class. The constructor of the class SingleImageAnimation is as follows: 1 def __init__(self, width=None, height=None):2 pyglet.window.Window.__init__(self,3 width=width,4 height=height,5 resizable = True)6 self.drawableObjects = []7 self.rising = False8 self.ballSprite = None9 self.createDrawableObjects()10 self.adjustWindowSize() As mentioned earlier, the class SingleImageAnimation inherits pyglet.window.Window. However, its constructor doesn't take all the arguments supported by its super class. This is because we don't need to change most of the default argument values. If you want to extend this application further and need these arguments, you can do so by adding them as __init__ arguments. The constructor initializes some instance variables and then calls methods to create the animation sprite and resize the window respectively. The method createDrawableObjects creates a sprite instance using the ball.png image. 1 def createDrawableObjects(self):2 """3 Create sprite objects that will be drawn within the4 window.5 """6 ball_img= pyglet.image.load('images/ball.png')7 ball_img.anchor_x = ball_img.width / 28 ball_img.anchor_y = ball_img.height / 2910 self.ballSprite = pyglet.sprite.Sprite(ball_img)11 self.ballSprite.position = (12 self.ballSprite.width + 100,13 self.ballSprite.height*2 - 50)14 self.drawableObjects.append(self.ballSprite) The anchor_x and anchor_y properties of the image instance are set such that the image has an anchor exactly at its center. This will be useful while rotating the image later. On line 10, the sprite instance self.ballSprite is created. Later, we will be setting the width and height of the Pyglet window as twice of the sprite width and thrice of the sprite height. The position of the image within the window is set on line 11. The initial position is chosen as shown in the next screenshot. In this case, there is only one Sprite instance. However, to make the program more general, a list of drawable objects called self.drawableObjects is maintained. To continue the discussion from the previous step, we will now review the on_draw method. def on_draw(self): self.clear() for d in self.drawableObjects: d.draw() As mentioned previously, the on_draw function is an API method of class pyglet.window.Window that is called when a window needs to be redrawn. This method is overridden here. The self.clear() call clears the previously drawn contents within the window. Then, all the Sprite objects in the list self.drawableObjects are drawn in the for loop. The preceding image illustrates the initial ball position in the animation. The method adjustWindowSize sets the width and height parameters of the Pyglet window. The code is self-explanatory: def adjustWindowSize(self): w = self.ballSprite.width * 3 h = self.ballSprite.height * 3 self.width = w self.height = h So far, we have set up everything for the animation to play. Now comes the fun part. We will change the position of the sprite representing the image to achieve the animation effect. During the animation, the image will also be rotated, to give it the natural feel of a bouncing ball. 1 def moveObjects(self, t):2 if self.ballSprite.y - 100 < 0:3 self.rising = True4 elif self.ballSprite.y > self.ballSprite.height*2 - 50:5 self.rising = False67 if not self.rising:8 self.ballSprite.y -= 59 self.ballSprite.rotation -= 610 else:11 self.ballSprite.y += 512 self.ballSprite.rotation += 5 This method is scheduled to be called 20 times per second using the following code in the program. pyglet.clock.schedule_interval(win.moveObjects, 1.0/20) To start with, the ball is placed near the top. The animation should be such that it gradually falls down, hits the bottom, and bounces back. After this, it continues its upward journey to hit a boundary somewhere near the top and again it begins its downward journey. The code block from lines 2 to 5 checks the current y position of self.ballSprite. If it has hit the upward limit, the flag self.rising is set to False. Likewise, when the lower limit is hit, the flag is set to True. The flag is then used by the next code snippet to increment or decrement the y position of self.ballSprite. The highlighted lines of code rotate the Sprite instance. The current rotation angle is incremented or decremented by the given value. This is the reason why we set the image anchors, anchor_x and anchor_y at the center of the image. The Sprite object honors these image anchors. If the anchors are not set this way, the ball will be seen wobbling in the resultant animation. Once all the pieces are in place, run the program from the command line as: $python SingleImageAnimation.py This will pop up a window that will play the bouncing ball animation. The next illustration shows some intermediate frames from the animation while the ball is falling down. What just happened? We learned how to create an animation using just a single image. The image of a ball was represented by a sprite instance. This sprite was then translated and rotated on the screen to accomplish a bouncing ball animation. The whole functionality, including the event handling, was encapsulated in the class SingleImageAnimation.
Read more
  • 0
  • 0
  • 13658

Packt
31 Aug 2010
8 min read
Save for later

MySQL 5.1 Plugin: HTML Storage Engine—Reads and Writes

Packt
31 Aug 2010
8 min read
(For more resources on MySQL, see here.) An idea of the HTML engine Ever thought about what your tables might look like? Why not represent a table as a <TABLE>? You would be able to see it, visually, in any browser. Sounds cool. But how could we make it work? We want a simple engine, not an all-purpose Swiss Army Knife HTML-to-SQL converter, which means we will not need any existing universal HTML or XML parsers, but can rely on a fixed file format. For example, something like this: <html><head><title>t1</title></head><body><table border=1><tr><th>col1</th><th>other col</th><th>more cols</th></tr><tr><td>data</td><td>more data</td><td>more data</td></tr><!-- this row was deleted ... --><tr><td>data</td><td>more data</td><td>more data</td></tr>... and so on ...</table></body></html> But even then this engine is way more complex than the previous example, and it makes sense to split the code. The engine could stay, as usual, in the ha_html.cc file, the declarations in ha_html.h, and if we need any utility functions to work with HTML we can put them in the htmlutils.cc file. Flashback A storage engine needs to declare a plugin and an initialization function that fills a handlerton structure. Again, the only handlerton method that we need here is a create() method. #include "ha_html.h"static handler* html_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root){ return new (mem_root) ha_html(hton, table);}static int html_init(void *p){ handlerton *html_hton = (handlerton *)p; html_hton->create = html_create_handler; return 0;}struct st_mysql_storage_engine html_storage_engine ={ MYSQL_HANDLERTON_INTERFACE_VERSION };mysql_declare_plugin(html){ MYSQL_STORAGE_ENGINE_PLUGIN, &html_storage_engine, "HTML", "Sergei Golubchik", "An example HTML storage engine", PLUGIN_LICENSE_GPL, html_init, NULL, 0x0001, NULL, NULL, NULL}mysql_declare_plugin_end; Now we need to implement all of the required handler class methods. Let's start with simple ones: const char *ha_html::table_type() const{ return "HTML";}const char **ha_html::bas_ext() const{ static const char *exts[] = { ".html", 0 }; return exts;}ulong ha_html::index_flags(uint inx, uint part, bool all_parts) const{ return 0;}ulonglong ha_html::table_flags() const{ return HA_NO_TRANSACTIONS | HA_REC_NOT_IN_SEQ | HA_NO_BLOBS;}THR_LOCK_DATA **ha_html::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type){ if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) lock.type = lock_type; *to ++= &lock; return to;} These methods are familiar to us. They say that the engine is called "HTML", it stores the table data in files with the .html extension, the tables are not transactional, the position for ha_html::rnd_pos() is obtained by calling ha_html::position(), and that it does not support BLOBs. Also, we need a function to create and initialize an HTML_SHARE structure: static HTML_SHARE *find_or_create_share( const char *table_name, TABLE *table){ HTML_SHARE *share; for (share = (HTML_SHARE*)table->s->ha_data; share; share = share->next) if (my_strcasecmp(table_alias_charset, table_name, share->name) == 0) return share; share = (HTML_SHARE*)alloc_root(&table->s->mem_root, sizeof(*share)); bzero(share, sizeof(*share)); share->name = strdup_root(&table->s->mem_root, table_name); share->next = (HTML_SHARE*)table->s->ha_data; table->s->ha_data = share; return share;} It is exactly the same function, only the structure is now called HTML_SHARE, not STATIC_SHARE. Creating, opening, and closing the table Having done the basics, we can start working with the tables. The first operation, of course, is the table creation. To be able to read, update, or even open the table we need to create it first, right? Now, the table is just an HTML file and to create a table we only need to create an HTML file with our header and footer, but with no data between them. We do not need to create any TABLE or Field objects, or anything else—MySQL does it automatically. To avoid repeating the same HTML tags over and over we will define the header and the footer in the ha_html.h file as follows: #define HEADER1 "<html><head><title>"#define HEADER2 "</title></head><body><table border=1>\n"#define FOOTER "</table></body></html>"#define FOOTER_LEN ((int)(sizeof(FOOTER)-1)) As we want a header to include a table name we have split it in two parts. Now, we can create our table: int ha_html::create(const char *name, TABLE *table_arg, HA_CREATE_INFO *create_info){ char buf[FN_REFLEN+10]; strcpy(buf, name); strcat(buf, *bas_ext()); We start by generating a filename. The "table name" that the storage engine gets is not the original table name, it is converted to be a safe filename. All "troublesome" characters are encoded, and the database name is included and separated from the table name with a slash. It means we can safely use name as the filename and all we need to do is to append an extension. Having the filename, we open it and write our data: FILE *f = fopen(buf, "w"); if (f == 0) return errno; fprintf(f, HEADER1); write_html(f, table_arg->s->table_name.str); fprintf(f, HEADER2 "<tr>"); First, we write the header and the table name. Note that we did not write the value of the name argument into the header, but took the table name from the TABLE_SHARE structure (as table_arg->s->table_name.str), because name is mangled to be a safe filename, and we would like to see the original table name in the HTML page title. Also, we did not just write it into the file, we used a write_html() function—this is our utility method that performs the necessary entity encoding to get a well-formed HTML. But let's not think about it too much now, just remember that we need to write it, it can be done later. Now, we iterate over all fields and write their names wrapped in <th>...</th> tags. Again, we rely on our write_html() function here: for (uint i = 0; i < table_arg->s->fields; i++) { fprintf(f, "<th>"); write_html(f, table_arg->field[i]->field_name); fprintf(f, "</th>"); } fprintf(f, "</tr>"); fprintf(f, FOOTER); fclose(f); return 0;} Done, an empty table is created. Opening it is easy too. We generate the filename and open the file just as in the create() method. The only difference is that we need to remember the FILE pointer to be able to read the data later, and we store it in fhtml, which has to be a member of the ha_html object: int ha_html::open(const char *name, int mode, uint test_if_locked){ char buf[FN_REFLEN+10]; strcpy(buf, name); strcat(buf, *bas_ext()); fhtml = fopen(buf, "r+"); if (fhtml == 0) return errno; When parsing an HTML file we will often need to skip over known patterns in the text. Instead of using a special library or a custom pattern parser for that, let's try to use scanf()—it exists everywhere, has a built-in pattern matching language, and it is powerful enough for our purposes. For convenience, we will wrap it in a skip_html() function that takes a scanf() format and returns the number of bytes skipped. Assuming we have such a function, we can finish opening the table: skip_html(fhtml, HEADER1 "%*[^<]" HEADER2 "<tr>"); for (uint i = 0; i < table->s->fields; i++) { skip_html(fhtml, "<th>%*[^<]</th>"); } skip_html(fhtml, "</tr>"); data_start = ftell(fhtml); We skip the first part of the header, then "everything up to the opening angle bracket", which eats up the table name, and the second part of the header. Then we skip individual row headers in a loop and the end of row </tr> tag. In order not to repeat this parsing again we remember the offset where the row data starts. At the end we allocate an HTML_SHARE and initialize lock objects: share = find_or_create_share(name, table); if (share->use_count++ == 0) thr_lock_init(&share->lock); thr_lock_data_init(&share->lock,&lock,NULL); return 0;} Closing the table is simple, and should not come as a surprise to us: int ha_html::close(void){ fclose(fhtml); if (--share->use_count == 0) thr_lock_delete(&share->lock); return 0;}
Read more
  • 0
  • 0
  • 1439

article-image-adding-advanced-form-features-using-chronoforms
Packt
31 Aug 2010
16 min read
Save for later

Adding Advanced Form Features using ChronoForms

Packt
31 Aug 2010
16 min read
(For more resources on ChronoForms, see here.) Using PHP to create "select" dropdowns One frequent request is to create a select drop-down from a set of records in a database table. Getting ready You'll need to know the MySQL query to extract the records you need from the database table. This requires some Joomla! knowledge and some MySQL know-how. Joomla! keeps its articles in the jos_content database table and the two-table columns that we want are the article title and the article id. A quick check in the database tells us that the columns are appropriately called title and id. We can also see that the section id column is called sectionid (with no spaces); the column that tells us if the article is published or not is called state and takes the values 1 for published and 0 for not published. How to do it . . . We are going to use some PHP to look up the information about the articles in the database table and then output the results it finds as a series of options for our drop-down box. You'll recall that normal HTML code for a drop-down box looks something like this: <select . . .> <option value='option_value_1'>Option 1 text</option> <option value='option_value_2'>Option 2 text</option> . . .</select> This is simplified a little so that we can see the main parts that concern us here—the options. Each option uses the same code with two variables—a value attribute and a text description. The value will be returned when the form is submitted; the text description is shown when the form is displayed on your site. In simple forms, the value and the description text are often the same. This can be useful if all you are doing with the results is to display them on a web page or in an e-mail. If you are going to use them for anything more complicated than that, it can be much more useful to use a simplified, coded form in the value. For our list of articles, it will be helpful if our form returns the ID of the article rather than its title. Hence, we need to set the options to be something like this: <option value='99'>Article title</option> Having the article ID will let us look up the article in the database and extract any other information that we might need, or to update the record to change the corresponding record. Here's the code that we'll use. The beginning and ending HTML lines are exactly the same as the standard drop-down code that ChronoForms generates but the "option" lines are replaced by the section inside the <?php . . . ?> tags. The PHP snippet looks up the article IDs and titles from the jos_content table, then loops through the results writing an <option> tag for each one: <div class="form_item"> <div class="form_element cf_dropdown"> <label class="cf_label" style="width: 150px;"> Articles</label> <select class="cf_inputbox validate-selection" id="articles" size="1" name="articles"> <option value=''>--?--</option><?phpif (!$mainframe->isSite() ) {return;}$db =& JFactory::getDBO();$query = " SELECT `id`, `title` FROM `#__content` WHERE `sectionid` = 1 AND `state` = 1 ;";$db->setQuery($query);$options = $db->loadAssocList();foreach ( $options as $o ) { echo "<option value='".$o[id]."'>".$o[title]."</option>";}?> </select> </div> <div class="cfclear">&nbsp;</div></div> The resulting HTML will be a standard select drop-down that displays the list of titles and returns the article ID when the form is submitted. Here's what the form input looks like: A few of the options from the page source are shown: <option value='1'>Welcome to Joomla!</option><option value='2'>Newsflash 1</option><option value='3'>Newsflash 2</option>. . . How it works... We are loading the values of the id and title columns from the database record and then using a PHP for each loop to go through the results and add each id and title pair into an <option> tag. There's more... There are many occasions when we want to add select drop-downs into forms with long lists of options. Date and time selectors, country and language lists, and many others are frequently used. We looked here to get the information from a database table which is simple and straightforward when the data is in a table or when the data can conveniently be stored in a table. It is the preferred solution for data such as article titles that can change from day to day. There are a couple of other solutions that can also be useful: Creating numeric options lists directly from PHP Using arrays to manage option lists that change infrequently Creating numeric options lists Let's imagine that we need to create a set of six numeric drop-downs to select: day, month, year, hour, minute, and second. We could clearly do these with manually-created option lists but it soon gets boring creating sixty similar options. There is a PHP method range() that lets us use a similar approach to the one in the recipe. For a range of zero to 60, we can use range(0, 60). Now, the PHP part of our code becomes: <div class="form_item"> <div class="form_element cf_dropdown"> <label class="cf_label" style="width: 150px;"> Minutes</label> <select class="cf_inputbox validate-selection" id="minutes" size="1" name="minutes"> <option value=''>--?--</option><?phpif (!$mainframe->isSite() ) {return;}foreach ( range(0, 60) as $v ) { echo "<option value='$v'>$v</option>";}?> </select> </div> <div class="cfclear">&nbsp;</div></div> This is slightly simpler than the database foreach code, as we don't need the quotes round the array values. This will work very nicely and we could repeat something very similar for each of the other five drop-downs. However, when we think about it, they will all be very similar and that's usually a sign that we can use more PHP to do some of the work for us. Indeed we can create our own little PHP function to output blocks of HTML for us. Looking at this example, there are four things that will change between the blocks—the label text, the name and id, and the range start and the range end. We can set these as variables in a PHP function: <?phpif ( !$mainframe->isSite() ) {return;}function createRangeSelect($label, $name, $start, $end) {?><div class="form_item"> <div class="form_element cf_dropdown"> <label class="cf_label" style="width: 150px;"> <?php echo $label; ?></label> <select class="cf_inputbox validate-selection" id="<?php echo $name; ?>" size="1" name="<?php echo $name; ?>"> <option value=''>--?--</option><?php foreach ( range($start, $end) as $v ) { echo "<option value='$v'>$v</option>"; }?> </select> </div> <div class="cfclear">&nbsp;</div></div><?php}?> Notice that this is very similar to the previous code example. We've added the function . . . line at the start, the } at the end, and replaced the values with variable names. It's important to get the placement of the <?php . . . ?> tags right. Code inside the tags will be treated as PHP, outside them as HTML. All that remains now is to call the function to generate our drop-downs: <?phpif (!$mainframe->isSite() ) {return;}createRangeSelect('Day', 'day', 0, 31);createRangeSelect('Month', 'month', 1, 12);createRangeSelect('Year', 'year', 2000, 2020);createRangeSelect('Hour', 'hour', 0, 24);createRangeSelect('Minute', 'minute', 0, 60);createRangeSelect('Second', 'second', 0, 60);function createRangeSelect($label, $name, $start, $end) {. . . The result tells us that we have more work to do on the layout, but the form elements work perfectly well. Creating a drop-down from an array In the previous example, we used the PHP range() method to generate our options. This works well for numbers but not for text. Imagine that we have to manage a country list. These do change, but not frequently. So they are good candidates for keeping in an array in the Form HTML. It's not too difficult to find pre-created PHP arrays of countries with a little Google research and it's probably easier to use one of these and correct it for your needs than to start from scratch. As we mentioned with the Article list, it's generally simpler and more flexible to use a list with standard IDs (we've used two-letter codes below). With countries, this can remove many problems with special characters and translations. Here are the first few lines of a country list: $countries = array( 'AF'=>'Afghanistan', 'AL'=>'Albania', 'DZ'=>'Algeria', 'AS'=>'American Samoa', . . .); Once we have this, it's easy to modify our foreach . . . loop to use it: foreach ( $countries as $k => $v ) { echo "<option value='$k'>$v</option>";} If you are going to use the country list in more than one form, then it may be worthwhile keeping it in a separate file that is included in the Form HTML. That way, any changes you make will be updated immediately in all of your forms. Using Ajax to look up e-mail addresses It's not very difficult to add Ajax functionality to ChronoForms, but it's not the easiest task in the world either. We'll walk through a fairly simple example here which will provide you with the basic experience to build more complex applications. You will need some knowledge of JavaScript to follow through this recipe. Normally, the only communication between the ChronoForms client (the user in their browser) and the server (the website host) is when a page is loaded or a form is submitted. Form HTML is sent to the client and a $_POST array of results is returned. Ajax is a technique, or a group of techniques, that enables communication while the user is browsing the page without them having to submit the form. As usual, at the browser end the Ajax communication is driven by JavaScript and at the server end we'll be responding using PHP. Put simply, the browser asks a question, the server replies, then the browser shows the reply to the user. For the browser JavaScript and the server PHP to communicate, there needs to be an agreed set of rules about how the information will be packaged. We'll be using the JSON (www.json.org) format. The task we will work on will use a newsletter form. We'll check to see if the user's e-mail is already listed in our user database. This is slightly artificial but the same code can easily be adapted to work with the other database tables and use more complex checks. Getting ready We'll need a form with an e-mail text input. The input id needs to be email for the following code to work: <div class="form_item"> <div class="form_element cf_textbox"> <label class="cf_label" style="width: 150px;">Email</label> <input class="cf_inputbox" maxlength="150" size="30" title="" id="email" name="email" type="text" /> </div> <div class="cfclear">&nbsp;</div></div> The form we use will also have a name text input and a submit button, but they are to make it look like a real form and aren't used in the Ajax coding. How to do it . . . We'll follow the action and start with the user action in the browser. We need to start our check when the user makes an entry in the e-mail input. So, we'll link our JavaScript to the blur event in that input. Here's the core of the code that goes in the Form JavaScript box: / set the url to send the request tovar url = 'index.php?option=com_chronocontact &chronoformname=form_name&task=extra&format=raw';// define 'email'var email = $('email');// set the Ajax call to run// when the email field loses focusemail.addEvent('blur', function() { // Send the JSON request var jSonRequest = new Json.Remote(url, { . . . }).send({'email': email.value});}); Note that the long line starting with var url = . . . &format=raw'; is all one line and should not have any breaks in it. You also need to replace 'form_name' with the name of your form in this URL. There really isn't too much to this. We are using the MooTools JSON functions and they make sending the code very simple. The next step is to look at what happens back on the server. The URL we used in the JavaScript includes the task=extra parameter. When ChronoForms sees this, it will ignore the normal Form Code and instead run the code from the Extra Code boxes at the bottom of the Form Code tab. By default, ChronoForms will execute the code from Extra code 1. If you need to access one of the other boxes, then use for example, task=extra&extraid=3 to run the code from Extra Code box 3. Now, we are working back on the server. So, we need to use PHP to unpack the Ajax message, check the database, and send a message back: <?php// clean up the JSON message$json = stripslashes($_POST['json']);$json = json_decode($json);$email = strtolower(trim($json->email));// check that the email field isn't empty$response = false;if ( $email ) { // Check the database $db =& JFactory::getDBO(); $query = " SELECT COUNT(*) FROM `#__users` WHERE LOWER(`email`) = ".$db->quote($email)."; "; $db->setQuery($query); $response = (bool) !$db->loadResult();}$response = array('email_ok' => $response );//send the replyecho json_encode($response);// stop the from running$MyForm->stopRunning = true;die;?> This code has three main parts: To start with, we "unwrap" the JSON message. Then, we check if it isn't empty and run the database query. Lastly, we package up the reply and tidy up at the end to stop any more form processing from this request. The result we send will be array('email_ok' => $response ) where $response will be either true or false. This is probably the simplest JSON message possible, but is enough for our purpose. Note that here, true means that this e-mail is not listed and is OK to use. The third step is to go back to the form JavaScript and decide how we are going to respond to the JSON reply. Again, we'll keep it simple and just change the background color of the box—red if the e-mail is already in use (or isn't a valid e-mail) or green if the entry isn't in use and is OK to submit. Here's the code snippet to do this using the onComplete parameter of the MooTools JSON function: onComplete: function(r) { // check the result and set the background color if ( r.email_ok ) { email.setStyle('background-color', 'green'); } else { email.setStyle('background-color', 'red'); }} Instead of (or as well as) changing the background color, we could make other CSS changes, display a message, show a pop-up alert, or almost anything else. Lastly let's put the two parts of the client-side JavaScript together with a little more code to make it run smoothly and to check that there is a valid e-mail before sending the JSON request. window.addEvent('domready', function() { // set the url to send the request to var url = 'index.php?option=com_chronocontact &chronoformname=form_name&task=extra&format=raw'; var email = $('email'); email.addEvent('blur', function() { // clear any background color from the input email.setStyle('background-color', 'white'); // check that the email address is valid regex = /^([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})$/i; var value = email.value.trim(); if ( value.length > 6 && regex.test(value) ) { // if all is well send the JSON request var jSonRequest = new Json.Remote(url, { onComplete: function(r) { // check the result and set the background color if ( r.email_ok ) { email.setStyle('background-color', 'green'); } else { email.setStyle('background-color', 'red'); } } }).send({'email': email.value}); } else { // if this isn't a valid email set background color red email.setStyle('background-color', 'red'); } });}); Note that the long line starting with var url = . . . &format=raw'; is all one line and should not have any breaks in it. You also need to replace form_name with the name of your form in this URL. Make sure both the code blocks are in place in the Form JavaScript box and in the Extra Code 1 box, save, and publish your form. Then, test it to make sure that the code is working OK. The Ajax may take a second or two to respond but once you move out of the e-mail, input by tabbing on to another input or clicking somewhere else; the background colour should go red or green. How it works... As far as the Ajax and JSON parts of this are concerned, all we can say here is that it works. You'll need to dig into the MooTools, Ajax, or JSON documents to find out more. From the point of view of ChronoForms, the "clever" bit is the ability to interpret the URL that the Ajax message uses. We ignored most of it at the time but the JavaScript included this long URL (with the query string broken up into separate parameters): index.php?option=com_chronocontact&chronoformname=form_name&task=extra&format=raw The option parameter is the standard Joomla! way to identify which extension to pass the URL to. The chronoformname parameter tells ChronoForms which form to pass the URL to. The task=extra parameter tells ChronoForms that this URL is a little out of the ordinary (you may have noticed that forms usually have &task=send in the onSubmit URL). When ChronoForms sees this, it will pass the URL to the Extra Code box for processing and bypass the usual OnSubmit processing. Lastly, the format=raw parameter tells Joomla! to show the resulting code without any extra formatting and without adding the template code. This means that all that is sent back is just the JSON message. Without it we'd have to dig the message out from loads of surrounding HTML we don't need.
Read more
  • 0
  • 0
  • 5641
article-image-look-inside-mysql-daemon-plugin
Packt
31 Aug 2010
11 min read
Save for later

A Look Inside a MySQL Daemon Plugin

Packt
31 Aug 2010
11 min read
A look inside a Daemon plugin Unlike UDFs, MySQL plugins store all of the metadata in the plugins shared library. So when installing a plugin you only need to specify the name of the plugin and its shared library filename. This eliminates much of the user error while installing. With UDFs it is very easy to choose the wrong return type or forget the AGGREGATE keyword, but with plugins this is not possible. Why write a Daemon plugin Just like UDFs and other MySQL plugin types the Daemon plugin can be used to add extra functionality to MySQL with the same advantages and disadvantages. Daemon plugins are ideal for writing code that needs to reside in the server but does not need to communicate with it—such as a heartbeat plugin or monitoring plugins—because the simple Daemon plugin API does not provide any means for a server and a plugin to communicate with each other. Installing and using Daemon plugins Installing plugins is relatively easy because all of the information about a plugin is stored inside it. To install a plugin we can use the INSTALL PLUGIN statement as follows: mysql> INSTALL PLUGIN my_plugin SONAME 'my_plugin.so'; Likewise, to remove a plugin we use: mysql> UNINSTALL PLUGIN my_plugin; When a plugin is installed it is initialized instantly and this means that the code we write will start automatically when our plugin is installed. Upon installing a plugin it is added to the mysql.plugin table so MySQL knows it is installed and can load it again on startup. In other words, similar to UDFs, all installed plugins are loaded automatically when a server is started. A plugin is de-initialized when either it is uninstalled or the MySQL server is being shut down. It is worth noting at this time that if the MySQL server crashes for any reason the de-initialization of the plugin will not happen. If a plugin is installed, we can prevent it from being loaded and executed at startup with the --disable-plugin-my-plugin or --plugin-my-plugin=OFF commands. If we do not do that MySQL will try to load it because the default behavior is --plugin-my-plugin=ON. If the plugin fails to load, MySQL will note that fact in the error log and will continue without this plugin. If we want to be sure that a plugin is absolutely loaded in the server, and that the server will not simply ignore a plugin failure, we can use --plugin-my-plugin=FORCE. In this mode the server will exit if our plugin fails to load. As we can see below, the mysql.plugin table simply contains the plugin name and the filename for the shared library containing the plugin: mysql> SELECT * FROM mysql.plugin;+-----------+--------------+| name | dl |+-----------+--------------+| my_plugin | my_plugin.so |+-----------+--------------+1 row in set (0.01 sec) MySQL has a SHOW command to give us information about installed plugins. This is very useful to see if a plugin is actually running. If there was a problem during initialization then the status for the plugin will be marked as DISABLED. A sample output for SHOW PLUGINS can be seen below: mysql> SHOW PLUGINSG....*************************** 11. row *************************** Name: my_plugin Status: ACTIVE Type: DAEMONLibrary: my_plugin.soLicense: GPL11 rows in set (0.00 sec) Information Schema also includes a table for use with plugins, and it contains more detail than SHOW PLUGINS. It shows version information supplied by the plugin as well as the plugin description: mysql> SELECT * FROM information_schema.plugins WHERE PLUGIN_NAME='my_plugin'G*************************** 1. row *************************** PLUGIN_NAME: my_plugin PLUGIN_VERSION: 1.0 PLUGIN_STATUS: ACTIVE PLUGIN_TYPE: DAEMON PLUGIN_TYPE_VERSION: 50147.0PLUGIN_LIBRARY: my_plugin.so PLUGIN_LIBRARY_VERSION: 1.0 PLUGIN_AUTHOR: Andrew Hutchings PLUGIN_DESCRIPTION: Daemon example, shows a declaration PLUGIN_LICENSE: GPL1 row in set (0.00 sec) Technically, loading of plugins is very similar to loading of UDFs. Problems that can arise, ways of solving them, and error messages are similar to those of UDFs. The role of a version As we have seen, there are three two-component version numbers in the INFORMATION_SCHEMA.PLUGINS table. One of them, PLUGIN_VERSION, is purely informational. It is a number that a plugin author can specify arbitrarily, and MySQL itself does not do anything with it. The other two are very important though. They are used to protect the API, to make sure that if a plugin is loaded it uses the same API version that the server provides. This is one of the main differences to UDFs. UDF API is not versioned. Hence, it was not developed and still has only those features that it did in 3.21.24. Extending UDF API is risky; any change and old UDFs may start crashing the server. Plugin API, on the other hand, is safe. It is protected by a version, and this version is part of every plugin library, the API version that the plugin was compiled against. When a plugin is loaded the server verifies that the version is supported by the server and refuses to load a plugin otherwise. That is, the server can guarantee that all loaded plugins are fully compatible with the server, and no crash can happen because of API mismatch. The API is protected with two version numbers, as it contains two parts—one is common to all plugin types. It is version 1.0, as can be seen in the PLUGIN_LIBRARY_ VERSION column above. The other one is specific to each plugin type. For Daemon plugins this version is 50147.0, as seen in the PLUGIN_TYPE_VERSION column, and it is derived from the MySQL server version (which was 5.1.47 in my examples). Defining Daemon plugins The most basic of Daemon plugins needs no code at all; only a declaration is required. A plugin declaration is an instance of a st_mysql_plugin structure: struct st_mysql_plugin{ int type; void *info; const char *name; const char *author; const char *descr; int license; int (*init)(void *); int (*deinit)(void *); unsigned int version; struct st_mysql_show_var *status_vars; struct st_mysql_sys_var **system_vars; void *__reserved1}; The type defines what type of plugin this will be, which in turn defines what it can do. In MySQL 5.1, type can be set to one of the following enumerated values: Here we are talking about Daemon plugins so this should be set to MYSQL_DAEMON_PLUGIN. The info member is a pointer to the descriptor of the plugin and its members. It contains the information specific to this particular plugin type (while the st_mysql_plugin structure itself contains the information applicable to any plugin, independently of its type). It always starts with an API version number and for Daemon plugins this is all it contains. For other plugin types it may also contain plugin methods that the server will call, but Daemon plugins are not designed to communicate with the server, and their descriptor structure contains nothing besides the version: struct st_mysql_daemon my_daemon_plugin ={ MYSQL_DAEMON_INTERFACE_VERSION }; Next we have the name member, which specifies the name of the plugin as it will be used by MySQL. This is the name that needs to be used in the INSTALL PLUGIN statement, the name that will be seen in SHOW PLUGINS and SHOW ENGINES, the name that all plugin configuration variables and command-line options will start from. That is, the name of the plugin should be a valid SQL identifier and should be good for the command line too. A safe choice would be a name that consists of only Latin letters, digits, and an underscore. Plugin names are not case-sensitive. The author member is a string containing details about the author; it can contain anything we wish. It must be in UTF-8 and can be arbitrarily long, but MySQL will only show the first 64 characters of it. The final string member is descr, which should contain a description of the plugin. Again we are free to put whatever we like here, but we would normally put a short line stating what the plugin does. Again, it is supposed to be UTF-8, but it can be as long as you want. In the next member, each plugin specifies its license. This does not strictly do anything as such, but should help with accidental distribution of a plugin under the wrong license. There are currently three possible values for the license member: Then we come to the init and deinit members, which are pointers to the plugin initialization and de-initialization functions. The initialization function is called when the plugin is loaded during INSTALL PLUGIN or server startup. The de-initialization function is called when a plugin is unloaded, which, again, can happen for two reasons, UNINSTALL PLUGIN or server shutdown. In a Daemon plugin the initialization function is often used to fork a thread to run the main function of the plugin. Both the initialization and the de-initialization functions should return 0 on success or 1 on failure. The version member should be used for the current version of our plugin. A two-component version is encoded as a hexadecimal number, where the lower 8 bits store the second component (minor version) and all others bits store the first component (major version). For example, if the version is set to 0x205, MySQL will show it as "2.5", and if the version is set to 0x123FF, MySQL will show it as "291.255". Unfortunately, there is no way to store in this member a more complex version such as "1.10.14b-RC2". MySQL has many status variables that can be seen with the SHOW STATUS statement, and there are different third-party tools that analyze this data, how the status variables change over time, draw graphs, and so on. A plugin can benefit from that and make its status and various statistics and performance values visible as MySQL status variables. A pointer to the list of the plugin status variables is stored in the status_vars member. Similarly, there is a SHOW VARIABLES statement. It lists all MySQL system variables, variables that are used to alter the behavior of the server. They can have serverwide or session-only effect, some of them can be set on the command line or in the configuration file. They can be modifiable run-time or read-only. This is all available to plugins too. A plugin can add new system variables to the server, global or session, with or without command-line option support, modifiable or read-only. As we would expect, a pointer to the array of these variables goes into the system_vars member. Finally there is one __reserved1 member, which is unused in MySQL 5.1 and should be set to NULL. MySQL provides two macros that help to declare plugins. A plugin declaration starts from the mysql_declare_plugin() macro. It takes one argument, a unique identifier for this plugin library, it will be used automatically as needed to avoid name clashes when plugins are linked statically into the server. This identifier must be the same one that was used as a plugin name in the plug.in file. We can put many plugins in one library, but they all need to be declared in one place, after the mysql_declare_plugin() macro, separated by commas. We end the list of plugin declarations with a mysql_declare_plugin_end macro. A complete example of the plugin declarations can be seen as follows: mysql_declare_plugin(my_plugin){ MYSQL_DAEMON_PLUGIN, &my_plugin_info, "my_plugin", "Andrew Hutchings (Andrew.Hutchings@Sun.COM)", "Daemon example, shows a declaration", PLUGIN_LICENSE_GPL, my_plugin_init, my_plugin_deinit, 0x0100, NULL, NULL, NULL},{ MYSQL_DAEMON_PLUGIN, &my_plugin2_info, "my_plugin2", "Sergei Golubchik (serg@mariadb.org)", "Another Daemon example, shows a declaration", PLUGIN_LICENSE_GPL, my_plugin2_init, NULL, 0xC0FFEE, status, vars, NULL}mysql_declare_plugin_end; This declares two plugins. We can see that the first one: is a Daemon plugin has an info structure called my_plugin_info is called my_plugin and was written by me (Andrew Hutchings) is described as an example plugin is GPL licensed has initialization and de-initialization functions is of version 1.0 has no system or status variables The second plugin can be interpreted similarly. It is also a Daemon plugin of version 49407.238 with initialization function, without de-initialization function, with both status and system variables.
Read more
  • 0
  • 0
  • 2671

article-image-using-chronoforms-add-more-features-your-joomla-form
Packt
31 Aug 2010
11 min read
Save for later

Using ChronoForms to add More Features to your Joomla! Form

Packt
31 Aug 2010
11 min read
(For more resources on ChronoForms, see here.) Showing a YouTube video This is the "not really a form" recipe in this article, it just opens a little door to some of the other, more unexpected, capabilities of ChronoForms. For the most part Joomla! protects the content you can display on your pages; it's easy to show HTML + CSS formatted content, more difficult to show PHP and JavaScript. There are many modules, plug-ins and extensions that can help with this but if you have ChronoForms installed then it may be able to help. ChronoForms is designed to show pages that use HTML, CSS, PHP, and JavaScript working together. Most often the pages created are forms but nothing actually requires that any form inputs are included so we can add any code that we like. ChronoForms will wrap our code inside <form>. . .</form> tags which means that we can't embed a form (why would we want to?), but otherwise most things are possible.   Getting ready You will need the ID of the YouTube video that you want to display. We're going to use a video from a conference at Ashridge Business School, but any video will work in essentially the same way. This recipe was developed for this particular video to force display of the HD version. At that time HD was a new option on YouTube and was not readily accessible as it is now. How to do it... Find the video you want on YouTube and look for the links boxes in the right hand column. Here we've clicked on the "customize" icon—the little gear wheel—to open up the options menu. When you've set the options you want copy the code from the Embed box. Here is the code from this video with some added line breaks for clarity: <object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/2Ok1SFnMS4E&hl=en_GB&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/2Ok1SFnMS4E&hl=en_GB&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object> To create a good looking page, we are going to add some HTML before and after this snippet: <h3>Video Postcards from the Edge</h3><div>The video of the 2008 AMOC Conference</div><div style='margin:6px; padding:0px; border:6px solid silver;width:425px;'><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/2Ok1SFnMS4E&hl =en&fs=1&ap=%2526fmt%3D18"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/2Ok1SFnMS4E&hl=en&fs =1&ap=%2526fmt%3D18" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object></div><div>Some more text . . .</div> If you look closely, you'll see that there is also a new parameter in the URL—&ap=%2526fmt%3D18—which is there to force the HD version of the video to be used. Paste this code into the Form HTML box of a new form, save, and publish it. Of course, it would be entirely possible to embed the video and to add form inputs in the same page, maybe to ask for comments or reviews. How it works... Very simply ChronoForms allows you to embed scripts into the page HTML that are not permitted in standard Joomla! articles. Adding a barcode to a form e-mail Sometimes it's important to add a unique identifier to the form response, for example travel or event tickets. In this recipe we will look at generating a "random" identifier and adding it to the form e-mail as a scannable barcode. Getting ready We're going to need a simple form. Our newsletter form will be perfect although we'll be adding to the code in the Form HTML box. We'll need a simple function to create the "random identifier" which we will see shortly. Lastly we"ll need code to generate a barcode. Rather than taking time reinventing this particular wheel, we're going to use a PHP program created by Charles J Scheffold and made available for use or download from http://www.sid6581.net/cs/php-scripts/barcode/. How to do it... First, grab a copy of the barcode.php file from sid6581.net. We'll need to make this file accessible to our form. So let's create a new folder inside the ChronoForms front-end folder. You'll probably need to use an FTP client to do this, or install the "exTplorer" Joomla! extension which will allow you to create folders from within the Joomla! Site Admin interface. Browse to [root]/components/com_chronocontact and create a new includes folder Copy the standard Joomla! index.html file from the com_chronocontact folder into the new folder Upload the barcode.php file into the new includes folder Now, we are going to add the function to create a "random" identifier to the Form HTML. This is a small function that creates an alphanumeric string when it is called. <?phpif ( !$mainframe->isSite() ) { return; }/* function to generate a random alpha-numeric code using a specified pattern * * @param $pattern string * @return string */function generateIdent($pattern='AA9999A'){ $alpha = array("A","B","C","D","E","F","G","H", "J","K","L","M","N","P","Q","R","S","T","U","V","W", "X","Y","Z"); $digit = array("1","2","3","4","5","6","7","8","9"); $return = ""; $pattern_array = str_split($pattern, 1); foreach ( $pattern_array as $v ) { if ( is_numeric($v) ) { $return .= $digit[array_rand($digit)]; } elseif ( in_array(strtoupper($v), $alpha) ) { $return .= $alpha[array_rand($alpha)]; } else { $return .= " "; } } return $return;}?> We call this function using generateIdent() or generateIdent('pattern') where the pattern is a string of As and 9s that defines the shape of the ident we want. The default is AA9999A, giving idents like KX4629G. This will be perfectly fine for our example here. We also want to add the ident into the form and we'll use a hidden field to do that, but to make it visible we'll also display the value. <?php$ident = generateIdent();echo "<div>Ident is $ident</div>";?><input type='hidden' name='ident' id='ident' value='<?php echo $ident; ?>' /> In day to day use we probably wouldn't generate the ident until after the form is submitted. There is often no useful value in displaying it on the form and essentially the same code will work in the OnSubmit boxes. However, here it makes the process clearer to generate it in the form HTML. We can add both these code snippets to our form just before the submit button element. Then apply or save the form and view it in your browser. The layout may not be very elegant but the Ident is there. Refresh the browser a few times to be sure that it is different each time. It's simpler and tempting to use serial numbers to identify records. If you are saving data in a table then these are generated for you as record IDs. It does create some problems though; in particular, it can make it very easy to guess what other IDs are valid and if, as we often do, we include the ID in a URL it may well be possible to guess what other URLs will be valid. Using a random string like this makes that kind of security breach more difficult and less likely. We said though that we'd generate a barcode, so let's develop this form one more step and show the barcode in the form. If you look at the code in barcode.php, it shows a list of parameters and says what we can use. For example: <img src="barcode.php?barcode=123456&width=320&height=200"> We need to modify this a little to link to the new folder for the file and to add our generated ident value: <img src="/components/com_chronocontact/includes/barcode.php?barcode=<? php echo $ident;?>&width=320&height=8"> This code can go in place of the "echo" line we used to display the ident value: <?php$ident = generateIdent();echo "<img src='".JURI::base()."components/com_chronocontact/includes/barcode.php?barcode=".$ident."&width=320&height=80' />";?> Apply or save the form and view it in your browser. There we have it—a bar code in our form showing the random ident that we have created. If you don't see any graphic and the code appears to be correct then you may not have the PHP GD graphics library installed. Check on the AntiSpam tab for any of your forms and you will see a GD Info box. The GD library is now included in the vast majority of PHP installations. If you don't have it then check with your ISP to see if the library can be enabled. Now that's actually not of much use except to show that it works, you can't scan a bar code off the screen. Where we want it is in our Email template. The code to add to the template is: <div>Your code: {ident}</div><img src="<?php echo JURI::base().'components/com_chronocontact/ includes/'; ?>barcode.php?barcode={ident}&width=280&height=100" /> As this includes some PHP, we can't add it using the Rich Text Editor. First we need to go to the Email Setup | Properties box and set Use Template Editor to No, apply the Properties, then apply the form and go to the Email Template tab. To avoid an "oddity" in the current release of ChronoForms it may be necessary to comment out the generateIdent() function code block in the Form HTML, while you create an Email Setup. Just put /* & */ before and after the block if you get a blank page or see a PHP Error message about re-declaring the function. Now click on the Email Template tab and paste the code at the end of the textarea. Submit the form to test. We now have a printable e-mail complete with a barcode showing our random ident. How it works... In this recipe we did a couple of things. We added some more complex PHP to the Form HTML that we had before and we imported a PHP script found on the internet and successfully used that in combination with ChronoForms. There are many hundreds of useful scripts available for almost any conceivable function. Not all are of good quality and not all will work in this way but, with a little work, a surprising number will function perfectly well. There's more... We said earlier that it might be better to generate the ident after the form is submitted. Here's the code to use in the OnSubmit Before code box to get the same result in the e-mail: <?phpif ( ! $mainframe->isSite() ) { return; }JRequest::setVar('ident', generateIdent());/* function to generate a random alpha-numeric code using a specified pattern * * @param $pattern string * @return string */function generateIdent($pattern='AA9999A'){ $alpha = array("A","B","C","D","E","F","G","H", "J","K","L","M","N","P","Q","R","S","T","U","V","W", "X","Y","Z"); $digit = array("1","2","3","4","5","6","7","8","9"); $return = ""; $pattern_array = str_split($pattern, 1); foreach ( $pattern_array as $v ) { if ( is_numeric($v) ) { $return .= $digit[array_rand($digit)]; } elseif ( in_array(strtoupper($v), $alpha) ) { $return .= $alpha[array_rand($alpha)]; } else { $return .= " "; } } return $return;}?> If you use this, then you can remove all of the additional code from the Form HTML box leaving just the basic HTML generated by the Form Wizard. The Email template code remains as we created it previously.
Read more
  • 0
  • 0
  • 6253
Modal Close icon
Modal Close icon