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

How-To Tutorials

7018 Articles
article-image-jquery-14-dom-manipulation-methods-style-properties-and-class-attributes
Packt
19 Feb 2010
3 min read
Save for later

jQuery 1.4 DOM Manipulation Methods for Style Properties and Class Attributes

Packt
19 Feb 2010
3 min read
General attributes These methods get and set DOM attributes of elements. .attr() (getter) Get the value of an attribute for the first element in the set of matched elements. .attr(attributeName) Parameters attributeName: The name of the attribute to get Return value A string containing the attribute value. Description It's important to note that the .attr() method gets the attribute value for only the first element in the matched set. To get the value for each element individually, we need to rely on a looping construct such as jQuery's .each() method. Using jQuery's .attr() method to get the value of an element's attribute has two main benefits: Convenience: It can be called directly on a jQuery object and chained to other jQuery methods. Cross-browser consistency: Some attributes have inconsistent naming from browser to browser. Furthermore, the values of some attributes are reported inconsistently across browsers, and even across versions of a single browser. The .attr() method reduces such inconsistencies. .attr() (setter) Set one or more attributes for the set of matched elements. .attr(attributeName, value).attr(map).attr(attributeName, function) Parameters (first version) attributeName: The name of the attribute to set value: A value to set for the attribute Parameters (second version) map: A map of attribute-value pairs to set Parameters (third version) attributeName: The name of the attribute to set function: A function returning the value to set Return value The jQuery object, for chaining purposes. Description The .attr() method is a convenient and powerful way to set the value of attributes, especially when setting multiple attributes or using values returned by a function. Let's consider the following image: <img id="greatphoto" src="brush-seller.jpg" alt="brush seller" /> Setting a simple attribute We can change the alt attribute by simply passing the name of the attribute and its new value to the .attr() method. $('#greatphoto').attr('alt', 'Beijing Brush Seller'); We can add an attribute the same way. $('#greatphoto').attr('title', 'Photo by Kelly Clark'); Setting several attributes at once To change the alt attribute and add the title attribute at the same time, we can pass both sets of names and values into the method at once using a map (JavaScript object literal). Each key-value pair in the map adds or modifies an attribute: $('#greatphoto').attr({alt: 'Beijing Brush Seller',title: 'photo by Kelly Clark'}); When setting multiple attributes, the quotation marks around attribute names are optional. Computed attribute values By using a function to set attributes, we can compute the value based on other properties of the element. For example, we could concatenate a new value with an existing value as follows: $('#greatphoto').attr('title', function() { return this.alt + ' – photo by Kelly Clark'}); This use of a function to compute attribute values can be particularly useful when we modify the attributes of multiple elements at once. .removeAttr() Remove an attribute from each element in the set of matched elements. .removeAttr(attributeName).removeAttr(function) Parameters (first version) attributeName: An attribute to remove Parameters (second version) function: A function returning the attribute to remove Return value The jQuery object, for chaining purposes. Description The .removeAttr() method uses the JavaScript removeAttribute() function, but it has the advantage of being able to be called directly on a jQuery object and it accounts for different attribute naming across browsers. As of jQuery 1.4, the .removeAttr() function allows us to indicate the attribute to be removed by passing in a function.
Read more
  • 0
  • 0
  • 3462

article-image-spring-mvc-configuring-and-deploying-application
Packt
19 Feb 2010
5 min read
Save for later

Spring MVC - Configuring and Deploying the Application

Packt
19 Feb 2010
5 min read
The first section will focus on configuring the application and its components so that the application can be deployed. The focus of the second section will be a real world application that will be developed using the steps described in the article on Developing the MVC components and in this article. That sets the agenda for this discussion. Using Spring MVC – Configuring the Application There are four main steps in configuring of the application. They are: Configure the DispatcherServlet Configure the Controller Configure the View Configure the Build Script The first step will be same for any application that is built using Spring MVC. The other three steps change according to the components that have been developed for the application. Here are the details. Configure the DispatcherServlet The first step is to tell the Application server that all the requests for this (Spring MVC based) application need to be routed to Spring MVC. This is done by setting up the DispatcherServlet. The reason for setting up DispatcherServlet is that it acts as the entry point to the Spring MVC and thus to the application.  Since the DispatcherServlet interacts with the application as a whole (instead of individual components), its configuration or setting up at application level. And any setup that needs to be done at the application level is done by making the required entries in the web.xml. The entries required  in the web.xml can be divided into the following: Servlet mapping URL mapping The former specifies the details of the servlet and the latter specifies how the servlet is related to a URL. Here are the details. Servlet mapping Servlet mapping is akin to declaring a variable. It is through servlet mapping that Application Server knows which servlets of the application it needs to support.  Servlet mapping, in essence, assigns a name to a servlet class that can be reference throughout web.xml. To set up the DispatcherServlet, first it has to be mapped to a name. that can be done using <servlet-name> and <servlet-class> tags that are the child nodes of the <servlet> tag. The following statement maps the DispatcherServlet to the name "dispatcher". <servlet> <servlet-name> dispatcher </servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> </servlet> Since the DispatcherServlet needs to be loaded on the startup of the Application Server instead of the loading when a request arrives, the optional node <load-on-startup> with value of 1 is also required. The modified <servlet> tag will be: <servlet> <servlet-name> dispatcher </servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> Next step is to map the URL to the servlet name so that the requests can be routed to the DispatcherServlet. URL mapping Once the servlet has been mapped, the next step is to map the servlet name with a URL so that the requests for that particular URL can be passed on to the application via the DispatcherServlet. That can be done using the <servlet-name> and <url-pattern> nodes of the <servlet-mapping> node. The <servlet-name> is used to refer the name that was mapped with the DispatcherServlet class. The <url-pattern> is used to map a URL pattern with a servlet name so that when a request arrives matching the URL pattern, Application Server can redirect it to the mapped servlet. To map the DispatcherServlet with a URL pattern the <servlet-mapping> tag will be: <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> With this configuration/setting up of DispatcherServlet is complete. One point to keep in mind is that the URL pattern can be any pattern of one’s choice. However, it’s a common practice to use *.html for DispatcherServlet and *do for ActionServlet (Struts 1.x). Next step is to configure the View and Controller components of the application. Mapping the Controller By setting up the DispatcherServlet, the routing of requests to the application will be taken care of by the Application Server. However, unless the individual controllers of the application are setup/configured, the Framework would not know which controller to be called once the DispatcherServlet receives the request. The configuration of the Controller as well as the View components is done in the Spring MVC configuration file. The name of the configuration file is dependent on the name of the DispatcherServlet in web.xml, which is of the form <DispatcherServlet_name-servlet>.xml. So if the DispacherServlet is mapped to the name dispatcher, then the name of the configuration file will be dispatcher-servlet.xml. The file will reside in WEB-INF folder of the application. Everything in Spring Framework is a bean. Controllers are no exceptions. Controllers are configured as beans using the <bean> child tag of <beans> tag. A Controller is mapped by providing the URL of the request as the name attribute and complete qualified name of the Controller class as the value of the class attribute. For example, if the request URL is say, http://localhost/test/hello.html, then the name attribute will have /hello.html and the value attribute will have the fully qualified class name say, org.me.HelloWorldController. The following statements depicts the same: <bean name="/hello.html" class=" org.me.HelloWorldController "/> One point to keep in mind is that the "/" in the bean name represents the relative path. In other words, /hello.html means that hello.html is directly under http://localhost/test. If hello.html was under another directory say, jsp which, in turn was directly under the application, then the name attribute will be /jsp/hello.html. Let us move onto configuring the Views.
Read more
  • 0
  • 0
  • 3432

article-image-build-your-own-application-access-twitter-using-java-and-netbeans-part-2
Packt
19 Feb 2010
17 min read
Save for later

Build your own Application to access Twitter using Java and NetBeans: Part 2

Packt
19 Feb 2010
17 min read
In this tutorial, we’ll develop the simple Java application further to add some more functions. Now that we can connect to our Twitter account via the Twitter4J API, it would be nice to use a login dialog instead of hard-coding our Twitter username and password in the Java application. But before we start to build our enhanced SwingAndTweet application, let me show you how it will look like once we finish all the exercises in this part of the tutorial: And now, let the show begin… Creating a Login dialog for our SwingAndTweet application Open your NetBeans IDE along with your SwingAndTweet project, and make sure you’re in the Design View. Go to the Palette window and locate the Dialog component under the Swing Windows section; then drag and drop it anywhere inside the SwingAndTweetUI JFrame component: A JDialog will be added automatically to your SwingAndTweet application, and it will show up in the Component Inspector tab located at the lower-left part of the screen, under Other Components: Right-click on the jDialog1 component in the Inspector tab and select Change Variable Name… from the pop-up menu. The Rename dialog will show up next. Type twitterLogin in the New Name field and press Enter to change the dialog’s name. Now you can start adding text fields, labels and buttons to your twitterLogin dialog. Double-click on the twitterLogin component under the Inspector tab. The twitterLogin dialog will show up empty in the Design Editor window. Use the Palette window to add two JLabels, one JTextField, one JPasswordField and two JButtons to the twitterLogin dialog. Arrange these controls as shown below: Now let’s change the names of the JTextField control, the JPasswordField control and the two JButton controls, so we can easily identify them within your SwingAndTweet application’s code. Right-click on the first text field (jLabel2), select Change Variable Name… from the pop-up menu and replace the text field’s name with txtUsername. Do the same with the other fields; use txtPassword for the JPasswordField control, btnLogin for the Login button and btnExit for the Exit button. And now the last touch. Right-click anywhere inside the twitterLogin dialog, being careful not to right-click inside any of the controls, and select the Properties option from the pop-up menu. The twitterLogin [JDialog] – Properties dialog will appear next. Locate the title property, double-click on the null value and type Twitter Login to replace it. Next, scroll down the properties list until you find the modal property; click on its checkbox to enable it and then click on Close to save your changes. Basically, in the previous exercise we added all the Swing controls you’re going to need to type your username and password, so you can connect to your Twitter account. The twitterLogin dialog is going to take care of the login process for your SwingAndTweet application. We replaced the default names for the JTextField, the JPasswordField and the two JButton controls because it will be easier to identify them during the coding process of the application. On step 8 we used the Properties window of the twitterLogin dialog to change the title property and give your dialog a decent title. We also enabled the modal property on step 9, so you can’t just close the dialog and jump right to the SwingAndTweetUI main window; you’ll have to enter a valid Twitter username and password combination for that. Invoking the Login dialog Ok, now we have a good-looking dialog called twitterLogin. The next step is to invoke it before our main SwingAndTweet JFrame component shows up, so we need to insert some code inside the SwingAndTweetUI() constructor method. Click on the Source button of the Editor window to change to the Source View: Now locate the SwingAndTweetUI() constructor, and type the following lines right after the initComponents(); line: int loginWidth = twitterLogin.getPreferredSize().width; int loginHeight = twitterLogin.getPreferredSize().height; twitterLogin.setBounds(0,0,loginWidth,loginHeight); twitterLogin.setVisible(true); The code inside the SwingAndTweetUI() constructor method shall now look like this: To see your new twitterLogin dialog in action, press F6 or select Run | Run Project to run your SwingAndTweetUI application. The twitterLogin dialog will pop right up. You’ll be able to type in your username and password, but since we haven’t added any functionality yet, the buttons won’t do anything right now. Click on the Close (X) button to close the dialog window and the SwingAndTweetUI main window will appear next. Click on its Close (X) button to exit your Twitter Java application. Now let’s take a look at the code we added to your twitterLogin dialog. On the first line, int loginWidth = twitterLogin.getPreferredSize().width; we declare an integer variable named loginWidth, and assign to it the preferred width of the twitterLogin dialog. The getPreferredSize method retrieves the value of the preferredSize property from the twitterLogin dialog through the .width field. On the second line, int loginHeight = twitterLogin.getPreferredSize().height; we declare another integer variable named loginHeight, and assign to it the preferred height of the twitterLogin dialog. This time, the getPreferredSize() method retrieves the value of the preferredWidth property from the twitterLogin dialog through the .height field. On the next line, twitterLogin.setBounds(0,0,loginWidth,loginHeight); we use the setBounds method to set the x,y coordinates where the dialog should appear on the screen, along with its corresponding width and height. The first two parameters are for the x,y coordinates; in this case, x=0 and y=0, which means the twitterLogin dialog will show up at the upper-left part of the screen. The last two parameters receive the value of the loginWidth and loginHeight variables to establish the twitterLogin dialog’s width and height, respectively. The last line, twitterLogin.setVisible(true); makes the twitterLogin dialog appear on the screen. And since the modal property of this dialog is enabled, once it shows up on the screen it won’t let you do anything else with your SwingAndTweet1 application until you close it up or enter a valid Twitter username and password, as we’ll see in the next exercise. Adding functionality to the twitterLogin dialog Now your twitterLogin dialog is ready to roll! Basically, it won’t let you go to the SwingAndTweet main window until you’ve entered a valid Twitter username and password. And for doing that, we’re going to use the same login code from Build your own Application to access Twitter using Java and NetBeans: Part 1 of this article series. Go to the end of your SwingAndTweetUI application source code and locate the // Variables declaration – do not modify line. Below this line, you’ll see all the variables used in your application: the btnExit and btnLogin buttons, the text fields from your twitterLogin dialog and your SwingAndTweetUI main window, etc. Add the following line just below the // End of variables declaration line:     Twitter twitter; Now click on the Design button to change to the Design View: You’ll see the twitterLogin dialog again –in case you don’t, double-click on the twitterLogin component under the Inspector tab. Now double-click on the Login button to go back to the Code View. The btnLoginActionPerformed method will show up next. Add the following code inside this method: try { twitter = new Twitter(txtUsername.getText(), String.valueOf(txtPassword.getPassword())); twitter.verifyCredentials(); JOptionPane.showMessageDialog(null, "You're logged in!"); twitterLogin.dispose(); } catch (TwitterException e) { JOptionPane.showMessageDialog (null, "Login failed"); } Make sure you write each line on its own, to avoid errors. The btnLoginActionPerformed method shall look like this: Now you’re ready to test your twitterLogin dialog. Press F6 to run your application. The Twitter Login dialog will show up next. Type your Twitter username and password, and then click on the OK button. If the username and password are correct, the You’re logged in! dialog will show up and you’ll be able to go to the SwingAndTweetUI main window. If they’re not correct, the Login failed dialog will appear instead and, after you click on the OK button, you’ll return to the twitterLogin dialog until you type a correct Twitter username and password combination. To exit your SwingAndTweetUI application, click on the Close(X) button of the twitterLogin dialog and then on the Close(X) button of the SwingAndTweetUI window. You’ll be taken back to the NetBeans IDE. Click on the Design button to go back to the Design View, and double-click on the Exit button to open the btnExitActionPerformed method. Type System.exit(0); inside the btnExitActionPerformed method, as shown below: Now go back to the Design View again, right-click anywhere inside the twitterLogin dialog (just be careful not to right-click over any of the dialog’s controls) and select the Events | Window | windowClosing option from the pop-up menu: NetBeans will change to Code View automatically and you’ll be inside the twitterLoginWindowClosing method. Type System.exit(0); inside this method, as shown below: Now run your application to test the new functionality in your loginTwitter dialog. You’ll be able to exit the SwingAndTweet application when clicking on the Exit or Close(X) buttons, and you’ll be able to go to your application’s main window if you type a correct Twitter username and password combination. You can close your SwingAndTweet application now. And now, let’s examine what we just accomplished. First you added the Twitter twitter; line to your application code. With this line we’re declaring a Twitter object named twitter, and it will be available throughout all the application code. On step 4, you added some lines of code to the btnLoginActionPerformed method; this code will be executed every time you click on the Login button from the twitterLogin dialog. All the code is enclosed in a try block, so that if an error occurs during the login process, a TwitterException will be thrown and the code inside the catch block will execute. The first line inside the try block is twitter = new Twitter(txtUsername.getText(),String.valueOf(txtPassword.getPassword())); This code creates the twitter object that we’re going to use throughout the application. It uses the text value you entered in the txtUsername and txtPassword fields to log into your Twitter account. The next line, twitter.verifyCredentials(); checks to see if the username and password provided to the twitter object are correct; if that’s true, a message dialog box shows up in the screen with the You’re logged in! message and the rest of the code executes once you click on the OK button of this message dialog; otherwise, the code in the catch block executes and a message dialog shows up in the screen with the Login failed message, and the twitterLogin dialog keeps waiting for you to type a correct username and password combination. The next line in the sequence, JOptionPane.showMessageDialog(null, "You're logged in!"); shows the message dialog that we talked about before, and the last line inside the try block, twitterLogin.dispose(); makes the twitterLogin dialog disappear from the screen once you’ve logged into your Twitter account successfully. The only line of code inside the catch block is JOptionPane.showMessageDialog (null, "Login failed"); This line executes when there’s an error in the Twitter login process; it shows the Login failed message in the screen and waits for you to press the OK button. On step 9 we added one line of code to the btnExitActionPerformed method: System.exit(0); This line closes your SwingAndTweet application whenever you click on the Exit button. Finally, on steps 10-12 we added another System.exit(0); line to the twitterLoginWindowClosing method, to close your SwingAndTweet application whenever you click on the Close(X) button of the twitterLogin dialog. Showing your Twitter timeline right after logging in Now let’s see some real Twitter action! The following exercise will show you how to show your most recent tweets inside a text area. Click on the Design button to go to the Design View; then double-click on the [JFrame] component under the Inspector tab to show the SwingAndTweetUI dialog in the Design View window: The SwingAndTweet frame will show the three controls we created during Build your own Application to access Twitter using Java and NetBeans: Part 1. Replace the My Last Tweet text in the JLabel control with the What’s happening text. Then right-click on the JTextField control and select the Change Variable Name… option from the pop-up menu, to change its name from jTextField1 to txtUpdateStatus. Now do the same with the JButton control. Right-click on it and select the Change Variable Name… option from the pop-up menu to change its name from jButton1 to btnUpdateStatus. Right-click on the same button again, but this time select the Edit Text option from the pop-up menu and replace the Login text with Update. Rearrange the three controls as per the following screenshot (you’ll need to make the SwingAndTweet container wider): Now drag a JTextArea control from the Palette window and drop it inside the SwingAndTweetUI container. Resize the text area so it fills up the rest of the container, as shown below: Double-click on the Update button to open the btnUpdateStatusActionPerformed method. The first thing you’ll notice is that it’s not empty; this is because this used to be the old Login button, remember? Now just replace all the code inside this method, as shown below: private void btnUpdateStatusActionPerformed(java.awt.event.ActionEvent evt) { try { if (txtUpdateStatus.getText().isEmpty()) JOptionPane.showMessageDialog (null, "You must write something!"); else { twitter.updateStatus(txtUpdateStatus.getText()); jTextArea1.setText(null); java.util.List<Status> statusList = twitter.getUserTimeline(); for (int i=0; i<statusList.size(); i++) { jTextArea1.append(String.valueOf(statusList.get(i).getText())+"n"); jTextArea1.append("-----------------------------n"); } } } catch (TwitterException e) { JOptionPane.showMessageDialog (null, "A Twitter Error ocurred!"); } txtUpdateStatus.setText(""); jTextArea1.updateUI(); The next step is to modify your btnLoginActionPerformed method; we need to add several lines of code to show your Twitter timeline. The complete method is shown below (the lines you need to add are shown in bold): private void btnLoginActionPerformed(java.awt.event.ActionEvent evt) { try { twitter = new Twitter(txtUsername.getText(), String.valueOf(txtPassword.getPassword())); twitter.verifyCredentials(); // JOptionPane.showMessageDialog(null, "You're logged in!"); java.util.List<Status> statusList = twitter.getUserTimeline(); for (int i=0; i<statusList.size(); i++) { jTextArea1.append(String.valueOf(statusList.get(i).getText())+"n"); jTextArea1.append("-----------------------------n"); } twitterLogin.dispose(); } catch (TwitterException e) { JOptionPane.showMessageDialog (null, "Login failed"); } jTextArea1.updateUI(); } Once you have added all the necessary code in each button’s actionPerformed method, press F6 to run the SwingAndTweet application and check if all things work as intended. If you type a message in the txtUpdateStatus text field and then click on the Update button, the timeline information inside the JTextArea1 control will change to reflect your new Twitter status: You can close your SwingAndTweet application now. That was cool, right? Now you have a much better-looking Twitter client! And you can update your status, too! Let’s examine the code we added in this last exercise… private void btnUpdateStatusActionPerformed(java.awt.event.ActionEvent evt) { try { if (txtUpdateStatus.getText().isEmpty()) JOptionPane.showMessageDialog (null, "You must write something!"); else { twitter.updateStatus(txtUpdateStatus.getText()); jTextArea1.setText(null); java.util.List<Status> statusList = twitter.getUserTimeline(); for (int i=0; i<statusList.size(); i++) { jTextArea1.append(String.valueOf(statusList.get(i).getText())+"n"); jTextArea1.append("-----------------------------n"); } } } catch (TwitterException e) { JOptionPane.showMessageDialog (null, "A Twitter Error ocurred!"); } txtUpdateStatus.setText(""); jTextArea1.updateUI(); On step 7 we added some code to the btnUpdateStatusActionPerformed method. This code will execute whenever you click on the Update button to update your Twitter status. First, let’s look at the code inside the try block. The first two lines, if (txtUpdateStatus.getText().isEmpty()) JOptionPane.showMessageDialog (null, "You must write something!"); are the first part of a simple if-else statement that checks to see if you’ve written something inside the txtUpdateStatus text field; if it’s empty, a message dialog will show the You must write something! message on the screen, and then it will wait for you to click on the OK button. If the txtUpdateStatus text field is not empty, the code inside the else block will execute instead of showing up the message dialog. The next part of the code is the else block. The first line inside this block, twitter.updateStatus(txtUpdateStatus.getText()); updates your twitter status with the text you wrote in the txtUpdateStatus text field; if an error occurs at this point, a TwitterException is thrown and the program execution will jump to the catch block. If your Twitter status was updated correctly, the next line to execute is jTextArea1.setText(null); This line erases all the information inside the jTextArea1 control. And the next line, java.util.List<Status> statusList = twitter.getUserTimeline(); gets the 20 most recent tweets from your timeline and assigns them to the statusList variable. The next line is the beginning of a for statement: for (int i=0; i<statusList.size(); i++) { Basically, what this for block does is iterate through all the 20 most recent tweets in your timeline, one at a time, executing the two statements inside this block on each iteration: jTextArea1.append(String.valueOf(statusList.get(i).getText())+"n"); jTextArea1.append("-----------------------------n"); Although the getUserTimeline() function retrieves the 20 most recent tweets, we need to use the statusList.size() statement as the loop continuation condition inside the for block to get the real number of tweets obtained, because they can be less than 20, and we can’t iterate through something that maybe doesn’t exist, right? The first line appends the text of each individual tweet to the jTextArea1 control, along with a new-line character ("n") so each tweet is shown in one individual line, and the second line appends the "-----------------------------n" text as a separator between each individual tweet, along with a new-line character. The final result is a list of the 20 most recent tweets inside the jTextArea1 control. The only line of code inside the catch block displays the A Twitter Error occurred! message in case something goes wrong when trying to update your Twitter status. The next line of code right after the catch block is txtUpdateStatus.setText(""); This line just clears the content inside the txtUpdateStatus control, so you don’t accidentally insert the same message two times in a row. And finally, the last line of code in the btnUpdateStatusActionPerformed method is jTextArea1.updateUI(); This line updates the jTextArea1 control, so you can see the list of your 20 most recent tweets after updating your status. private void btnLoginActionPerformed(java.awt.event.ActionEvent evt) { try { twitter = new Twitter(txtUsername.getText(), String.valueOf(txtPassword.getPassword())); twitter.verifyCredentials(); // JOptionPane.showMessageDialog(null, "You're logged in!"); java.util.List<Status> statusList = twitter.getUserTimeline(); for (int i=0; i<statusList.size(); i++) { jTextArea1.append(String.valueOf(statusList.get(i).getText())+"n"); jTextArea1.append("-----------------------------n"); } twitterLogin.dispose(); } catch (TwitterException e) { JOptionPane.showMessageDialog (null, "Login failed"); } jTextArea1.updateUI(); And now let’s have a look at the code we added inside the btnLoginActionPerformed method. The first thing you’ll notice is that we’ve added the '//' characters to the // JOptionPane.showMessageDialog(null, "You're logged in!"); line; this means it’s commented out and it won’t be executed, because it’s safe to go directly to the SwingAndTweet main window right after typing your Twitter username and password. The next lines are identical to the ones inside the btnUpdateStatusActionPerformed method we saw before; the first line retrieves your 20 most recent tweets, and the for block displays the list of tweets inside the jTextArea1 control.  And the last line of code, jTextArea1.updateUI(); updates the jTextArea1 control so you can see the most recent information regarding your latest tweets. Summary Well, now your SwingAndTweet application looks better, don’t you think so? In this article, we enhanced the SwingAndTweet application which we build in the first part of the tutorials series. In short, we: Created a twitterLogin dialog to take care of the login process Added functionality to show your 20 most recent tweets right after logging in Added the functionality to update your Twitter status
Read more
  • 0
  • 0
  • 4291

article-image-working-data-application-components-sql-server-2008-r2
Packt
19 Feb 2010
4 min read
Save for later

Working with Data Application Components in SQL Server 2008 R2

Packt
19 Feb 2010
4 min read
(For more resources on Microsoft, see here.) A Data Application Component is an entity that integrates all data tier related objects used in authoring, deploying and managing into a single unit instead of working with them separately. Programmatically DACs belong to classes that are found in The Microsoft.SqlServer.Management.Dac namespace. DACs are stored in a DacStore and managed centrally. Dacs can be authored and built using SQL Server Data-Tier Application templates in VS2010 (now in Beta 2) or using SQL Server Management Studio. This article describes creating DAC using SQL Server 2008 R2 Nov-CTP(R2 server in this article), a new feature in this version. Overview of the article In order to proceed working with this example you need to download SQL Server 2008 R2 Nov-CTP. The ease with which this installs depend on the Windows OS on your machine. I had encountered problems installing it on my Windows XP SP3 where only partial files were installed. On Windows 7 Ultimate it installed very easily. This article uses the R2 Server installed on Windows 7 Ultimate. You can download the R2 Server from this link after registering at the site. Download the x 32 version, a 1.2 GB file. In order to work with Data Tier Applications in Visual Studio you need to install Visual Studio 2010 now in Beta 2. If you have installed Beta 1, take it out (use Add/Remove programs) before you install Beta 2. You would create a Database Project as shown in the next figure. In the following sections we will look at how to extract a DAC from a existing Database using tools in SSMS and R2 Server. This will be followed by deploying the DAC to a SQL Server 2008 (before R2 Version). In a future article we will see how to create and work with the DACs in Visual Studio. Extracting a DAC We will use the Extract a Data-Tier Application wizard to create a DAC file. Connect to the SQL Server 2008 Server in the Management Studio as shown. We will create a DAC package that will create a DAC file for us on completing this task. Right click the Pubs database and click on Tasks | Extract Data-Tier Appplication... You may also use any other database for working with this exercise. This brings up the Wizard as shown in the next figure. Read the notes on this window and review the database icons on this window. Click Next. The Set Properties page of the wizard gets displayed. The Application name will be the database name with which you started. You can change it if you like. The package file name will reflect the application name. The version is set at 1.0.0.0., but you may specify any version you like. You can create different DACs with different version numbers. Click Next. The program cranks up and after validation the Validation & Summary page gets displayed as shown. The file path of the package, the name of the package and the DAC objects that got into the package are all shown here. All database objects (Tables, Views, Stored Procedures etc) are included in the package. Click Save Report button to save the packaging info to a file. This saves the HTML file ExtractDACSummary_HODENTEK3_ pubs_20100125 to the SQL Server Management Studio folder. This report shows what objects were validated during this process as shown. Click Next. The Build the Package opens up and after the build process is completed you will be able to save the package as shown in the next picture. At the package location shown earlier you will see a package object as shown. This file can be Unpacked to a destination as well as opened with Microsoft SQL Server DAC Package File Unpack wizard. These actions can be accessed by making a right click on this package file.
Read more
  • 0
  • 0
  • 2686

article-image-php-web-20-mashup-projects-your-own-video-jukebox-part-1
Packt
19 Feb 2010
11 min read
Save for later

PHP Web 2.0 Mashup Projects: Your Own Video Jukebox: Part 1

Packt
19 Feb 2010
11 min read
Project Overview What Mashup the web APIs from Last.fm and YouTube to create a video jukebox of songs Protocols Used REST (XML-RPC available) Data Formats XML, XPSF, RSS Tools Featured PEAR APIs Used Last.fm and YouTube   Now that we've had some experience using web services, it's time to fine tune their use. XML-RPC, REST, and SOAP will be frequent companions when you use web services and create mashups. You will encounter a lot of different data formats, and interesting ways in which the PHP community has dealt with these formats. This is especially true because REST has become so popular. In REST, with no formalized response format, you will encounter return formats that vary from plain text to ad-hoc XML to XML-based standards. The rest of our projects will focus on exposing us to some new formats, and we will look at how to handle them through PHP. We will begin with a project to create our own personalized video jukebox. This mashup will pull music lists feeds from the social music site, Last.fm. We will parse out artist names and song titles from these feeds and use that information to search videos on YouTube, a user-contributed video site, using the YouTube web service. By basing the song selections on ever-changing feeds, our jukebox selection will not be static, and will change as our music taste evolves. As YouTube is a user-contributed site, we will see many interesting interpretations of our music, too. This jukebox will be personalized, dynamic, and quite interesting. Both Last.fm and YouTube's APIs offer their web services through REST, and YouTube additionally offers an XML-RPC interface. Like with previous APIs, XML is returned with each service call. Last.fm returns either plain text, an XML playlist format called XSPF (XML Shareable Playlist Format), or RSS (Really Simple Syndication). In the case of YouTube, the service returns a proprietary format. Previously, we wrote our own SAX-based XML parser to extract XML data. In this article, we will take a look at how PEAR, the PHP Extension and Application Repository, can do the XSPF parsing work for us on this project and might help in other projects. Let's take a look at the various data formats we will be using, and then the web services themselves. XSPF One of XML's original goals was to allow industries to create their own markup languages to exchange data. Because anyone can create their own elements and schemas, as long as people agreed on a format, XML can be used as the universal data transmission language for that industry. One of the earliest XML-based languages was ChemXML, a language used to transmit data within the chemical industry. Since then, many others have popped up. XSPF was a complete grassroots project to create an open, non-proprietary music playlist format based on XML. Historically, playlists for software media players and music devices were designed to be used only on the machine or device, and schemas were designed by the vendor themselves. XSPF's goal was to create a format that could be used in software, devices, and across networks. XSPF is a very simple format, and is easy to understand. The project home page is at http://www.xspf.org. There, you will find a quick start guide which outlines a simple playlist as well as the official specifications at http://www.xspf.org/specs. Basically, a typical playlist has the following structure: <?xml version="1.0" encoding="UTF-8"?><playlist version="1" > <title>Shu Chow's Playlist</title> <date>2006-11-24T12:01:21Z</data> <trackList> <track> <title>Pure</title> <creator>Lightning Seeds</creator> <location> file:///Users/schow/Music/Pure.mp3 </location> </track> <track> <title>Roadrunner</title> <creator>The Modern Lovers</creator> <location> file:///Users/schow/Music/Roadrunner.mp3 </location> </track> <track> <title>The Bells</title> <creator>April Smith</creator> <location> file:///Users/schow/Music/The_Bells.mp3 </location> </track> </trackList></playlist> playlist is the parent element for the whole document. It requires one child element, trackList, but there can be several child elements that are the metadata for the playlist itself. In this example, the playlist has a title specified in the title element, and the creation date is specified in the date element. Underneath trackList are the individual tracks that make up the playlist. Each track is encapsulated by the track element. Information about the track, including the location of its file, is encapsulated in elements underneath track. In our example, each track has a title, an artist name, and a local file location. The official specifications allow for more track information elements such as track length and album information. Here are the playlist child elements summarized: Playlist Child Element Required? Description trackList Yes The parent of individual track elements. This is the only required child element of a playlist. Can be empty if the playlist has no songs. title No A human readable title of the XSPF playlist. creator No The name of the playlist creator. annotation No Comments on the playlist. info No A URL to a page containing more information about the playlist. location No The URL to the playlist itself. identifier No The unique ID for the playlist. Must be a legal Uniform Resource Name (URN). image No A URL to an image representing the playlist. date No The creation (not the last modified!) date of the playlist. Must be in XML schema dateTime format. For example, "2004-02-27T03:30:00". license No If the playlist is under a license, the license is specified with this element. attribution No If the playlist is modified from another source, the attribution element gives credit back to the original source, if necessary. link No Allows non-XSPF resources to be included in the playlist. meta No Allows non-XSPF metadata to be included in the playlist. extension No Allows non-XSPF XML extensions to be included in the playlist. A trackList element has an unlimited number of track elements to represent each track. track is the only allowed child of trackList. track's child elements give us information about each track. The following table summarizes the children of track: Track Child Element Required? Description location No The URL to the audio file of the track. identifier No The canonical ID for the playlist. Must be a legal URN. title No A human readable title of the track. Usually, the song's name. creator No The name of the track creator. Usually, the song's artist. annotation No Comments on the track. info No A URL to a page containing more information about the track. image No A URL to an image representing the track. album No The name of the album that the track belongs to. trackNum No The ordinal number position of the track in the album. duration No The time to play the track in milliseconds. link No Allows non-XSPF resources to be included in the track. meta No Allows non-XSPF metadata to be included in the track. extension No Allows non-XSPF XML extensions to be included in the track. Note that XSPF is very simple and track oriented. It was not designed to be a repository or database for songs. There are not a lot of options to manipulate the list. XSPF is merely a shareable playlist format, and nothing more. RSS The simplest answer to, "What is RSS?", is that it's an XML file used to publish frequently updated information, like news items, blogs entries, or links to podcast episodes. News sites like Slashdot.org and the New York Times provide their news items in RSS format. As new news items are published, they are added to the RSS feed. Being XML-based, third-party aggregator software makes reading news items easy. With one piece of software, I can tell it to grab feeds from various sources and read the news items in one location. Web applications can also read and parse RSS files. By offering an RSS feed for my blog, another site can grab the feed and keep track of my daily life. This is one way by which a small site can provide rudimentary web services with minimal investment. The more honest answer is that it is a group of XML standards (used to publish frequently updated information like news items or blogs) that may have little compatibility with each other. Each version release also has a tale of conflict and strife behind it. We won't dwell on the politicking of RSS. We'll just look at the outcomes. The RSS world now has three main flavors: The RSS 1.0 branch includes versions 0.90, 1.0, and 1.1. It's goal is to be extensible and flexible. The downside to the goals is that it is a complex standard. The RSS 2.0 branch includes versions 0.91, 0.92, and 2.0.x. Its goal is to be simple and easy to use. The drawback to this branch is that it may not be powerful enough for complex sites and feeds. There are some basic skeletal similarities between the two formats. After the XML root element, metadata about the feed itself is provided in a top section. After the metadata, one or more items follow. These items can be news stories, blog entries, or podcasts episodes. These items are the meat of an RSS feed. The following is an example RSS 1.1 file from XML.com: <Channel rdf_about="http://www.xml.com/xml/news.rss"> <title>XML.com</title> <link>http://xml.com/pub</link> <description> XML.com features a rich mix of information and services for the XML community. </description> <image rdf_parseType="Resource"> <title>XML.com</title> <url>http://xml.com/universal/images/xml_tiny.gif</url> </image> <items rdf_parseType="Collection"> <item rdf_about= "http://www.xml.com/pub/a/2005/01/05/restful.html"> <title> The Restful Web: Amazon's Simple Queue Service </title> <link> http://www.xml.com/pub/a/2005/01/05/restful.html </link> <description> In Joe Gregorio's latest Restful Web column, he explains that Amazon's Simple Queue Service, a web service offering a queue for reliable storage of transient messages, isn't as RESTful as it claims. </description> </item> <item rdf_about= "http://www.xml.com/pub/a/2005/01/05/tr-xml.html"> <title> Transforming XML: Extending XSLT with EXSLT </title> <link> http://www.xml.com/pub/a/2005/01/05/tr-xml.html </link> <description> In this month's Transforming XML column, Bob DuCharme reports happily that the promise of XSLT extensibility via EXSLT has become a reality. </description> </item> </items></Channel> The root element of an RSS file is an element named Channel. Immediately, after the root element are elements that describe the publisher and the feed. The title, link, description, and image elements give us more information about the feed. The actual content is nested in the items element. Even if there are no items in the feed, the items element is required, but will be empty. Usage of these elements can be summarized as follows: Channel Child Element Required? Description title Yes A human readable title of the channel. link Yes A URL to the feed. description Yes A human readable description of the feed. items Yes A parent element to wrap around item elements. image No A section to house information about an official image for the feed. others No Any other elements not in the RSS namespace can be optionally included here. The namespace must have been declared earlier, and the child elements must be prefixed. If used, the image element needs its own child elements to hold information about the feed image. A title element is required and while optional, a link element to the actual URL of the image would be extremely useful. Each news blog, or podcast entry is represented by an item element. In this RSS file, each item has a title, link, and a description, each, represented by the respective element. This file has two items in it before the items and Channel elements are closed off.
Read more
  • 0
  • 0
  • 2471

article-image-jquery-14-dom-insertion-methods
Packt
19 Feb 2010
8 min read
Save for later

jQuery 1.4 DOM Insertion Methods

Packt
19 Feb 2010
8 min read
DOM insertion, inside These methods allow us to insert new content inside an existing element. .prepend() Insert content specified by the parameter at the beginning of each element in the set of matched elements. .prepend(content).prepend(function) Parameters (first version) content: An element, an HTML string, or a jQuery object to insert at the beginning of each element in the set of matched elements Parameters (second version) function: A function that returns an HTML string to insert at the beginning of each element in the set of matched elements Return value The jQuery object, for chaining purposes. Description The .prepend() and .prependTo() methods perform the same task. The major difference is in the syntax, specifically in the placement of the content and target. With .prepend(), the selector expression preceding the method is the container into which the content is inserted. With .prependTo(), on the other hand, the content precedes the method either as a selector expression or as markup created on the fly. It is then inserted into the target container. Consider the following HTML code: <h2>Greetings</h2> <div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> We can create content and insert it into several elements at once. $('.inner').prepend('<p>Test</p>'); Each <div class="inner"> element gets the following new content: <h2>Greetings</h2> <div class="container"> <div class="inner"> <p>Test</p> Hello </div> <div class="inner"> <p>Test</p> Goodbye </div></div> We can also select an element on the page and insert it into another: $('.container').prepend($('h2')); If an element selected this way is inserted elsewhere, it will be moved into the target (not cloned). <div class="container"> <h2>Greetings</h2> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> However, if there are more than one target elements, cloned copies of the inserted elements will be created for each target after the first. .prependTo() Insert every element in the set of matched elements at the beginning of the target. .prependTo(target) Parameters target: A selector, element, HTML string, or jQuery object; the matched set of elements will be inserted at the beginning of the element(s) specified by this parameter Return value The jQuery object, for chaining purposes. Description The .prepend() and .prependTo() methods perform the same task. The major difference is in the syntax, specifically in the placement of the content and target. With .prepend(), the selector expression preceding the method is the container into which the content is inserted. With .prependTo(), on the other hand, the content precedes the method either as a selector expression or as markup created on the fly, and is inserted into the target container. Consider the following HTML code: <h2>Greetings</h2> <div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> We can create content and insert it into several elements at once. $('<p>Test</p>').prependTo('.inner'); Each inner <div> element gets the following new content: <h2>Greetings</h2><div class="container"> <div class="inner"> <p>Test</p> Hello </div> <div class="inner"> <p>Test</p> Goodbye </div></div> We can also select an element on the page and insert it into another. $('h2').prependTo($('.container')); If an element selected this way is inserted elsewhere, it will be moved into the target (not cloned). <div class="container"> <h2>Greetings</h2> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> However, if there are more than one target elements, cloned copies of the inserted elements will be created for each target after the first. .append() Insert content specified by the parameter at the end of each element in the set of matched elements. .append(content).append(function) Parameters (first version) content: An element, an HTML string, or a jQuery object to insert at the end of each element in the set of matched elements Parameters (second version) function: A function that returns an HTML string to insert at the end of each element in the set of matched elements Return value The jQuery object, for chaining purposes. Description The .append() and .appendTo() methods perform the same task. The major difference is in the syntax, specifically in the placement of the content and target. With .append(), the selector expression preceding the method is the container into which the content is inserted. With .appendTo(), on the other hand, the content precedes the method either as a selector expression or as markup created on the fly, and is inserted into the target container. Consider the following HTML code: <h2>Greetings</h2><div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> We can create content and insert it into several elements at once. $('.inner').append('<p>Test</p>'); Each inner <div> element gets the following new content: <h2>Greetings</h2><div class="container"> <div class="inner"> Hello <p>Test</p> </div> <div class="inner"> Goodbye <p>Test</p> </div></div> We can also select an element on the page and insert it into another. $('.container').append($('h2')); If an element selected this way is inserted elsewhere, it will be moved into the target (not cloned). <div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div> <h2>Greetings</h2></div> However, if there is more than one target element, cloned copies of the inserted elements will be created for each target after the first. .appendTo() Insert every element in the set of matched elements at the end of the target. .appendTo(target) Parameters target: A selector, element, HTML string, or jQuery object; the matched set of elements will be inserted at the end of the element(s) specified by this parameter Return value The jQuery object, for chaining purposes. Description The .append() and .appendTo() methods perform the same task. The major difference is in the syntax, specifically in the placement of the content and target. With .append(), the selector expression preceding the method is the container into which the content is inserted. With .appendTo(), on the other hand, the content precedes the method either as a selector expression or as markup created on the fly, and is inserted into the target container. Consider the following HTML code: <h2>Greetings</h2><div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div></div> We can create content and insert it into several elements at once. $('<p>Test</p>').appendTo('.inner'); Each inner <div> element gets the following new content: <h2>Greetings</h2> <div class="container"> <div class="inner"> Hello <p>Test</p> </div> <div class="inner"> Goodbye <p>Test</p> </div></div> We can also select an element on the page and insert it into another. $('h2').append($('.container')); If an element selected this way is inserted elsewhere, it will be moved into the target (not cloned). <div class="container"> <div class="inner">Hello</div> <div class="inner">Goodbye</div> <h2>Greetings</h2></div> However, if there are more than one target elements, cloned copies of the inserted elements will be created for each target after the first.
Read more
  • 0
  • 0
  • 1911
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
article-image-drupal-6-performance-optimization-using-throttle-and-devel-module
Packt
19 Feb 2010
6 min read
Save for later

Drupal 6 Performance Optimization Using Throttle and Devel Module

Packt
19 Feb 2010
6 min read
Enabling and configuring the Throttle module Drupal allows you to control when your modules and blocks get enabled and shown to your site visitors. This helps you to prevent bottlenecks in your server's web traffic and to optimize your server load to prevent any congestion that it might experience with its bandwidth and traffic. Throttling blocks and modules becomes increasingly important on larger scale websites where you have many blocks and modules active. You may have a site that contains a large number of blocks, for example, that have been built with the Views module. You can throttle these blocks, so they only get enabled when the site visitor calls a page that is supposed to show that block. The throttle module allows you to configure it, so it automatically gets enabled when the usage of your site goes above a certain threshold. For example, this can be the number of anonymous users visiting your site. When a certain amount of visitors are on your site, you can have Drupal enable throttling. Using the Throttle module is essential on shared servers where you may not have all of the resources on the server made available to you at any given time or on a server that gives you limited CPU resources and bandwidth. You may not need to use Throttle on higher performance-dedicated servers because they will most likely be providing you with good performance. But on shared servers it does become important to use Throttle. If you did not enable the Throttle module after we upgraded our site to Drupal 6.13, we need to enable it first. Once enabled, we can then configure the module. Follow these steps: Under your Core-optional module list, check the box next to Throttle and then save your module configuration. There are two methods of accessing the Throttle module configuration. You can visit the main Throttle configuration page to set auto throttling settings for your site. Also, you can enable throttling for each module and block on your site. We'll look at both methods now. Note that your modules admin page explains how to access and enable both types of throttling (module and auto) at the top of the page in its introductory help text. You will only see your module throttle checkboxes available if you have enabled the Throttle module first. Configuring the Throttle module for auto throttling features Go to Site configuration | Throttle to load your Throttle module settings form or click on the throttle configuration page link through your main modules admin page. The Throttle configuration page explains what the Throttle module does and gives you a link to more information through the more help link. If you click on that link and have the Advanced Help module active, you will launch a detailed Throttle module help and explanation page. On this page you can configure three throttle elements that fall under the default Throttle module congestion control feature: Auto-throttle on anonymous users Auto-throttle on authenticated users Auto-throttle probability limiter Auto-throttle on anonymous users allows you to set a threshold for enabling your congestion control throttle dependent on anonymous user activity. So, for example, you will want to choose a threshold number of anonymous users to enter into this field. When this number of anonymous users is reached, the auto-throttle feature will be enabled. If you want the auto-throttle to be enabled after 250 anonymous users are on your site at the same time, you can type 250 into this field. Set this field to 0 if you do not want to use the auto-throttle feature. Drupal also tells you here that you can determine how many users are on your site at any given time by enabling the Who's Online block—this will show you all of the anonymous users who are browsing your site and authenticated users who are logged into your site. The Auto-throttle on authenticated users works using the same method. Add the threshold number of authenticated users that you want logged into your site before the point your throttle gets enabled. We'll set this to 50 authenticated users. The Auto-throttle probability limiter helps to reduce the overhead of the auto-throttle module. It's a built-in performance check just for this Throttle module. You can set a pe rcentage of page views for your site. For example, if you set the page view percentage to 10%, then the module will only perform extra database queries to update the Throttle module status once for every 10 page views. Drupal tells you that the busier your website, the lower you want to set this value. Leave it set to the default of 10%. Save your auto-throttle configuration. Throttling your modules You can also throttle each of your core and contributed modules as long as they have a Throttle checkbox next to their line item on the modules admin page. Load your modules admin page and look for the Throttle checkboxes. This allows you to tell Drupal to throttle a specific module during high traffic periods on your site. This means that when your site reaches a high traffic threshold (based on the auto throttling settings you determined above) your site will temporarily disable the module in question. This will throttle the module until your site returns to a stable status. You do need to be careful here. You should throttle those modules that are of lesser importance when your site reaches its threshold of user activity. When you throttle, you are temporarily disabling the module, so it will also temporarily disable that module's functionality during high server loads. So, you may want to disable some modules, such as Views, but leave your CCK module enabled so that your users can still see the content that is being filtered into the View. We'll go ahead and throttle the following modules: Administration menu (because this module is only being used by our logged in admins) Chaos tool suite (all submodules here) Comment Contact Database logging Help PHP filter Search Statistics Advanced Help FCKEditor IMCE Lightbox2 Poormanscron You can select more modules to throttle based on your preferences and the usage of your site. Use the above as an example and model to follow. Check the throttle boxes and save your configuration. During the next high server load period, these modules will be disabled temporarily to increase the performance of your site during its high server load period. Throttling blocks You can also throttle your blocks. To do this, go to the Blocks admin page here: Administer | Site building | Blocks. You'll notice that there is a new checkbox selection for Throttle. You can choose which blocks to throttle by checking the Throttle checkbox next to each of your enabled blocks. We'll go ahead and throttle all of our blocks except for the User login, as we still want to allow users to login to the site during high traffic periods. The throttle functionality works the same here as it does with modules. These blocks will be temporarily disabled during high site traffic. Once you check your throttle boxes, save your blocks configuration. The next time you have high site traffic, these blocks will be temporarily disabled.
Read more
  • 0
  • 0
  • 2869

article-image-ajax-form-validation-part-1
Packt
18 Feb 2010
4 min read
Save for later

AJAX Form Validation: Part 1

Packt
18 Feb 2010
4 min read
The server is the last line of defense against invalid data, so even if you implement client-side validation, server-side validation is mandatory. The JavaScript code that runs on the client can be disabled permanently from the browser's settings and/or it can be easily modified or bypassed. Implementing AJAX form validation The form validation application we will build in this article validates the form at the server side on the classic form submit, implementing AJAX validation while the user navigates through the form. The final validation is performed at the server, as shown in Figure 5-1: Doing a final server-side validation when the form is submitted should never be considered optional. If someone disables JavaScript in the browser settings, AJAX validation on the client side clearly won't work, exposing sensitive data, and thereby allowing an evil-intentioned visitor to harm important data on the server (for example, through SQL injection). Always validate user input on the server. As shown in the preceding figure, the application you are about to build validates a registration form using both AJAX validation (client side) and typical server-side validation: AJAX-style (client side): It happens when each form field loses focus (onblur). The field's value is immediately sent to and evaluated by the server, which then returns a result (0 for failure, 1 for success). If validation fails, an error message will appear and notify the user about the failed validation, as shown in Figure 5-3. PHP-style (server side): This is the usual validation you would do on the server—checking user input against certain rules after the entire form is submitted. If no errors are found and the input data is valid, the browser is redirected to a success page, as shown in Figure 5-4. If validation fails, however, the user is sent back to the form page with the invalid fields highlighted, as shown in Figure 5-3. Both AJAX validation and PHP validation check the entered data against our application's rules: Username must not already exist in the database Name field cannot be empty A gender must be selected Month of birth must be selected Birthday must be a valid date (between 1-31) Year of birth must be a valid year (between 1900-2000) The date must exist in the number of days for each month (that is, there's no February 31) E-mail address must be written in a valid email format Phone number must be written in standard US form: xxx-xxx-xxxx The I've read the Terms of Use checkbox must be selected Watch the application in action in the following screenshots: XMLHttpRequest, version 2 We do our best to combine theory and practice, before moving on to implementing the AJAX form validation script, we'll have another quick look at our favorite AJAX object—XMLHttpRequest. On this occasion, we will step up the complexity (and functionality) a bit and use everything we have learned until now. We will continue to build on what has come before as we move on; so again, it's important that you take the time to be sure you've understood what we are doing here. Time spent on digging into the materials really pays off when you begin to build your own application in the real world. Our OOP JavaScript skills will be put to work improving the existing script that used to make AJAX requests. In addition to the design that we've already discussed, we're creating the following features as well: Flexible design so that the object can be easily extended for future needs and purposes The ability to set all the required properties via a JSON object We'll package this improved XMLHttpRequest functionality in a class named XmlHttp that we'll be able to use in other exercises as well. You can see the class diagram in the following screenshot, along with the diagrams of two helper classes: settings is the class we use to create the call settings; we supply an instance of this class as a parameter to the constructor of XmlHttp complete is a callback delegate, pointing to the function we want executed when the call completes The final purpose of this exercise is to create a class named XmlHttp that we can easily use in other projects to perform AJAX calls. With our goals in mind, let's get to it! Time for action – the XmlHttp object In the ajax folder, create a folder named validate, which will host the exercises in this article.
Read more
  • 0
  • 0
  • 6943

article-image-web-scraping-python
Packt
17 Feb 2010
5 min read
Save for later

Web Scraping with Python

Packt
17 Feb 2010
5 min read
To perform this task, usually three basic steps are followed: Explore the website to find out where the desired information is located in the HTML DOM tree Download as many web pages as needed Parse downloaded web pages and extract the information from the places found in the exploration step The exploration step is performed manually with the aid of some tools that make it easier to locate the information and reduce the development time in next steps. The download and parsing steps are usually performed in an iterative cycle since they are interrelated. This is because the next page to download may depend on a link or similar in the current page, so not every web page can be downloaded without previously looking into the earlier one. This article will show an example covering the three steps mentioned and how this could be done using python with some development. The code that will be displayed is guaranteed to work at the time of writing, however it should be taken into account that it may stop working in future if the presentation format changes. The reason is that web scraping depends on the DOM tree to be stable enough, that is to say, as happens with regular expressions, it will work fine for slight changes in the information being parsed. However, when the presentation format is completely changed, the web scraping scripts have to be modified to match the new DOM tree. Explore Let's say you are a fan of Pack Publishing article network and that you want to keep a list of the titles of all the articles that have been published until now and the link to them. First of all, you will need to connect to the main article network page (http://www.packtpub.com/article-network) and start exploring the web page to have an idea about where the information that you want to extract is located. Many ways are available to perform this task such as view the source code directly in your browser or download it and inspect it with your favorite editor. However, HTML pages often contain auto-generated code and are not as readable as they should be, so using a specialized tool might be quite helpful. In my opinion, the best one for this task is the Firebug add-on for the Firefox browser. With this add-on, instead of looking carefully in the code looking for some string, all you have to do is press the Inspect button, move the pointer to the area in which you are interested and click. After that, the HTML code for the area marked and the location of the tag in the DOM tree will be clearly displayed. For example, the links to the different pages containing all the articles are located inside a right tag, and, in every page, the links to the articles are contained as list items in an unnumbered list. In addition to this, the links URLs, as you probably have noticed while reading other articles, start with http://www.packtpub.com/article/ So, our scraping strategy will be Get the list of links to all pages containing articles Follow all links so as to extract the article information in all pages One small optimization here is that main article network page is the same as the one pointed by the first page link, so we will take this into account to avoid loading the same page twice when we develop the code. Download Before parsing any web page, the contents of that page must be downloaded. As usual, there are many ways to do this: Creating your own HTTP requests using urllib2 standard python library Using a more advanced library that provides the capability to navigate through a website simulating a browser such as  mechanize. In this article mechanize will be covered as it is the easiest choice. mechanize is a library that provides a Browser class that lets the developer to interact with a website in a similar way a real browser would. In particular it provides methods to open pages, follow links, change form data and submit forms. Recalling the scraping strategy in our previous version, the first thing we would like to do is to download the main article network web page. To do that we will create a Browser class instance and then open the main article network page: >>> import mechanize>>> BASE_URL = "http://www.packtpub.com/article-network">>> br = mechanize.Browser()>>> data = br.open(BASE_URL).get_data()>>> links = scrape_links(BASE_URL, data) Where the result of the open method is an HTTP response object, the get_data method returns the contents of the web page. The scrape_links function will be explained later. For now, as pointed out in the introduction section, bear in mind that the downloading and parsing steps are usually performed iteratively since some contents to be downloaded depends on the parsing done in some kind of initial contents such as in this case.
Read more
  • 0
  • 0
  • 9367

article-image-author-podcast-aleksander-seovic-talks-about-oracle-coherence-35
Packt
17 Feb 2010
1 min read
Save for later

Author Podcast - Aleksander Seovic Talks About Oracle Coherence 3.5

Packt
17 Feb 2010
1 min read
Aleksander Seovic is the author of Oracle Coherence 3.5, which will help you to design and build scalable, reliable, high-performance applications using software of the same name. The book is due out in March, but you can get a flavour of it in his interview with Cameron Purdy, below. For more information on Aleksander's book, visit: http://www.packtpub.com/oracle-coherence-3-5/book. Listen Here      
Read more
  • 0
  • 0
  • 3361
article-image-forms-grok-10
Packt
12 Feb 2010
13 min read
Save for later

Forms in Grok 1.0

Packt
12 Feb 2010
13 min read
A quick demonstration of automatic forms Let's start by showing how this works, before getting into the details. To do that, we'll add a project model to our application. A project can have any number of lists associated with it, so that related to-do lists can be grouped together. For now, let's consider the project model by itself. Add the following lines to the app.py file, just after the Todo application class definition. We'll worry later about how this fits into the application as a whole. class IProject(interface.Interface): name = schema.TextLine(title=u'Name',required=True) kind = schema.Choice(title=u'Kind of project', values=['personal','business']) description = schema.Text(title=u'Description')class AddProject(grok.Form): grok.context(Todo) form_fields = grok.AutoFields(IProject) We'll also need to add a couple of imports at the top of the file: from zope import interfacefrom zope import schema Save the file, restart the server, and go to the URL http://localhost:8080/todo/addproject. The result should be similar to the following screenshot: OK, where did the HTML for the form come from? We know that AddProject is some sort of a view, because we used the grok.context class annotation to set its context and name. Also, the name of the class, but in lowercase, was used in the URL, like in previous view examples. The important new thing is how the form fields were created and used. First, a class named IProject was defined. The interface defines the fields on the form, and the grok.AutoFields method assigns them to the Form view class. That's how the view knows which HTML form controls to generate when the form is rendered. We have three fields: name, description, and kind. Later in the code, the grok.AutoFields line takes this IProject class and turns these fields into form fields. That's it. There's no need for a template or a render method. The grok.Form view takes care of generating the HTML required to present the form, taking the information from the value of the form_fields attribute that the grok.AutoFields call generated. Interfaces The I in the class name stands for Interface. We imported the zope.interface package at the top of the file, and the Interface class that we have used as a base class for IProject comes from this package. Example of an interface An interface is an object that is used to specify and describe the external behavior of objects. In a sense, the interface is like a contract. A class is said to implement an interface when it includes all of the methods and attributes defined in an interface class. Let's see a simple example: from zope import interfaceclass ICaveman(interface.Interface): weapon = interface.Attribute('weapon') def hunt(animal): """Hunt an animal to get food""" def eat(animal): """Eat hunted animal""" def sleep() """Rest before getting up to hunt again""" Here, we are describing how cavemen behave. A caveman will have a weapon, and he can hunt, eat, and sleep. Notice that the weapon is an attribute—something that belongs to the object, whereas hunt, eat, and sleep are methods. Once the interface is defined, we can create classes that implement it. These classes are committed to include all of the attributes and methods of their interface class. Thus, if we say: class Caveman(object): interface.implements(ICaveman) Then we are promising that the Caveman class will implement the methods and attributes described in the ICaveman interface: weapon = 'ax'def hunt(animal): find(animal) hit(animal,self.weapon)def eat(animal): cut(animal) bite()def sleep(): snore() rest() Note that though our example class implements all of the interface methods, there is no enforcement of any kind made by the Python interpreter. We could define a class that does not include any of the methods or attributes defined, and it would still work. Interfaces in Grok In Grok, a model can implement an interface by using the grok.implements method. For example, if we decided to add a project model, it could implement the IProject interface as follows: class Project(grok.Container): grok.implements(IProject) Due to their descriptive nature, interfaces can be used for documentation. They can also be used for enabling component architectures, but we'll see about that later on. What is of more interest to us right now is that they can be used for generating forms automatically. Schemas The way to define the form fields is to use the zope.schema package. This package includes many kinds of field definitions that can be used to populate a form. Basically, a schema permits detailed descriptions of class attributes that are using fields. In terms of a form—which is what is of interest to us here—a schema represents the data that will be passed to the server when the user submits the form. Each field in the form corresponds to a field in the schema. Let's take a closer look at the schema we defined in the last section: class IProject(interface.Interface): name = schema.TextLine(title=u'Name',required=True) kind = schema.Choice(title=u'Kind of project', required=False, values=['personal','business']) description = schema.Text(title=u'Description', required=False) The schema that we are defining for IProject has three fields. There are several kinds of fields, which are listed in the following table. In our example, we have defined a name field, which will be a required field, and will have the label Name beside it. We also have a kind field, which is a list of options from which the user must pick one. Note that the default value for required is True, but it's usually best to specify it explicitly, to avoid confusion. You can see how the list of possible values is passed statically by using the values parameter. Finally, description is a text field, which means it will have multiple lines of text. Available schema attributes and field types In addition to title, values, and required, each schema field can have a number of properties, as detailed in the following table: Attribute Description title A short summary or label. description A description of the field. required Indicates whether a field requires a value to exist. readonly If True, the field's value cannot be changed. default The field's default value may be None, or a valid field value. missing_value If input for this field is missing, and that's OK, then this is the value to use. order The order attribute can be used to determine the order in which fields in a schema are defined. If one field is created after another (in the same thread), its order will be greater. In addition to the field attributes described in the preceding table, some field types provide additional attributes. In the previous example, we saw that there are various field types, such as Text, TextLine, and Choice. There are several other field types available, as shown in the following table. We can create very sophisticated forms just by defining a schema in this way, and letting Grok generate them. Field type Description Parameters Bool Boolean field.   Bytes Field containing a byte string (such as the python str). The value might be constrained to be within length limits.   ASCII Field containing a 7-bit ASCII string. No characters > DEL (chr(127)) are allowed. The value might be constrained to be within length limits.   BytesLine Field containing a byte string without new lines.   ASCIILine Field containing a 7-bit ASCII string without new lines.   Text Field containing a Unicode string.   SourceText Field for the source text of an object.   TextLine Field containing a Unicode string without new lines.   Password Field containing a Unicode string without new lines, which is set as the password.   Int Field containing an Integer value.   Float Field containing a Float.   Decimal Field containing a Decimal.   DateTime Field containing a DateTime.   Date Field containing a date.   Timedelta Field containing a timedelta.   Time Field containing time.   URI A field containing an absolute URI.   Id A field containing a unique identifier. A unique identifier is either an absolute URI or a dotted name. If it's a dotted name, it should have a module or package name as a prefix.   Choice Field whose value is contained in a predefined set. values: A list of text choices for the field. vocabulary: A Vocabulary object that will dynamically produce the choices. source: A different, newer way to produce dynamic choices. Note: only one of the three should be provided. More information about sources and vocabularies is provided later in this book. Tuple Field containing a value that implements the API of a conventional Python tuple. value_type: Field value items must conform to the given type, expressed via a field. Unique. Specifies whether the members of the collection must be unique. List Field containing a value that implements the API of a conventional Python list. value_type: Field value items must conform to the given type, expressed via a field. Unique. Specifies whether the members of the collection must be unique. Set Field containing a value that implements the API of a conventional Python standard library sets.Set or a Python 2.4+ set. value_type: Field value items must conform to the given type, expressed via a field. FrozenSet Field containing a value that implements the API of a conventional Python2.4+ frozenset. value_type: Field value items must conform to the given type, expressed via a field. Object Field containing an object value. Schema: The interface that defines the fields comprising the object. Dict Field containing a conventional dictionary. The key_type and value_type fields allow specification of restrictions for keys and values contained in the dictionary. key_type: Field keys must conform to the given type, expressed via a field. value_type: Field value items must conform to the given type, expressed via a field. Form fields and widgets Schema fields are perfect for defining data structures, but when dealing with forms sometimes they are not enough. In fact, once you generate a form using a schema as a base, Grok turns the schema fields into form fields. A form field is like a schema field but has an extended set of methods and attributes. It also has a default associated widget that is responsible for the appearance of the field inside the form. Rendering forms requires more than the fields and their types. A form field needs to have a user interface, and that is what a widget provides. A Choice field, for example, could be rendered as a <select> box on the form, but it could also use a collection of checkboxes, or perhaps radio buttons. Sometimes, a field may not need to be displayed on a form, or a writable field may need to be displayed as text instead of allowing users to set the field's value. Form components Grok offers four different components that automatically generate forms. We have already worked with the first one of these, grok.Form. The other three are specializations of this one: grok.AddForm is used to add new model instances. grok.EditForm is used for editing an already existing instance. grok.DisplayForm simply displays the values of the fields. A Grok form is itself a specialization of a grok.View, which means that it gets the same methods as those that are available to a view. It also means that a model does not actually need a view assignment if it already has a form. In fact, simple applications can get away by using a form as a view for their objects. Of course, there are times when a more complex view template is needed, or even when fields from multiple forms need to be shown in the same view. Grok can handle these cases as well, which we will see later on. Adding a project container at the root of the site To get to know Grok's form components, let's properly integrate our project model into our to-do list application. We'll have to restructure the code a little bit, as currently the to-do list container is the root object of the application. We need to have a project container as the root object, and then add a to-do list container to it. To begin, let's modify the top of app.py, immediately before the TodoList class definition, to look like this: import grokfrom zope import interface, schemaclass Todo(grok.Application, grok.Container): def __init__(self): super(Todo, self).__init__() self.title = 'To-Do list manager' self.next_id = 0 def deleteProject(self,project): del self[project] First, we import zope.interface and zope.schema. Notice how we keep the Todo class as the root application class, but now it can contain projects instead of lists. We also omitted the addProject method, because the grok.AddForm instance is going to take care of that. Other than that, the Todo class is almost the same. class IProject(interface.Interface): title = schema.TextLine(title=u'Title',required=True) kind = schema.Choice(title=u'Kind of project',values=['personal', 'business']) description = schema.Text(title=u'Description',required=False) next_id = schema.Int(title=u'Next id',default=0) We then have the interface definition for IProject, where we add the title, kind, description, and next_id fields. These were the fields that we previously added during the call to the __init__ method at the time of product initialization. class Project(grok.Container): grok.implements(IProject) def addList(self,title,description): id = str(self.next_id) self.next_id = self.next_id+1 self[id] = TodoList(title,description) def deleteList(self,list): del self[list] The key thing to notice in the Project class definition is that we use the grok.implements class declaration to see that this class will implement the schema that we have just defined. class AddProjectForm(grok.AddForm): grok.context(Todo) grok.name('index') form_fields = grok.AutoFields(Project) label = "To begin, add a new project" @grok.action('Add project') def add(self,**data): project = Project() self.applyData(project,**data) id = str(self.context.next_id) self.context.next_id = self.context.next_id+1 self.context[id] = project return self.redirect(self.url(self.context[id])) The actual form view is defined after that, by using grok.AddForm as a base class. We assign this view to the main Todo container by using the grok.context annotation. The name index is used for now, so that the default page for the application will be the 'add form' itself. Next, we create the form fields by calling the grok.AutoFields method. Notice that this time the argument to this method call is the Project class directly, rather than the interface. This is possible because the Project class was associated with the correct interface when we previously used grok.implements. After we have assigned the fields, we set the label attribute of the form to the text: To begin, add a new project. This is the title that will be shown on the form. In addition to this new code, all occurrences of grok.context(Todo) in the rest of the file need to be changed to grok.context(Project), as the to-do lists and their views will now belong to a project and not to the main Todo application. For details, take a look at the source code of this article for Grok 1.0 Web Development>>Chapter 5.
Read more
  • 0
  • 0
  • 1881

article-image-trunks-using-3cx-part-2
Packt
12 Feb 2010
7 min read
Save for later

Trunks using 3CX: Part 2

Packt
12 Feb 2010
7 min read
The next wizard screen is for Outbound Call Rules. Let's go over it enough so that you can setup a simple rule. We start off with a name. This can be anything you like but I prefer something meaningful. For our example I want to dial 9 to use the analog line, and only allow extensions 100-102 to use this line. I also only want to be able to dial certain phone numbers. Then I have to delete the 9 before it goes out to the phone carrier. Let's have a look at each section of this screen: Calls to numbers starting with (Prefix) This is where you specify what you want someone to dial before the line is used. You could enter a string of numbers here to use as a "password" to dial out. You don't just let anyone call an international phone number, so set this to a string of numbers to use as your international password. Give the password only to those who need it. Just make sure you change it occasionally in case it slips out. Calls from extension(s) Now, you can specify who (by extension number) can use this gateway. Just enter the extension number(s) you want to allow either in a range (100-110), individually (100, 101, 104), or as a mix (100-103, 110). Usually, you will leave this open for everyone to use; otherwise, you will restrict extensions that were allowed to use the gateway, which will have repercussions of forwarding rules to external numbers. Calls to numbers with a length of This setting can be left blank if you want all calls to be able to go out on this gateway. In the next screenshot, I specified 3, 7, 10, and 11. This covers calls to 911, 411, 555-1234, 800-555-1234, and 1-800-555-1234, respectively. You can control what phone numbers go out based on the number of digits that are dialed. Route and strip options Since this is our only gateway right now, we will have it route the calls to the Patton gateway. The Strip Digits option needs to be set to 1. This will strip out the "9" that we specified above to dial out with. We can leave the Prepend section blank for now. Now, go ahead and click Finish: Once you click Finish, you will see a gateway wizard summary, as shown in the next screenshot. This shows you that the gateway is created, and it also gives an overview of the settings. Your next step is to get those settings configured on your gateway. There is a list of links for various supported gateways on the bottom of the summary page with up-to-date instructions. Feel free to visit those links. These links will take you to the 3CX website and explain how to configure that particular gateway. With Patton this is easy; click the Generate config file button. The only other information you need for the configuration file is the Subnet mask for the Patton gateway. Enter your network subnet mask in the box. Here, I entered a standard Class C subnet mask. This matches my 192.168.X.X network. Click OK when you are done: Once you click OK, your browser will prompt you to save the file, as shown in the following screenshot. Click Save: The following screenshot shows a familiar Save As Windows screen. I like to put this file in an easy-to-remember location on my hard drive. As I already have a 3CX folder created, I'm going to save the file there. You can change the name of the file if you wish. Click Save: Now that your file is saved, let's take a look at modifying those settings. Open the administration web interface and, on the left-hand side, click PSTN Devices. Go ahead and expand this by clicking the + sign next to it. Now, you will see our newly created Patton SN4114A gateway listed. Click the + sign again and expand that gateway. Next, click the Patton SN4114A name, and you will see the right-hand side window pane fill up with five separate tabs. The first tab is General. This is where you can change the gateway IP address, SIP port, and all the account details. If you change anything, you will need a new configuration file. So click the Generate config file button at the bottom of the screen. If you forgot to save the file previously, here's your chance to generate and save it again: On the Advanced tab, we have some Provider Capabilities. Leave these settings alone for now: We will leave the rest of the tabs for now. Go ahead and click the 10000 line information in the navigation pane on the left. These are the settings for that particular phone port (10000). The first group of settings that we can change is the authentication username and password. Remember, this is to register the line with 3CX and not to use the phone line. The next two sections are about what to do with an inbound call during Office Hours and Outside Office Hours. I didn't change anything from the gateway wizard but, on this screen, you can see that we selected Ring group 800 MainRingGroup. This is the Ring group that we configured previously. We also see similar drop-down boxes for Outside Office Hours. As no one will be in the office to answer the phone, I've selected a Digital Receptionist 801 DR1. In the section Other Options, the Outbound Caller ID box is used to enter what you would like to have presented to the outside world as caller ID information. If your phone carrier supports this, you can enter a phone number or a name. If the carrier does not support this, just leave it blank and talk to your carrier as to what you would require to have it assigned as your caller ID. The Allow outbound calls on this line and Allow incoming calls on this line checkboxes are used to limit calls in or out. Depending on your environment, you might want to leave one line selected as no outbound calls. This will always leave an incoming line for customers to call. Otherwise, unless you have other lines that they can call on, they will get a busy signal. Maximum simultaneous calls cannot be changed here as analog lines only support one call at a time. If you changed anything, click Apply and then go back and generate a new configuration file: For the most up-to-date information on configuring your gateway, visit the 3CX site: http://www.3cx.com/voip-gateways/index.html We will go over a summary of it here: Since nothing was changed, it is now time to configure the Patton device with the config file that we generated from the 3CX template. If you know the IP address of the device, go ahead and open a browser and navigate to that IP address. Mine would be http://192.168.2.10. If you do not know the IP address of your device, you will need the SmartNode discovery tool. The easiest place to get this tool is the CD that came with the device. You can also download it from http://www.3cx.com/downloads/misc/sndiscovery.zip, or search the Patton website for it. Go ahead and install the SmartNode discovery tool and run it. You will get a screen that tells you all the SmartNodes on your network with their IP address, MAC address, and firmware version. Double-click on the SmartNode to open the web interface in a browser. The default username is administrator, and the password field is left blank. Click Import/Export on the left and Import Configuration on the right. Click Browse to find the configuration file that we generated. Click Import and then Reload to restart the gateway with the new configuration. That's it . We can now get incoming calls and make an outbound call.
Read more
  • 0
  • 0
  • 2986

article-image-trunks-using-3cx-part-1
Packt
12 Feb 2010
11 min read
Save for later

Trunks using 3CX: Part 1

Packt
12 Feb 2010
11 min read
PSTN trunks A Public Switch Telephone Network (PSTN) trunk is an old fashioned analog Basic Rate Interface (BRI) ISDN or Primary Rate Interface (PRI) phone line. 3CX can use any of these with the correct analog to SIP gateway. Usually these come into your home or business through a pair of copper lines. Depending on where you live, this may be the only means of connecting 3CX and communicating outside of your network. One of the advantages of a PSTN line is reliability and great call quality. Unless the wires break, you will almost always have phone service. However, what about call quality? After all, many people would like to have a comparison between VoIP and PSTN. Analog hardware for BRI ISDN and PRI's will be discussed in greater detail in Chapter 9. For using an analog PSTN line, you will need an FXO gateway. There are many external ones available. Until Sangoma introduced a new line at the end of 2008, there had not been any gateway which worked inside a Windows PC with 3CX. There are many manufacturers of analog gateways such as Linksys, Audio-Codes, Patton Electronics, GrandStream, and Sangoma. What these FXO gateways do is convert the analog phone line into IP signaling. Then the IP signaling gets passed over your network to the 3CX server and your phones. My personal preference is Patton Electronics. They are probably the most expensive FXOs' out there, but in this case, you get what you pay for. I have tried all of them and they all work. Some have issues with echo which can be hard to get rid of without support, or lots of trial and error, whereas some cannot support high demands (40 calls/hour) without needing to be reset every day, so if you are just testing, get a low-end one. For a high demand business, my preference is Patton. Not only do they make great products, but their support is top notch too. We will configure a Patton SmartNode SN4114 later in this article. SIP trunks What is a SIP trunk? A SIP trunk is a call that is routed by IP over the Internet through an Internet Telephony Service Provider (ITSP). For enterprises wanting to make full use of their installed IP PBXs' and communicate over IP not only within the enterprise, but also outside the enterprise—a SIP trunk provided by an ITSP that connects to the traditional PSTN network is the solution. Unlike traditional telephony, where bundles of physical wires were once delivered from the service provider to a business, a SIP trunk allows a company to replace traditional fixed PSTN lines with PSTN connectivity via a SIP trunking service provider on the Internet. SIP trunks can offer significant cost savings for enterprises, eliminating the need for local PSTN gateways, costly ISDN BRI's or PRI's. The following figure is an example of how our phone system operates: You can see that we have a local area network containing our desktops, servers, phones, and our 3CX Phone System. To reach the outside world using a SIP trunk, we have to go through our firewall or router. Depending on your network, you could be using a private IP address (10.x.x.x, 172.16.x.x, or 192.168.x.x) which is not allowed on the public Internet, so it has to get translated to the public IP address. This translation process is called Network Address Translation (NAT). Once we get outside the local network, we are in the public realm. Our ITSP uses the internet to get our phone call to/from the various carriers PSTN (analog) lines where our phone call is connected/terminated. There are three components necessary to successfully deploy SIP trunks: A PBX with a SIP-enabled trunk side An enterprise edge device understanding SIP An Internet Telephony or SIP trunking service provider The PBX In most cases, the PBX is an IP-based PBX, communicating with all endpoints over IP. However, it may just as well be a traditional digital or analog PBX. The sole requirement that has to be available is an interface for SIP trunking connectivity. The enterprise border element The PBX on the LAN connects to the ITSP via the enterprise border element. The enterprise edge component can either be a firewall with complete support for SIP, or an edge device connected to the firewall handling the traversal of the SIP traffic. The ITSP On the Internet, the ITSP provides connectivity to the PSTN for communication with mobile and fixed phones. Choosing a VoIP carrier—more than just price I feel two of the most important features to look for when choosing a VoIP carrier is support and call quality. Usually once you setup and everything is working, you won't need support. I always tell clients that there is no "boxed" solution that I can sell, every installation is a little different. Internet connections are all different even with the same provider. If you have a rock-solid T1 or something better, then this shouldn't be a problem. DSL seems different from building to building, even in the same area. So how do you test support before giving them your credit card? Call them! Try calling support at the worst times such as Monday afternoons when everyone is back to work and online, also try calling after business hours. See how long does it take to connect to a live person and if you can understand them once you speak to them? Find where is their support located? Try talking to them and tell them you are thinking about signing up with their service and ask them for help. If they go out of their way before they have your money, chances are they will be good to work with later on. Some carriers only offer chat or email support in favor of lower prices. While this may work fine for your business, it certainly won't work for the ones who need answers right away. I know I seem to be stressing a lot on support but it's for good reason. If your business depends on phone service and it goes down then you need answers! I pay more for a product if the support is worth it. Part of this is your Return On Investment (ROI). For example, if you have 3 lawyers billing at $200/hour and they need phones to work, that's $600/hour of lost time. Does the extra $50 or $100 upfront cover that? Now back to the topic at hand. Once you have connected 3CX to the carrier, how is the call quality? If it sounds like a bad cell phone, you probably don't want it, unless the price is so cheap that you can live with the low quality. Certain carriers even change the way your call gets routed through the Internet, based on the lowest cost for the particular call. They don't care about quality as long as you get that connection and they make money on it. Concurrent calls with an ITSP are a feature that you may want to look for when choosing an ITSP. Some accounts are a one-to-one ratio of lines per call. If you want to have 5 people on the phone at the same time (inbound or outbound), you would need to pay for 5 lines, this is similar to a PSTN line. You may get some savings here over a PSTN but that depends on what is available in your area. Some ITSP's have concurrent calls where you can use more than one line per call. Not many carriers have this feature but for a small business, this can be a great cost saving feature to look for. I use a couple of different carriers that have this feature. One carrier that I use lets you have 3 concurrent calls simultaneously on the same line. If you need more than 3 calls, you're a higher use customer and they want you to buy several lines. VoIP IP signaling uses special algorithms to compress your voice into IP packets. This compression uses a codec. There are several available, but the most common one is G.711u-law or A-law. This uses about 80kpbs of upload and download bandwidth. Another popular codec is G.729, it uses about 36kpbs. So for the same bandwidth you can have twice the number of calls using G.729 than G.711. You will need to check with your ITSP and see what codec they support. Another carrier I use is based purely on how much internet bandwidth you have. If you have 1Mbps of upload speed (usually the slowest part of your internet connection), you can support about 10 simultaneous or concurrent calls using G.711. You then pay for the minutes you use. This works very well for a small office as your monthly bill is very low and you don't have to maintain a bunch of lines that don't get used. Cable internet providers are also offering VoIP service to your home or business. These are usually single-use lines but they terminate at your office with an FXS plug. To integrate this with 3CX, you will need an FXO just like it's a PSTN line, same setup but you get the advantage of a VoIP line. Another great benefit of a SIP trunk is expandability. You can easily start out with one line which can usually be completed in one day. As you grow you can add more, usually in minutes as you already have the plan setup. Time to consolidate lines? You can even drop them later on without having contracts (most of the time). Try doing that with the local phone company! Call for a new business and it can take 1-2 weeks to get set up, plus contracts to worry about. No wonder they are jumping on the VoIP band wagon. Disaster recovery What do you do when your internet goes down? Some of you might be saying, "Ha! It never goes down". In my experience, it will eventually, and at the worst time. So what do you do? Go home for the day or plan for a backup? Most VoIP carriers provide some kind of disaster recovery option. They try to send you a call and when they don't get a connection to your 3CX box; then then re-route the call to another phone number. This could be a PSTN line or even a cell phone. It can be a free feature or there can be a small monthly fee on the account. It's worth having, especially if you rely on phones. Okay, so that covers inbound disaster recovery. What about outbound? Yes just about everyone has a cell phone these days, if that isn't enough, I'd suggest you invest in a pay-per-use PSTN line. This keeps the monthly cost very low but it's there when you need it. Whether it's an emergency pizza order for that Friday afternoon party or a true emergency when someone panics and dials 911—you want that call to go out. Speaking of emergency numbers, make sure you have your carrier register that phone number to your local address. Let's say you are in New York and you have a Californian phone number to give you some local presence in that part of the country. Your co-worker grabs his chest and falls down and someone dials 911 from the closest phone they see. Emergency services see your Californian number and contacts California for help for your New York office, that's not what you want when someone is clutching their chest, even though it was just heartburn from that pepperoni pizza. Mixing VoIP and PSTN Some of my clients even mix VoIP and PSTN together. Why would you mix? Local calls and inbound calls use the PSTN lines for the best call quality (and do not use any VoIP minutes if they have to pay for those). Long distance calls use the cheaper rate VoIP line. Another scenario is using PSTN lines for all your incoming and outgoing calls and use VoIP to talk to your other offices. Your own office can deal with a lower call quality, and management will appreciate the lower cost. These types of setups can be controlled using a dial plan.
Read more
  • 0
  • 0
  • 3982
article-image-call-control-using-3cx
Packt
11 Feb 2010
9 min read
Save for later

Call Control using 3CX

Packt
11 Feb 2010
9 min read
Let's get started! Ring groups Ring groups are designed to direct calls to a group of extensions so that a person can answer the call. An incoming call will ring at several extensions at once, and the one who picks up the phone gets control of that call. At that point, he/she can transfer the call, send it to voicemail, or hang up. Ring groups are my preferred call routing method. Does anyone really like those automated greetings? I don't. We will of course, set those up because they do have some great uses. However, if you like your customers to get a real live voice when they call, you have two choices—either direct the call to an extension or use a ring group and have a few phones ring at once. To create a ring group, we will use the 3CX web interface. There are several ways to do this. From the top toolbar menu, click Add | Ring Group. In the following screenshot, I chose Add | Ring Group: The following screenshot shows another way of adding a ring group using the Ring Groups section in the navigation pane on the left-hand side. Then click on the Add Ring Group button on the toolbar: Once we click Add Ring Group, 3CX will automatically create a Virtual machine number for this ring group as shown in the next screenshot. This helps the system keep track of calls and where they are. This number can be changed to any unused number that you like. As a reseller, I like to keep them the same from client to client. This creates some standardization among all the systems. Now it's time to give the ring group a Name. Here I use MainRingGroup as it lets me know that when a call comes in, it should go to the Main Ring Group. After you create the first one, you can make more such as SalesRingGroup, SupportRingGroup, and so on. We now have three choices for the Ring Strategy: Prioritized Hunt: Starts hunting for a member from the top of the Ring Group Members list and works down until someone picks up the phone or goes to the Destination if no answer section. Ring All: If all the phones in the Ring Group Members section ring at the same time then the first person to pick up gets the call. Paging: This is a paid feature that will open the speakerphone on Ring Group Members. Now you will need to select your Ring Time (Seconds) to determine how long you want the phones to ring before giving up. The default ring time is 20 seconds, which all my clients agree is too long. I'd recommend 10-15 seconds, but remember, if no one picks up the phone, then the caller goes to the next step, such as a Digital Receptionist. If the next step also makes the caller wait another 10-20 seconds, he/she may just hang up. You also need to be sure that you do not exceed the phone company's timeout of diverting calls to their voicemail (which could be turned off) or returning a busy signal. Adding ring group members Ring Group Members are the extensions that you would like the system to call or page in a ring group. If you select the Prioritized Hunt strategy, it will hunt from the top and go down the list. Ring All and Paging will get everyone at once. The listbox on the left will show you a list of available extensions. Select the ones you want and click the Add button. If you are using Prioritized Hunt, you can change the order of the hunt by using the Up and Down buttons. Destination if no answer The last setting as shown in the next screenshot illustrates what to do when no one answers the call. The options are as follows: End Call: Just drop the call, no chance for the caller to talk to someone. Connect to Extension: Ring the extension of your choice. Connect to Queue / Ring Group: This sends the caller to a call queue (discussed later in the Call queues section)) or to another ring group. A second ring group could be created for stage two that calls the same group plus additional extensions. Connect to Digital Receptionist: As a person didn't pick up the call, we can now send it to an automated greeting/menu system. Voicemail box for Extension: As the caller has already heard phones ringing, you may just want to put him/her straight to someone's voicemail. Forward to Outside Number: If you have had all the phones in the building ringing and no one has picked up, then you might want to send the caller to a different phone outside of your PBX system. Just make sure that you enter the correct phone number and any area codes that may be required. This will use another simultaneous call license and another phone line. If you have one line only, then this is not the option you can use. Digital Receptionist setup A Digital Receptionist (DR) is not a voicemail box; it's an automated greeting with a menu of choices to choose from. A DR will answer the phone for you if no one is available to answer the phone (directly to an extension or hunt group) or if it is after office hours. You need to set up a DR unless you want all incoming calls to go to someone's voicemail. You will also need it if you want to present the caller with a menu of options. Let's see how to create a DR. Recording a menu prompt The first thing you need to do in order to create a DR is record a greeting. There are a couple of ways to do this. However, first let's create the greeting script. In this greeting, you will be defining your phone menu; that is, you will be directing calls to extensions, hunts, agent groups, and the dial by name directory. Following is an example: Thank you for calling. If you know your party's extension, you may dial it at any time. Or else, please listen to the following options: For Rob, dial 1 For the sales group, dial 2 For Zachary, dial 4 Solicitors, please dial 8 For a dial by name directory, dial 9 I suggest having it written down. This makes it easier to record and also gives the person setting up the DR in 3CX a copy of the menu map. Now that you know what you want your callers to hear when they call, it's time to get it recorded so that we can import it into 3CX. You have a couple of options for recording the greeting script. It doesn't matter which option you use or how you obtain this greeting file, as long as the end format is correct. You can hire a professional announcer, put it to music, and obtain the file from him/her. You can record it using any audio software you like such as Windows Sound Recorder, or any audio recording software. The file needs to be a .wav or an .mp3 file saved in PCM, 8KHz, 16 bit, Mono format. If you have Windows Sound Recorder only, I'd suggest that you try out Audacity. Audacity is an open source audio file program available at http://audacity.sourceforge.net/. Audacity gives you a lot more power such as controlling volume, combining several audio tracks (a music track to go with the announcer), using special effects, and many other cool audio tools. I'm not an expert in it but the basics are easy to do. First, hit the Audacity website and download it, then install it using the defaults. Now let's launch Audacity and set it up to use the correct file format, which will save us any issues later. Start by clicking Edit | Preferences. On the Quality tab, select the Default Sample Rate as 8000 Hz. Then change the Default Sample Format to 16-bit as shown in the following screenshot: Now, on the File Formats tab, select WAV (Microsoft 16 bit PCM) from the drop-down list and click OK: Now that those settings are saved, you can record your greeting without having to change any formats. Now it's time to record your greeting. Click on the red Record button as shown in the following screenshot. It will now use your PC's microphone to record the announcer's voice and when the recording is done, click on the Stop button. Press Play to hear it, and if you don't like it, start over again: If you like the way your greeting sounds, then you will need to save it. Click File | Export As WAV... or Export As MP3.... Save it to a location that you remember (for example, c:3CX prompts is a good place) with a descriptive filename. While you are recording this greeting, you might as well record a few more if you have plans for creating multiple DRs: Creating the Digital Receptionist With your greeting script in hand, it's time to create your first DR. In the navigation pane on the left side, click Digital Receptionist, then click Add Digital Receptionist as shown in the following screenshot: Or on the top menu toolbar, click Add | Digital Receptionist: Just like your ring group, the DR gets a Virtual extension number by default, Feel free to change it or stick with it. Give it a Name, (I like to use the same name as the audio greeting filename.) Now, click Browse... and then Add. Browse to your c:3CX prompts directory and select your .wav or .mp3 file as shown in the following screenshot: Next, we need to create the menu system as shown in the following screenshot. We have lots of options available. You can connect to an extension or ring group, transfer directly to someone's voicemail, end the call (my solicitors' option), or start the call by name feature (discussed in the Call by name setup section). At any time during playback, callers can dial the extension number; they don't have to hear all the options. I usually explain this in the DR recorded greeting.
Read more
  • 0
  • 0
  • 7649

article-image-testing-and-debugging-grok-10-part-1
Packt
11 Feb 2010
12 min read
Save for later

Testing and Debugging in Grok 1.0: Part 1

Packt
11 Feb 2010
12 min read
Grok offers some tools for testing, and in fact, a project created by grokproject (as the one we have been extending) includes a functional test suite. In this article, we are going to discuss testing a bit and then write some tests for the functionality that our application has so far. Testing helps us avoid bugs, but it does not eliminate them completely, of course. There are times when we will have to dive into the code to find out what's going wrong. A good set of debugging aids becomes very valuable in this situation. We'll see that there are several ways of debugging a Grok application and also try out a couple of them. Testing It's important to understand that testing should not be treated as an afterthought. As mentioned earlier, agile methodologies place a lot of emphasis on testing. In fact, there's even a methodology called Test Driven Development (TDD), which not only encourages writing tests for our code, but also writing tests before any other line of code. There are various kinds of testing, but here we'll briefly describe only two: Unit testing Integration or functional tests Unit testing The idea of unit testing is to break a program into its constituent parts and test each one of them in isolation. Every method or function call can be tested separately to make sure that it returns the expected results and handles all of the possible inputs correctly. An application which has unit tests that cover the majority of its lines of code, allows its developers to constantly run the tests after a change, and makes sure that modifications to the code do not break the existing functionality. Functional tests Functional tests are concerned with how the application behaves as a whole. In a web application, this means how it responds to a browser request and whether it returns the expected HTML for a given call. Ideally, the customer himself has a hand in defining these tests, usually through explicit functionality requirements or acceptance criteria. The more formal the requirements from the customer are, the easier it is to define appropriate functional tests. Testing in Grok Grok highly encourages the use of both kinds of tests, and in fact, includes a powerful testing tool that is automatically configured with every project. In the Zope world—from where Grok originated—a lot of value is placed in a kind of tests known as "doctests", so Grok comes with a sample test suite of this kind. Doctests A doctest is a test that's written as a text file, with lines of code mixed with explanations of what the code is doing. The code is written in a way that simulates a Python interpreter session. As tests exercise large portions of the code (ideally 100%), they usually offer a good way of finding out of what an application does and how. So, if an application has no written documentation, its tests would be the next obvious way of finding out what it does. Doctests take this idea further by allowing the developer to explain in the text file exactly what each test is doing. Doctests are especially useful for functional testing, because it makes more sense to document the high-level operations of a program. Unit tests, on the other hand, are expected to evaluate the program bit by bit and it can be cumbersome to write a text explanation for every little piece of code. A possible drawback of doctests is that they can make the developer think that he needs no other documentation for his project. In almost all of the cases, this is not true. Documenting an application or package makes it immediately more accessible and useful, so it is strongly recommended that doctests should not be used as a replacement for good documentation. We'll show an example of using doctests in the Looking at the test code section of this article. Default test setup for Grok projects As mentioned above, Grok projects that are started with the grokproject tool already include a simple functional test suite by default. Let's examine it in detail. Test configuration The default test configuration looks for packages or modules that have the word 'tests' in their name and tries to run the tests inside. For functional tests, any files ending with .txt or .rst are considered. For functional tests that need to simulate a browser, a special configuration is needed to tell Grok which packages to initialize in addition to the Grok infrastructure (usually the ones that are being worked on). The ftesting.zcml file in the package directory has this configuration. This also includes a couple of user definitions that are used by certain tests to examine functionality specific to a certain role, such as manager. Test files Besides the already mentioned ftesting.zcml file, in the same directory, there is a tests.py file added by grokproject, which basically loads the ZCML declarations and registers all of the tests in the package. The actual tests that are included with the default project files are contained in the app.txt file. These are doctests that do a functional test run by loading the entire Grok environment and imitating a browser. We'll take a look at the contents of the file soon, but first let's run the tests. Running the tests As part of the project's build process, a script named test is included in the bin directory when you create a new project. This is the test runner and calling it without arguments, finds and executes all of the tests in the packages that are included in the configuration. We haven't added a single test so far, so if we type bin/test in our project directory, we'll see more or less the same thing that doing that on a new project would show: $ bin/testRunning tests at level 1 Running todo.FunctionalLayer tests: Set up in 12.319 seconds. Running: ...2009-09-30 15:00:47,490 INFO sqlalchemy.engine.base.Engine.0x...782c PRAGMA table_info("users") 2009-09-30 15:00:47,490 INFO sqlalchemy.engine.base.Engine.0x...782c () Ran 3 tests with 0 failures and 0 errors in 0.465 seconds. Tearing down left over layers: Tear down todo.FunctionalLayer ... not supported The only difference between our output to that of a newly created Grok package is in the sqlalchemy lines. Of course, the most important part of the output is the "penultimate" line, which shows the number of tests that were run and whether there were any failures or errors. A failure means that some test didn't pass, which means that the code is not doing what it's supposed to do and needs to be checked. An error signifies that the code crashed unexpectedly at some point, and the test couldn't even be executed, so it's necessary to find the error and correct it before worrying about the tests. The test runner The test runner program looks for modules that contain tests. The test can be of three different types: Python tests, simple doctests, and full functionality doctests. To let the test runner know, which test file includes which kind of tests, a comment similar to the following is placed at the top of the file: Do a Python test on the app. :unittest: In this case, the Python unit test layer will be used to run the tests. The other value that we are going to use is "doctest" when we learn how to write doctests. The test runner then finds all of the test modules and runs them in the corresponding layer. Although unit tests are considered very important in regular development, we may find functional tests more necessary for a Grok web application, as we will usually be testing views and forms, which require the full Zope/Grok stack to be loaded to work. That's the reason why we find only functional doctests in the default setup. Test layers A test layer is a specific test setup which is used to differentiate the tests that are executed. By default, there is a test layer for each of the three types of tests handled by the test runner. It's possible to run a test layer without running the others and also to name new test layers to be able to cluster together tests that require a specific setup. Invoking the test runner As shown above, running bin/test will start the test runner with the default options. It's also possible to specify a number of options, and the most important ones are summarized below. In the following table, command-line options are shown to the left. Most options can be expressed with a short form (one dash) or a long form (two dashes). Arguments for the option in question are shown in uppercase. -s PACKAGE, --package=PACKAGE, --dir=PACKAGE Search the given package's directories for tests. This can be specified more than once, to run tests in multiple parts of the source tree. For example, when refactoring interfaces, you don't want to see the way you have broken setups for tests in other packages. You just want to run the interface tests. Packages are supplied as dotted names. For compatibility with the old test runner, forward and backward slashes in package names are converted to dots. (In the special case of packages, which are spread over multiple directories, only directories within the test search path are searched.) -m MODULE, --module=MODULE Specify a test-module filter as a regular expression. This is a case sensitive regular expression, which is used in search (not match) mode, to limit which test modules are searched for tests. The regular expressions are checked against dotted module names. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The option can specy multiple test-module filters. Test modules matching any of the test filters are searched. If no test-module filter is specified, then all of the test modules are used. -t TEST, --test=TEST Specify a test filter as a regular expression. This is a case sensitive regular expression, which is used in search (not match) mode, to limit which tests are run. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The option can specify multiple test filters. Tests matching any of the test filters are included. If no test filter is specified, then all of the tests are executed. --layer=LAYER Specify a test layer to run. The option can be given multiple times to specify more than one layer. If not specified, all of the layers are executed. It is common for the running script to provide default values for this option. Layers are specified regular expressions that are used in search mode, for dotted names of objects that define a layer. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The layer named 'unit' is reserved for unit tests, however, take note of the -unit and non-unit options. -u, --unit Executes only unit tests, ignoring any layer options. -f, --non-unit Executes tests other than unit tests. -v, --verbose Makes output more verbose. Increment the verbosity level. -q, --quiet Makes the output minimal by overriding any verbosity options. Looking at the test code Let's take a look at the three default test files of a Grok project, to see what each one does. ftesting.zcml As we explained earlier, ftesting.zcml is a configuration file for the test runner. Its main objective is to help us set up the test instance with users, so that we can test different roles according to our needs. <configure i18n_domain="todo" package="todo" > <include package="todo" /> <include package="todo_plus" /> <!-- Typical functional testing security setup --> <securityPolicy component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" /> <unauthenticatedPrincipal id="zope.anybody" title="Unauthenticated User" /> <grant permission="zope.View" principal="zope.anybody" /> <principal id="zope.mgr" title="Manager" login="mgr" password="mgrpw" /> <role id="zope.Manager" title="Site Manager" /> <grantAll role="zope.Manager" /> <grant role="zope.Manager" principal="zope.mgr" /> As shown in the preceding code, the configuration simply includes a security policy, complete with users and roles and the packages that should be loaded by the instance, in addition to the regular Grok infrastructure. If we run any tests that require an authenticated user to work, we'll use these special users. The includes at the top of the file just make sure that all of the Zope Component Architecture setup needed by our application is performed prior to running the tests. tests.py The default test module is very simple. It defines the functional layer and registers the tests for our package: import os.path import z3c.testsetup import todo from zope.app.testing.functional import ZCMLLayer ftesting_zcml = os.path.join( os.path.dirname(todo.__file__), 'ftesting.zcml') FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer', allow_teardown=True) test_suite = z3c.testsetup.register_all_tests('todo') After the imports, the first line gets the path for the ftesting.zcml file, which then is passed to the layer definition method ZCMLLayer. The final line in the module tells the test runner to find and register all of the tests in the package. This will be enough for our testing needs in this article, but if we needed to create another non-Grok package for our application, we would need to add a line like the last one to it, so that all of its tests are found by the test runner. This is pretty much boilerplate code, as only the package name has to be changed.
Read more
  • 0
  • 0
  • 2386
Modal Close icon
Modal Close icon