Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7019 Articles
article-image-formatting-and-enhancing-your-moodle-materials-part-2
Packt
18 Nov 2009
8 min read
Save for later

Formatting and Enhancing Your Moodle Materials: Part 2

Packt
18 Nov 2009
8 min read
Images If you've taken digital images, or scanned images onto your computer, it's likely that they'll be high resolution images, ready for printing. We don't need high resolution images on our computer screens for two good reasons: screen resolutions can't show so much detail and they take up a lot of storage space. There's a process called optimization which you can use to make your images more usable for your language learning activities. You can either use a program like Photoshop Elements (commercial), or Google's Picasa, which is free from http://picasa.google.com. These will enable you to edit your pictures and get the best quality with the smallest storage size. Let's look at a few things you can do to enhance your images using Picasa. Cropping Imagine we've taken this picture, but we're only interested in using the picture of the mug for a vocabulary exercise. Open up Picasa. Click on the photo we want to edit. Click on Basic Fixes and then on Crop. Click on Manual in the drop-down menu, then select the bit of the image we want to crop (cut out). Then click on Apply. The result will be: Color balance As it stands, the picture is too dark. There is too little contrast. Picasa will also allow us to create a stronger contrast, just by clicking on Tuning and Fill light and then moving the button across to brighten up the picture. The final picture looks like this: It's smaller and brighter than the original and more appropriate for our Moodle page. Optimization and image size The picture above is 340 kb in storage size, which is pretty big. The reason it's so big in storage size is that its real size is 837 px in width and 960 px in height. In case you're new to image measurement, px stands for pixels, which are the dots on your screen. So we have an unnecessarily large image. We can reduce the image size when we insert an image, but it's a much better idea to reduce the dimensions to what we actually need before we import the image into Moodle. That will reduce the storage size at the same time. Another reason for resizing images is that if you're using several photos on the same page, you'll achieve a much greater sense of balance and therefore effectiveness if all your images are the same size. If we reduce the mug to 100 px in width, the final version is only 92 kb in size. We can resize images in Picasa by exporting, emailing, or uploading our photos to Picasa Web Albums. When we select File | Export, we can select what size we want. Videos One exciting way of enhancing your web pages in Moodle is to use video. You can either upload videos to your own Moodle site or upload them to a site like YouTube, or TeacherTube. Mashable at http://mashable.com/category/video is an excellent source of ideas and resources for editing, uploading, and sharing your videos. If you decide to upload your videos onto your Moodle site, you'll need to check their size and the upload limits on your Moodle site. The default limits are usually quite low, but you can get your administrator to change them. You can also get round this problem by uploading your videos direct to the server using an FTP program. You will need to ask your Moodle administrator for help with that. Embedding videos will save your server's storage space and traffic. Adding subtitles to your videos One way of making video content more accessible for language learners is to add same-language subtitles. This would work well as an extension to the read and listen activity. Alternatively, you could get students to produce the subtitles, a rather glamorous type of dictation. If you want to add subtitles to your own videos, this is quite straightforward in free programs like Movie Maker (for Windows) or iMovie (for Macs). Look up "subtitles" in the help files. If you want to add subtitles to a YouTube video, http://www.overstream.net/ allows you to do just that. You can then embed the final product in your website. Here's what a video with added subtitles could look like: Sound If you're not happy with the quality of sound, there are various things you can do to improve it. The six examples below are created with the audio program called Audacity, but most audio editing programs will offer the same tools. The first four edits are in the Effect menu. Remove noise Effect | Noise removal If there is an unwelcome background noise on your recording, Audacity has a tool for reducing it. Open Audacity. Open your recording. Select the whole sound track or part of the track that has too much noise by highlighting it with your mouse cursor. Select click on Effect | Noise removal to get to the noise removal tool. On-screen instructions will guide you through the rest of the process. Be careful not to reduce the noise too much, as this sometimes creates distortion. Fade in and fade out Effect | Fade in/out If you want the sound to fade in and out, use your mouse to select the part of the sound track where you want the effect. Then select Effect | Fade in/out to create the effect. This could be useful for a presentation to avoid having a burst of sound at the beginning of the recording. Change tempo without changing pitch Effect | Change tempo This can be very useful, particularly for lower-level learners. It's useful to create two versions of your recordings: one fast and one slow. You can upload both and give students the choice of which one they listen to. The great thing about this tool is that the pitch doesn't change. Change pitch without changing tempo Effect | Change pitch Sometimes you might want to lower or raise the pitch of a voice to make it more audible. This tool lets you do that without the speed changing. It can even be used to simulate a dialog, with you speaking both parts, keeping one at your normal pitch and the other one at a higher or lower pitch. You'll find the next two settings in the Preferences menu. Sample rate This helps determine the quality of your recording. You can think of it as the number of times per second you capture a snapshot of sound while you're recording. Higher sample rates give you more detail. In other words, it's a fuller sound. 8 KHz is the lowest sampling rate you should select for voice recordings. 16 KHz is more likely to produce an acceptable sound. If you have music as well, you'll need a higher sample rate, like 44 KHz. Bit rate This is the number of bits (digital 1s and 0s) that are used each second to represent the sound signal. The bit-rate for digital audio is represented in thousands of bits per second (kbps). The higher the bit-rate is, the larger the file size and the better the sound quality. Lower bit rates result in smaller files but poorer sound quality. A good bit rate for recording in Audacity is 32. Audacity offers many more possibilities, and it's well worth exploring it in detail. Go to http://audacity.sourceforge.net/help/ for more help. Navigation Most of the navigation work—that is, menus and links—is done for you in Moodle. However, there are things you can do to improve it. Here's a list of tips: Font size and color Make consistent use of font size and color with headings so that users recognize the relative importance of different sections. For example, make topics big and bold so that they stand out. Remember that the default font and color is the same as for all other text. You have to make the difference yourself. For example: User control Allow users to move ahead if necessary, so that they feel in control. You can do this by providing explicit headings in your course topics. Don't call an activity "activity". Give it a more specific name, like "Second prepositions grammar exercise". Here are some options for navigation maps: Use the Topics view on your course pages Use Book to organize a syllabus. There's an example of this in the introduction to this article. Use a flowchart program to create a plan. Then import it into your Moodle web page. For example:   Many flowchart programs allow you to include hyperlinks to the actual activities. To do that, first copy and paste the target web page address from the address bar. Then paste that address into the hyperlink in your flowchart program. Here's an example using gliffy: Create a web page with a menu on it, as in the following screenshots. To make your new web page appear with blocks to the left and right, click on Show the course blocks on the set-up page—it's at the bottom of the next screenshot. The final page would look like this. Students will see all the other navigation blocks in the left and right-hand columns.
Read more
  • 0
  • 0
  • 1545

article-image-version-management-upk-35-part-2
Packt
18 Nov 2009
5 min read
Save for later

Version Management with UPK 3.5: Part 2

Packt
18 Nov 2009
5 min read
Restoring a deleted document In a client/server environment, content objects are never really deleted; they still exist on the server. Instead, they are removed from the Library folders so that they are not visible within the Library itself. However, they will appear in the Deleted documents view, from where developers can display and restore them. The only way they can be truly deleted is by the Administrator purging them. The advantage of this is that you can always (up until the point that the Administrator has purged it) "un-delete" a content object, if you realize that you deleted it by mistake. To restore (un-delete) a content object, carry out the following steps: From the Library screen, click on the drop-down button for the View field (which will normally indicate Details view) on the View toolbar, and select Deleted documents. A list of all of the content objects that have been deleted (but not purged) is displayed, as shown in the following screenshot: Right-click on the document that you want to restore, and select Restore from the context menu. This is the only option available on this context menu. The document is restored to the Library, and placed back in the folder it was originally deleted from. This location is also shown in the Original location column in the Deleted documents view. This will create another version in the version history for the content object—just as deleting it did. This is shown in the screenshot below: Exporting and importing content If you are working in a stand-alone environment and want to share your developed content with another user, you will need to export it from your Library so they can import it into their Library. If you are a developer working in a client/server environment, it is unlikely that you will need to export your content because all users will be able to access it in the same Library. However, even then there may be a need to export your content, for example: Your company may use a "staged" approach to implementation, whereby all content is developed in a Development library before being migrated to a more secure production environment If you are working with multiple libraries, then it is recommended that you always export/ import folders, and not simply Outline Elements and their contents. This will reduce the possibility of elements that appear in multiple outlines being overwritten on successive imports. You have developed content in one environment (for example, your personal stand-alone environment) and now want to bring it into your company's client/server environment You want to take a copy of everything you have done, as a personal back-up You want to export all of your content before upgrading to a new version of UPK and importing your content into the new version's Library Whatever the reason, UPK makes it easy to export content. In this section we'll look at just how to do this. You would not normally use this approach to export content for translation. Exporting content objects You can use the Export function to export content objects as an archive, publishing options, sound files, and content objects for localization. This section covers exporting content objects as an archive. You can export any type of content object to an archive: Modules, Sections, and Topics, as well as Web Pages and Packages. You can also export any of the objects in the System folder, such as Templates and Publishing Packages. To export a content object, carry out the following steps: In the Library, select on the content objects that you want to export. You can select a single object, or multiple objects of various types. You also have the option of selecting a folder and exporting all of the content within the folder. Select menu option Tools|Export|Documents. The Export dialog box is displayed, as shown in the following screenshot: Click on the Browse button next to the Export content to: field. The Save As dialog box is displayed, as shown below: Enter a suitable file name for your export file in the File name field. Note that UPK will automatically select a file type of Content archives (*.odarc) and add the file prefix .odarc (for OnDemand Archive. Note the reference to OnDemand even though the product is no longer called OnDemand!). Click on Save to confirm the destination folder and file. Back in the Export dialog box, under Selection Options, select whether you want to export only the specific content that you have selected (Export my selection), or the selected content and all related documents (Export my selection and related documents). Related documents are typically content objects such as web pages that are linked to the selected object(s). You can see a list of these objects by clicking the View related documents link. Depending on how you have organized your Library, you should normally export entire folders and their contents, not Outline Elements. Otherwise, you risk missing out objects that are not directly linked into the Outline, such as Packages. You also risk having broken links when you (re)import the content object(s). Click on Export. The selected content objects (and all related documents, if you chose that option) are exported to the specified file. While this is being done, a progress indicator is displayed for the object currently being exported, and the overall percentage complete. The export may be very quick, especially if you are only exporting one or two Topics so don't blink or you'll miss it. An example of the progress indicator is shown below: Once the export is complete, you are returned to the Library. As the content is exported to a single file, you can easily send the file to another Developer for importing into their UPK Library, save it to CD as a back-up, and so on. How to import content objects is described next.
Read more
  • 0
  • 0
  • 2291

article-image-unity-game-development-interactions-part-2
Packt
18 Nov 2009
14 min read
Save for later

Unity Game Development: Interactions (Part 2)

Packt
18 Nov 2009
14 min read
Opening the outpost In this section, we will look at the two differing approaches for triggering the animation giving you an overview of the two techniques that will both become useful in many other game development situations. In the first approach, we'll use collision detection—a crucial concept to get to grips with as you begin to work on games in Unity. In the second approach, we'll implement a simple ray cast forward from the player. Approach 1—Collision detection To begin writing the script that will trigger the door-opening animation and thereby grant access to the outpost, we need to consider which object to write a script for. In game development, it is often more efficient to write a single script for an object that will interact with many other objects, rather than writing many individual scripts that check for a single object. With this in mind, when writing scripts for a game such as this, we will write a script to be applied to the player character in order to check for collisions with many objects in our environment, rather than a script made for each object the player may interact with, which checks for the player. Creating new assets Before we introduce any new kind of asset into our project, it is good practice to create a folder in which we will keep assets of that type. In the Project panel, click on the Create button, and choose Folder from the drop-down menu that appears. Rename this folder Scripts by selecting it and pressing Return (Mac) or by pressing F2 (PC). Next, create a new JavaScript file within this folder simply by leaving the Scripts folder selected and clicking on the Project panel's Create button again, this time choosing JavaScript. By selecting the folder, you want a newly created asset to be in before you create them, you will not have to create and then relocate your asset, as the new asset will be made within the selected folder. Rename the newly created script from the default—NewBehaviourScript—to PlayerCollisions. JavaScript files have the file extension of .js but the Unity Project panel hides file extensions, so there is no need to attempt to add it when renaming your assets. You can also spot the file type of a script by looking at its icon in the Project panel. JavaScript files have a 'JS' written on them, C# files simply have 'C#' and Boo files have an image of a Pacman ghost, a nice little informative pun from the guys at Unity Technologies! Scripting for character collision detection To start editing the script, double-click on its icon in the Project panel to launch it in the script editor for your platform—Unitron on Mac, or Uniscite on PC. Working with OnControllerColliderHit By default, all new JavaScripts include the Update() function, and this is why you'll find it present when you open the script for the first time. Let's kick off by declaring variables we can utilise throughout the script. Our script begins with the definition of four variables, public member variables and two private variables. Their purposes are as follows: doorIsOpen: a private true/false (boolean) type variable acting as a switch for the script to check if the door is currently open. doorTimer: a private floating-point (decimal-placed) number variable, which is used as a timer so that once our door is open, the script can count a defined amount of time before self-closing the door. currentDoor: a private GameObject storing variable used to store the specific currently opened door. Should you wish to add more than one outpost to the game at a later date, then this will ensure that opening one of the doors does not open them all, which it does by remembering the most recent door hit. doorOpenTime: a floating-point (potentially decimal) numeric public member variable, which will be used to allow us to set the amount of time we wish the door to stay open in the Inspector. doorOpenSound/doorShutSound: Two public member variables of data type AudioClip, for allowing sound clip drag-and-drop assignment in the Inspector panel. Define the variables above by writing the following at the top of the PlayerCollisions script you are editing: private var doorIsOpen : boolean = false;private var doorTimer : float = 0.0;private var currentDoor : GameObject;var doorOpenTime : float = 3.0;var doorOpenSound : AudioClip;var doorShutSound : AudioClip; Next, we'll leave the Update() function briefly while we establish the collision detection function itself. Move down two lines from: function Update(){} And write in the following function: function OnControllerColliderHit(hit : ControllerColliderHit){} This establishes a new function called OnControllerColliderHit. This collision detection function is specifically for use with player characters such as ours, which use the CharacterController component. Its only parameter hit is a variable that stores information on any collision that occurs. By addressing the hit variable, we can query information on the collision, including—for starters—the specific game object our player has collided with. We will do this by adding an if statement to our function. So within the function's braces, add the following if statement: function OnControllerColliderHit(hit: ControllerColliderHit){ if(hit.gameObject.tag == "outpostDoor" && doorIsOpen == false){ }} In this if statement, we are checking two conditions, firstly that the object we hit is tagged with the tag outpostDoor and secondly that the variable doorOpen is currently set to false. Remember here that two equals symbols (==) are used as a comparative, and the two ampersand symbols (&&) simply say 'and also'. The end result means that if we hit the door's collider that we have tagged and if we have not already opened the door, then it may carry out a set of instructions. We have utilized the dot syntax to address the object we are checking for collisions with by narrowing down from hit (our variable storing information on collisions) to gameObject (the object hit) to the tag on that object. If this if statement is valid, then we need to carry out a set of instructions to open the door. This will involve playing a sound, playing one of the animation clips on the model, and setting our boolean variable doorOpen to true. As we are to call multiple instructions—and may need to call these instructions as a result of a different condition later when we implement the ray casting approach—we will place them into our own custom function called OpenDoor. We will write this function shortly, but first, we'll call the function in the if statement we have, by adding: OpenDoor(); So your full collision function should now look like this: function OnControllerColliderHit(hit: ControllerColliderHit){ if(hit.gameObject.tag == "outpostDoor" && doorIsOpen == false){ OpenDoor(); }} Writing custom functions Storing sets of instructions you may wish to call at any time should be done by writing your own functions. Instead of having to write out a set of instructions or "commands" many times within a script, writing your own functions containing the instructions means that you can simply call that function at any time to run that set of instructions again. This also makes tracking mistakes in code—known as Debugging—a lot simpler, as there are fewer places to check for errors. In our collision detection function, we have written a call to a function named OpenDoor. The brackets after OpenDoor are used to store parameters we may wish to send to the function—using a function's brackets, you may set additional behavior to pass to the instructions inside the function. We'll take a look at this in more depth later in this article under the heading Function Efficiency. Our brackets are empty here, as we do not wish to pass any behavior to the function yet. Declaring the function To write the function we need to call, we simply begin by writing: function OpenDoor(){} In between the braces of the function, much in the same way as the instructions of an if statement, we place any instructions to be carried out when this function is called. Playing audio Our first instruction is to play the audio clip assigned to the variable called doorOpenSound. To do this, add the following line to your function by placing it within the curly braces after { "and before" }: audio.PlayOneShot(doorOpenSound); To be certain, it should look like this: function OpenDoor(){ audio.PlayOneShot(doorOpenSound);} Here we are addressing the Audio Source component attached to the game object this script is applied to (our player character object, First Person Controller), and as such, we'll need to ensure later that we have this component attached; otherwise, this command will cause an error. Addressing the audio source using the term audio gives us access to four functions, Play(), Stop(), Pause(), and PlayOneShot(). We are using PlayOneShot because it is the best way to play a single instance of a sound, as opposed to playing a sound and then switching clips, which would be more appropriate for continuous music than sound effects. In the brackets of the PlayOneShot command, we pass the variable doorOpenSound, which will cause whatever sound file is assigned to that variable in the Inspector to play. We will download and assign this and the clip for closing the door after writing the script. Checking door status One condition of our if statement within our collision detection function was that our boolean variable doorIsOpen must be set to false. As a result, the second command inside our OpenDoor() function is to set this variable to true. This is because the player character may collide with the door several times when bumping into it, and without this boolean, they could potentially trigger the OpenDoor() function many times, causing sound and animation to recur and restart with each collision. By adding in a variable that when false allows the OpenDoor() function to run and then disallows it by setting the doorIsOpen variable to true immediately, any further collisions will not re-trigger the OpenDoor() function. Add the line: doorOpen = true; to your OpenDoor() function now by placing it between the curly braces after the previous command you just added. Playing animation We have already imported the outpost asset package and looked at various settings on the asset before introducing it to the game in this article. One of the tasks performed in the import process was the setting up of animation clips using the Inspector. By selecting the asset in the Project panel, we specified in the Inspector that it would feature three clips: idle (a 'do nothing' state) dooropen doorshut In our openDoor() function, we'll call upon a named clip using a String of text to refer to it. However, first we'll need to state which object in our scene contains the animation we wish to play. Because the script we are writing is to be attached to the player, we must refer to another object before referring to the animation component. We do this by stating the line: var myOutpost : GameObject = GameObject.Find("outpost"); Here we are declaring a new variable called myOutpost by setting its type to be a GameObject and then selecting a game object with the name outpost by using GameObject.Find. The Find command selects an object in the current scene by its name in the Hierarchy and can be used as an alternative to using tags. Now that we have a variable representing our outpost game object, we can use this variable with dot syntax to call animation attached to it by stating: myOutpost.animation.Play("dooropen"); This simply finds the animation component attached to the outpost object and plays the animation called dooropen. The play() command can be passed any string of text characters, but this will only work if the animation clips have been set up on the object in question. Your finished OpenDoor() custom function should now look like this: function OpenDoor(){ audio.PlayOneShot(doorOpenSound); doorIsOpen = true; var myOutpost : GameObject = GameObject.Find("outpost"); myOutpost.animation.Play("dooropen");} Reversing the procedure Now that we have created a set of instructions that will open the door, how will we close it once it is open? To aid playability, we will not force the player to actively close the door but instead establish some code that will cause it to shut after a defined time period. This is where our doorTimer variable comes into play. We will begin counting as soon as the door becomes open by adding a value of time to this variable, and then check when this variable has reached a particular value by using an if statement. Because we will be dealing with time, we need to utilize a function that will constantly update such as the Update() function we had awaiting us when we created the script earlier. Create some empty lines inside the Update() function by moving its closing curly brace } a few lines down. Firstly, we should check if the door has been opened, as there is no point in incrementing our timer variable if the door is not currently open. Write in the following if statement to increment the timer variable with time if the doorIsOpen variable is set to true: if(doorIsOpen){ doorTimer += Time.deltaTime;} Here we check if the door is open — this is a variable that by default is set to false, and will only become true as a result of a collision between the player object and the door. If the doorIsOpen variable is true, then we add the value of Time.deltaTime to the doorTimer variable. Bear in mind that simply writing the variable name as we have done in our if statement's condition is the same as writing doorIsOpen == true. Time.deltaTime is a Time class that will run independent of the game's frame rate. This is important because your game may be run on varying hardware when deployed, and it would be odd if time slowed down on slower computers and was faster when better computers ran it. As a result, when adding time, we can use Time.deltaTime to calculate the time taken to complete the last frame and with this information, we can automatically correct real-time counting. Next, we need to check whether our timer variable, doorTimer, has reached a certain value, which means that a certain amount of time has passed. We will do this by nesting an if statement inside the one we just added—this will mean that the if statement we are about to add will only be checked if the doorIsOpen if condition is valid. Add the following code below the time incrementing line inside the existing if statement: if(doorTimer > doorOpenTime){shutDoor();doorTimer = 0.0;} This addition to our code will be constantly checked as soon as the doorIsOpen variable becomes true and waits until the value of doorTimer exceeds the value of the doorOpenTime variable, which, because we are using Time.deltaTime as an incremental value, will mean three real-time seconds have passed. This is of course unless you change the value of this variable from its default of 3 in the Inspector. Once the doorTimer has exceeded a value of 3, a function called shutDoor() is called, and the doorTimer variable is reset to zero so that it can be used again the next time the door is triggered. If this is not included, then the doorTimer will get stuck above a value of 3, and as soon as the door was opened it would close as a result. Your completed Update() function should now look like this: function Update(){ if(doorIsOpen){ doorTimer += Time.deltaTime; if(doorTimer > 3){ shutDoor(); doorTimer = 0.0; } }} Now, add the following function called shutDoor() to the bottom of your script. Because it performs largely the same function as openDoor(), we will not discuss it in depth. Simply observe that a different animation is called on the outpost and that our doorIsOpen variable gets reset to false so that the entire procedure may start over: function shutDoor(){audio.PlayOneShot(doorShutSound);doorIsOpen = false;var myOutpost : GameObject = GameObject.Find("outpost");myOutpost.animation.Play("doorshut");}
Read more
  • 0
  • 0
  • 3019

article-image-user-interaction-and-email-automation-symfony-13-part1
Packt
18 Nov 2009
14 min read
Save for later

User Interaction and Email Automation in Symfony 1.3: Part1

Packt
18 Nov 2009
14 min read
The signup module We want to provide the users with the functionality to enter their name, email address, and how they found our web site. We want all this stored in a database and to have an email automatically sent out to the users thanking them for signing up. To start things off, we must first add some new tables to our existing database schema. The structure of our newsletter table will be straightforward. We will need one table to capture the users' information and a related table that will hold the names of all the places where we advertised our site. I have constructed the following entity relationship diagram to show you a visual relationship of the tables: All the code used in this article can be accessed here. Let's translate this diagram into XML and place it in the config/schema.xml file: <table name="newsletter_adverts" idMethod="native" phpName="NewsletterAds"> <column name="newsletter_adverts_id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="advertised" type="VARCHAR" size="30" required="true" /> </table> <table name="newsletter_signups" idMethod="native" phpName="NewsletterSignup"> <column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="first_name" type="VARCHAR" size="20" required="true" /> <column name="surname" type="VARCHAR" size="20" required="true" /> <column name="email" type="VARCHAR" size="100" required="true" /> <column name="activation_key" type="VARCHAR" size="100" required="true" /> <column name="activated" type="BOOLEAN" default="0" required="true" /> <column name="newsletter_adverts_id" type="INTEGER" required="true"/> <foreign-key foreignTable="newsletter_adverts" onDelete="CASCADE"> <reference local="newsletter_adverts_id" foreign="newsletter_adverts_id" /> </foreign-key> <column name="created_at" type="TIMESTAMP" required="true" /> <column name="updated_at" type="TIMESTAMP" required="true" /> </table> We will need to populate the newsletter_adverts table with some test data as well. Therefore, I have also appended the following data to the fixtures.yml file located in the data/fixtures/ directory: NewsletterAds: nsa1: advertised: Internet Search nsa2: advertised: High Street nsa3: advertised: Poster With the database schema and the test data ready to be inserted into the database, we can once again use the Symfony tasks. As we have added two new tables to the schema, we will have to rebuild everything to generate the models using the following command: $/home/timmy/workspace/milkshake>symfony propel:build-all-load --no-confirmation Now we have populated the tables in the database, and the models and forms have been generated for use too. Binding a form to a database table Symfony contains a whole framework just for the development of forms. The forms framework makes building forms easier by applying object-oriented methods to their development. Each form class is based on its related table in the database. This includes the fields, the validators, and the way in which the forms and fields are rendered. A look at the generated base class Rather than starting off with a simple form, we are going to look at the base form class that has already been generated for us as a part of the build task we executed earlier. Because the code is generated, it will be easier for you to see the initial flow of a form. So let's open the base class for the NewsletterSignupForm form. The file is located at lib/form/base/BaseNewsletterSignupForm.class.php: class BaseNewsletterSignupForm extends BaseFormPropel { public function setup() { $this->setWidgets(array( 'id' => new sfWidgetFormInputHidden(), 'first_name' => new sfWidgetFormInput(), 'surname' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'activation_key' => new sfWidgetFormInput(), 'activated' => new sfWidgetFormInputCheckbox(), 'newsletter_adverts_id' => new sfWidgetFormPropelChoice (array('model' => 'NewsletterAds', 'add_empty' => false)), 'created_at' => new sfWidgetFormDateTime(), 'updated_at' => new sfWidgetFormDateTime(), )); $this->setValidators(array( 'id' => new sfValidatorPropelChoice(array ('model' => 'NewsletterSignup', 'column' => 'id', 'required' => false)), 'first_name' => new sfValidatorString(array('max_length' => 20)), 'surname' => new sfValidatorString(array('max_length' => 20)), 'email' => new sfValidatorString(array('max_length' => 100)), 'activation_key' => new sfValidatorString(array('max_length' => 100)), 'activated' => new sfValidatorBoolean(), 'newsletter_adverts_id'=> new sfValidatorPropelChoice(array ('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id')), 'created_at' => new sfValidatorDateTime(), 'updated_at' => new sfValidatorDateTime(), )); $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); $this->errorSchema = new sfValidatorErrorSchema ($this->validatorSchema); parent::setup(); } There are five areas in this base class that are worth noting: This base class extends the BaseFormPropel class, which is an empty class. All base classes extend this class, which allows us to add global settings to all our forms. All of the columns in our table are treated as fields in the form, and are referred to as widgets. All of these widgets are then attached to the form by adding them to the setWidgets() method. Looking over the widgets in the array, you will see that they are pretty standard, such as sfWidgetFormInputHidden(), sfWidgetFormInput(). However, there is one widget added that follows the relationship between the newsletter_sigups table and the newsletter_adverts table. It is the sfWidgetFormPropelChoice widget. Because there is a 1:M relation between the tables, the default behavior is to use this widget, which creates an HTML drop-down box and is populated with the values from the newsletter_adverts table. As a part of the attribute set, you will see that it has set the model needed to retrieve the values to NewsletterAds and the newsletter_adverts_id column for the actual values of the drop-down box. All the widgets on the form must be validated by default. To do this, we have to call the setValidators() method and add the validation requirements to each widget. At the moment, the generated validators reflect the attributes of our database as set in the schema. For example, the first_name field in the statement 'first_name' => new sfValidatorString(array('max_length' => 20)) demonstrates that the validator checks if the maximum length is 20. If you remember, in our schema too, the first_name column is set to 20 characters. The final part calls the parent's setup() function. The base class BaseNewsletterSignupForm contains all the components needed to generate the form for us. So let's get the form on a page and take a look at the method to customize it. There are many widgets that Symfony provides for us. You can find the classes for them inside the widget/ directory of your Symfony installation. The Symfony propel task always generates a form class and its corresponding base class. Of course, not all of our tables will need to have a form bound to them. Therefore, delete all the form classes that are not needed. Rendering the form Rendering this basic form requires us to instantiate the form object in the action. Assigning the form object to the global $this variable means that we can pass the form object to the template just like any other variable. So let's start by implementing the newsletter signup module. In your terminal window, execute the generate:module task like this: $/home/timmy/workspace/milkshake>symfony generate:module frontend signup Now we can start with the application logic. Open the action class from apps/frontend/modules/signup/actions/actions.class.php for the signup module and add the following logic inside the index action: public function executeIndex(sfWebRequest $request) { $this->form = new NewsletterSignupForm(); return sfView::SUCCESS; } As I had mentioned earlier, the form class deals with the form validation and rendering. For the time being, we are going to stick to the default layout by allowing the form object to render itself. Using this method initially will allow us to create rapid prototypes. Let's open the apps/frontend/signup/templates/indexSuccess.php template and add the following view logic: <form action="<?php echo url_for('signup/submit') ?>" method="POST"> <table><?php echo $form ?></table> <input type="submit" /> </form> The form class is responsible for rendering of the form elements only. Therefore, we have to include the <form> and submit HTML tags that wrap around the form. Also, the default format of the form is set to 'table'. Again, we must also add the start and end tags of the <table>. At this stage, we would normally be able to view the form in the browser. But doing so will raise a Symfony exception error. The cause of this is that the results retrieved from the newsletter_adverts table are in the form of an array of objects. These results need to populate the select box widget. But in the current format, this is not possible. Therefore, we have to convert each object into its string equivalent. To do this, we need to create a PHP magic function of __toString() in the DAO class NewsletterAds. The DAO class for NewlsetterAds is located at lib/model/NewsletterAds.php just as all of the other models. Here we need to represent each object as its name, which is the value in the advertised column. Remember that we need to add this method to the DAO class as this represents a row within the results, unlike the peer class that represents the entire result set. Let's add the function to the NewsletterAds class as I have done here: class NewsletterAds extends BaseNewsletterAds { public function __toString() { return $this->getAdvertised(); } } We are now ready to view the completed form. In your web browser, enter the URL http://milkshake/frontend_dev.php/signup and you will see the result shown in the following screenshot: As you can see, although the form has been rendered according to our table structure, the fields which we do not want the user to fill in are also included. Of course, we can change this quiet easily. But before we take a look at the layout of the form, let's customize the widgets and widget validators. Now we can begin working on the application logic for submitting the form. Customizing form widgets and validators All of the generated form classes are located in the lib/form and the lib/form/base directories. The latter is where the default generated classes are located, and the former is where the customizable classes are located. This follows the same structure as the models. Each custom form class inherits from its parent. Therefore, we have to override some of the functions to customize the form. Let's customize the widgets and validators for the NewsletterSignupForm. Open the lib/forms/NewsletterSignupForm.class.php file and paste the following code inside the configure() method: //Removed unneeded widgets unset( $this['created_at'], $this['updated_at'], $this['activation_key'], $this['activated'], $this['id'] ); //Set widgets //Modify widgets $this->widgetSchema['first_name'] = new sfWidgetFormInput(); $this->widgetSchema['newsletter_adverts_id'] = new sfWidgetFormPropelChoice(array('model' => 'NewsletterAds', 'add_empty' => true, 'label'=>'Where did you find us?')); $this->widgetSchema['email'] = new sfWidgetFormInput (array('label' => 'Email Address')); //Add validation $this->setValidators(array ('first_name'=> new sfValidatorString(array ('required' => true), array('required' => 'Enter your firstname')), 'surname'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your surname')), 'email'=> new sfValidatorString(array('required' => true), array('invalid' => 'Provide a valid email', 'required' => 'Enter your email')), 'newsletter_adverts_id' => new sfValidatorPropelChoice(array('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id'), array('required' => 'Select where you found us')), )); //Set post validators $this->validatorSchema->setPostValidator( new sfValidatorPropelUnique(array('model' => 'NewsletterSignup', 'column' => array('email')), array('invalid' => 'Email address is already registered')) ); //Set form name $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); //Set the form format $this->widgetSchema->setFormFormatterName('list'); Let's take a closer look at the code. Removing unneeded fields To remove the fields that we do not want to be rendered, we must call the PHP unset() method and pass in the fields to unset. As mentioned earlier, all of the fields that are rendered need a corresponding validator, unless we unset them. Here we do not want the created_at and activation_key fields to be entered by the user. To do so, the unset() method should contain the following code: unset( $this['created_at'], $this['updated_at'], $this['activation_key'], $this['activated'], $this['id'] ); Modifying the form widgets Although it'll be fine to use the remaining widgets as they are, let's have a look at how we can modify them: //Modify widgets $this->widgetSchema['first_name'] = new sfWidgetFormInput(); $this->widgetSchema['newsletter_adverts_id'] = new sfWidgetFormPropelChoice(array('model' => 'AlSignupNewsletterAds', 'add_empty' => true, 'label'=>'Where did you find us?')); $this->widgetSchema['email'] = new sfWidgetFormInput(array('label' => 'Email Address')); There are several types of widgets available, but our form requires only two of them. Here we have used the sfWidgetFormInput() and sfWidgetFormPropelChoice() widgets. Each of these can be initialized with several values. We have initialized the email and newsletter_adverts_id widgets with a label. This basically renders the label field associated to the widget on the form. We do not have to include a label because Symfony adds the label according to the column name. Adding form validators Let's add the validators in a similar way as we have added the widgets: //Add validation $this->setValidators(array( 'first_name'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your firstname')), 'surname'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your surname')), 'email'=> new sfValidatorEmail(array('required' => true), array('invalid' => 'Provide a valid email', 'required' => 'Enter your email')), 'newsletter_adverts_id' => new sfValidatorPropelChoice(array ('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id'), array('required' => 'Select where you found us')), )); //Set post validators $this->validatorSchema->setPostValidator(new sfValidatorPropelUnique(array('model' => 'NewsletterSignup', 'column' => array('email')), array('invalid' => 'Email address is already registered')) ); Our form will need four different types of validators: sfValidatorString: This checks the validity of a string against a criteria. It takes four arguments—required, trim, min_length, and max_length. SfValidatorEmail: This validates the input against the pattern of an email address. SfValidatorPropelChoice: It validates the value with the values in the newsletter_adverts table. It needs the model and column that are to be used.   SfValidatorPropelUnique: Again, this validator checks the value against the values in a given table column for uniqueness. In our case, we want to use the NewsletterSignup model to test if the email column is unique. As mentioned earlier, all the fields must have a validator. Although it's not recommended, you can allow extra parameters to be passed in. To achieve this, there are two steps: You must disable the default option of having all fields validated by $this->validatorSchema->setOption('allow_extra_fields', true). Although the above step allows the values to bypass validation, they will be filtered out of the results. To prevent this, you will have to set $this->validatorSchema->setOption('filter_extra_fields', false). Form naming convention and setting its style The final part we added is the naming convention for the HTML attributes and the style in which we want the form rendered. The HTML output will use our naming convention. For example, in the following code, we have set the convention to newsletter_signup[fieldname] for each input field's name. //Set form name $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); //Set the form format $this->widgetSchema->setFormFormatterName('list'); Two formats are shipped with Symfony that we can use to render our form. We can either render it in an HTML table or an unordered list. As we have seen, the default is an HTML table. But by setting this as list, the form is now rendered as an unordered HTML list, just like the following screenshot. (Of course, I had to replace the <table> tags with the <ul> tags.)
Read more
  • 0
  • 0
  • 2207

article-image-quality-assurance-asterisk-16
Packt
18 Nov 2009
10 min read
Save for later

Quality Assurance in Asterisk 1.6

Packt
18 Nov 2009
10 min read
The world has changed quite a bit in the last 150 years. Over this time, the telephone system has been invented, improved, and automated. Telephone switches no longer refer to people sitting in a large room connecting wires between the appropriate jacks. Flexible and powerful telephone service has moved from a dream to an expectation in large businesses, and for most of us it is a necessity. Today, telephone systems are the lifeblood of business. They are how we take orders, acquire supplies, and even call for emergency assistance. With the increase in prominence of telephones, the expectations of telephone users have increased proportionally. Not only have the technological expectations for telephone systems increased dramatically, but consumers are expecting more and more out of the businesses they call. Customers expect to be helped quickly and professionally. They want to know everything in a matter of minutes. Roads do not hold the only rage our society is facing today. As a business we have a variety of questions relating to our telephone system such as: How are our personnel handling angry callers? Are our employees answering the calls in a reasonable amount of time? Do we have any workers using the phone system for personal calls when they should be doing their job? We will never be able to make sure everybody does what they are supposed to do all of the time. What we will be able to do at the end of this article is perform spot-checks on how we are doing on customer service, and make sure our phone service isn't being used for unauthorized purposes. Ultimately, it comes down to a matter of trust; however, some people do not know better because they haven't been fully trained. Most will always act honorably; however, some just cannot and should not be trusted. We will try to find out who is who. Call Detail Records When we talk about security, what images come to mind? May be a big, burly guard? Perhaps a bunch of guys in green, carrying machine guns? Do we imagine a person with a metal-detecting wand? Or do we think of thick glass window panes? All of these are security features. It is just that some are a little more intrusive than others. Each time we increase security, we become a little bit less friendly. We all have to decide how far we are willing (and able) to go. In the continuum of security, Call Detail Records are the least intrusive. No special usernames or passwords have to be remembered. No fear of big brother breathing down your customers' and users' necks need be felt. We are simply doing the same thing telephone companies do—tracking what calls were made, when they were made, how long they lasted, where they came from, and a few other bits of information. This information is then available for us to review at our leisure. Asterisk gives us a few options on how we track this information. The two major choices are flat-file logging and database logging. Flat-file CDR logging By default, Asterisk includes a module called cdr_csv. Right out of the box, Asterisk logs all calls coming in and going out. The information for these calls is placed in a Comma Separated Value (CSV) file. This CSV file is located in var/log/asterisk/cdr-csv. All information is available in Master.csv, and some channels can be configured to send some information to other files as well. The benefit of using a CSV file is the simplicity. Right after compiling and installing Asterisk, this method will work. No additional configuration is required. Also, no additional network traffic is generated, and no additional services have to be installed on our server. When using the CSV form of CDR, we will see lists and lists of values. They are not very easy to parse, so here is the format, in the order in which they appear: account code: As determined by the channel (for DAHDI) or the user (for IAX and SIP) source: The source of the call destination: The destination of the call destination context caller ID channel: The channel of the source destination channel: If applicable last application: The last application run on the channel last application argument: The last argument to the last application on the channel start time: The time the call commenced answer time: The time the call was answered end time: The time the call ended duration: The difference between start time and end time billable seconds: The difference between answer time and end time, which must be less than the duration disposition: Either ANSWERED, NO ANSWER, or BUSY amaflags: As set for the channel or user, like account code uniqueid: A unique call identifier userfield: A user field set by the SetCDRUserField command We see that there are many items of information logged for each and every call. We can compare the billable seconds with our phone bill at the end of the month to make sure they're close. We can look at the destination and figure out if the calls were authorized. This gives us enough information to answer most questions we may have about a phone call. While we have enough information to answer questions, finding that answer is not very easy. We would have to scan through the entire file to try to find anything. If we are going to use an accounting package or reporting software, CSV may be exactly what we need. However, if we wish to use it in a more ad hoc sort of way, it is not very readable. Database CDR logging If we wish to read our CDR logs, it is most easily accomplished when the records are sortable. The easiest way to do this is to store our CDR records in a database. Even in this, Asterisk gives us choices. Included with Asterisk is support for PostgreSQL databases. In order to be able to install this, we must first have the postgresql-devel package installed on our system. If you have to install this package, you'll need to reinstall Asterisk. The automake system will automatically detect that we have the capability to use PostgreSQL and compile that module for us. Aside from the development packages we have installed, we will also need a PostgreSQL server somewhere in our network. It can be the same machine as the Asterisk server, but it doesn't necessarily need to be. In fact, it probably makes sense to have only one such database server on our network, and we don't want to tie up too much of our PBX's resources with database maintenance and storage. There is a script in /usr/src/asterisk/contrib/scripts/ called postgres_cdr.sql, which creates the correct table structure for us. This script should be run from the database server. If we get an error message while rebuilding that says something like "cannot find-lz", then we need to install zlib-devel. Now that we have set up our database and installed the CDR module, we must configure Asterisk to use the correct database. In order to do this, we need to edit /etc/asterisk/cdr_pgsql.conf. All of the configuration variables are in the global section. Our file should look like the following: [global]hostname=dbserver.mydomain.tldport=5432dbname=asteriskpassword=supersecretuser=asteriskuser Once we have these variables set, the next time we restart Asterisk, all CDR records will be logged in the database. If PostgreSQL is not our database of choice, we can use MySQL. This is not a part of the normal distribution of Asterisk. But as we have already installed asterisk-addons, we should already have the ability to use MySQL for CDR logging. Before we compile, we need to make sure that we have mysql-devel installed. First, we need to decide which version we're going to use. Because of some license quibbles, MySQL version 4.0 and later is not in the automatic package distribution chain. Instead, if we do need to download it, we will have to get it directly from www.mysql.com. However, the older version (3.x) will work with Asterisk and hence, you may wish to take a look at the differences between what version 3 offered and what later versions give us. Other than the development package mentioned, we will also need a MySQL server somewhere in our network. Just as with PostgreSQL, we can choose to have it on the same server as Asterisk, but for the same reasons, we probably shouldn't. Next, on the database server, we need to create the database with a user and a table for the CDR data. We do this by running the following code: # mysqladmin create database asteriskcdrdb # mysqlmysql> GRANT ALL PRIVILEGES   -> ON asteriskcdrdb.*   -> TO asteriskcdruser   -> IDENTIFIED BY 'changethis2yourpassword';mysql> USE asteriskcdrdb;mysql> CREATE TABLE cdr (   -> uniqueid varchar(32) NOT NULL default '',   -> userfield varchar(255) NOT NULL default '',   -> accountcode varchar(20) NOT NULL default '',   -> src varchar(80) NOT NULL default '',   -> dst varchar(80) NOT NULL default '',   -> dcontext varchar(80) NOT NULL default '',   -> clid varchar(80) NOT NULL default '',   -> channel varchar(80) NOT NULL default '',   -> dstchannel varchar(80) NOT NULL default '',   -> lastapp varchar(80) NOT NULL default '',   -> lastdata varchar(80) NOT NULL default '',   -> calldate datetime NOT NULL default '0000-00-00 00:00:00',   -> duration int(11) NOT NULL default '0',   -> billsec int(11) NOT NULL default '0',   -> disposition varchar(45) NOT NULL default '',   -> amaflags int(11) NOT NULL default '0'-> ); That's all there is to it! We only have to do this once, so it's really not so bad. Next, we have to modify the /etc/asterisk/cdr_mysql.conf file to correctly reflect our choices. [global]hostname=ourdbserver.ourdomain.tlddbname=asteriskcdrdbpassword=changethis2yourpassworduser=asteriskcdruserport=3306userfield=1 The next time we restart Asterisk, our CDR information will be stored in the MySQL database. What does that give us? We now have the ability to use a number of very powerful tools to search our CDR records to find trends and patterns.
Read more
  • 0
  • 0
  • 1680

article-image-build-advanced-contact-manager-using-jboss-richfaces-33-part-3
Packt
18 Nov 2009
11 min read
Save for later

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 3

Packt
18 Nov 2009
11 min read
  The ajaxSingle and the process attributes The ajaxSingle property is very useful to control the form submission when ajaxSingle is set to true—the form is not submitted and, just the Ajax component data is sent. This attribute is available in every Ajax action component and we can use it to call an action from a button, skipping the form validation (like the JSF immediate property does), or to send the value of just an input into a form without validation and submitting the other ones. The second use case can be used, for example, when we need an input menu that dynamically changes the value of other inputs without submitting the entire form: <h:form> <!-- other input controls --> <h:selectOneMenu id="country" value="#{myBean.selectedCountry}"> <f:selectItems value="#{myBean.myCountries}"> <a:support event="onchange" ajaxSingle="true" reRender="city" /> </h:selectOneMenu> <h:selectOneMenu id="city" value="#{myBean.selectedCity}"> <f:selectItems value="#{myBean.myCities}"> </h:selectOneMenu> <!-- other input controls --></h:form> In this example, every time the user selects a new country, the value is submitted to the bean that recalculates the myCities property for the new country, after that the city menu will be re-rendered to show the new cities. All that without submitting the form or blocking the changes because of some validation problem. What if you would like to send more than one value, but still not the entire form? We can use Ajax regions (we will see in the next sections), or we can use the process attribute. It contains a list of components to process while submitting the Ajax action: <h:form> <!-- other input controls --> <h:inputText id="input1" ... /> <h:selectOneMenu id="country" value="#{myBean.selectedCountry}"> <f:selectItems value="#{myBean.myCountries}"> <a:support event="onchange" ajaxSingle="true" process="input2, input3" reRender="city" /> </h:selectOneMenu> <h:inputText id="input2" ... /> <h:inputText id="input3" ... /> <h:selectOneMenu id="city" value="#{myBean.selectedCity}"> <f:selectItems value="#{myBean.myCities}"> </h:selectOneMenu> <!-- other input controls --></h:form> In this example, we also wanted to submit the input2 and the input3 values together with the new country, because they are useful for retrieving the new cities list—just by setting the process attribute with the id list and during the submission, they will be processed. Thus input1 will not be sent. Also, for action components such as buttons, you can decide what to send using the ajaxSingle and process attributes. Form submission and processingWe speak about form "submission" to simplify the concept and make things more understandable. In reality, for every request, all of the form is submitted, but only the selected components (using ajaxSingle and/or process attributes) will be "processed". By "processed" we mean "pass through" the JSF phases (decoding, conversion, validation, and model updating). More Ajax! For every contact, we would like to add more customizable fields, so let's use the ContactField entity connected to every Contact instance. First of all, let's create a support bean called HomeSelectedContactOtherFieldsHelper inside the book.richfaces.advcm.modules.main package. It might look like this: @Name("homeSelectedContactOtherFieldsHelper")@Scope(ScopeType.CONVERSATION)public class HomeSelectedContactOtherFieldsHelper { @In(create = true) EntityManager entityManager; @In(required = true) Contact loggedUser; @In FacesMessages facesMessages; @In(required = true) HomeSelectedContactHelper homeSelectedContactHelper; // my code} A notable thing is highlighted—we injected the homeSelectedContactHelper component, because to get the list of the customized fields from the database, we need the contact owner. We also set the required attribute to true, because this bean can't live without the existence of homeSelectedContactHelper in the context. Now, let's add the property containing the list of personalized fields for the selected contact: private List<ContactField> contactFieldsList;public List<ContactField> getContactFieldsList() { if (contactFieldsList == null) { // Getting the list of all the contact fields String query = "from ContactField cf where cf.contact.id=:idContactOwner order by cf.id"; contactFieldsList = (List<ContactField>) entityManager.createQuery(query) .setParameter("idContactOwner", homeSelectedContactHelper.getSelectedContact() .getId()).getResultList(); } return contactFieldsList;}public void setContactFieldsList(List<ContactField> contactFieldsList) { this.contactFieldsList = contactFieldsList;} As you can see, it is a normal property lazy initialized using the getter. This queries the database to retrieve the list of customized fields for the selected contact. We have to put into the bean some other method useful to manage the customized field (adding and deleting field to and from the database), let's add those methods: public void createNewContactFieldInstance() { // Adding the new instance as last field (for inserting a new field) getContactFieldsList().add(new ContactField());}public void persistNewContactField(ContactField field) { // Attaching the owner of the contact field.setContact(homeSelectedContactHelper.getSelectedContact()); entityManager.persist(field);}public void deleteContactField(ContactField field) { // If it is in the database, delete it if (isContactFieldManaged(field)) { entityManager.remove(field); } // Removing the field from the list getContactFieldsList().remove(field);}public boolean isContactFieldManaged(ContactField field) { return field != null && entityManager.contains(field);} The createNewContactFieldInstance() method will just add a new (not yet persisted), empty instance of the ContactField class into the list. After the user has filled the values in, he/she will press a button that calls the persistNewContactField() method to save the new data into the database. In order to delete it, we are going to use the deleteContactField() method, and to determine if an instance is persisted into the database or not, we are going to use the isContactFieldManaged() method. Now, let's open the /view/main/contactView.xhtml file and add the code to show the personalized fields after h:panelGrid— i shows the standard ones: <a:repeat value="#{homeSelectedContactOtherFieldsHelper.contactFieldsList}" var="field"> <h:panelGrid columns="2" rowClasses="prop" columnClasses="name,value"> <h:outputText value="#{field.type} (#{field.label}):"/> <h:outputText value="#{field.value}"/> </h:panelGrid></a:repeat> We are using a new RichFaces data iteration component that permits us to iterate over a collection and put the data we want (the rich:dataTable component would instead create a table for the elements list). In our case, the h:panelGrid block will be repeated for every element of the collection (so for every customized field). Now, let's open the /view/main/contactEdit.xhtml file and add the code for editing the customized fields into the list: <a:region> <a:outputPanel id="otherFieldsList"> <a:repeat value="#{homeSelectedContactOtherFieldsHelper. contactFieldsList}" var="field"> <h:panelGrid columns="3" rowClasses="prop" columnClasses="name,value,validatormsg"> <h:panelGroup> <h:inputText id="scOtherFieldType" value="#{field.type}" required="true" size="5"> <a:support event="onblur" ajaxSingle="true"/> </h:inputText> <h:outputText value=" ("/> <h:inputText id="scOtherFieldLabel" value="#{field.label}" size="5"> <a:support event="onblur" ajaxSingle="true"/> </h:inputText> <h:outputText value=")"/><br/> <rich:message for="scOtherFieldType" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> </h:panelGroup> <h:panelGroup> <h:inputText id="scOtherFieldValue" value="#{field.value}" required="true"> <a:support event="onblur" ajaxSingle="true"/> </h:inputText><br/> <rich:message for="scOtherFieldValue" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> </h:panelGroup> <h:panelGroup> <a:commandButton image="/img/add.png" reRender="otherFieldsList" action="#{homeSelectedContactOtherFieldsHelper. persistNewContactField(field)}" rendered="#{!homeSelectedContactOtherFieldsHelper. isContactFieldManaged(field)}"> </a:commandButton> <a:commandButton image="/img/remove.png" reRender="otherFieldsList" ajaxSingle="true" action="#{homeSelectedContactOtherFieldsHelper. deleteContactField(field)}"> </a:commandButton> </h:panelGroup> </h:panelGrid> </a:repeat> <a:commandLink reRender="otherFieldsList" ajaxSingle="true" action="#{homeSelectedContactOtherFieldsHelper. createNewContactFieldInstance}" rendered="#{homeSelectedContactHelper. selectedContactManaged}" styleClass="image-command-link"> <h:graphicImage value="/img/add.png"/> <h:outputText value="#{messages['addNewField']}"/> </a:commandLink> </a:outputPanel></a:region> The code looks very similar to the one in the view box, except for the action buttons (to add a new instance, persist, save, or delete) and, for the presence of the surrounding tag a:region (highlighted). This is very important in order to make sure the form works correctly; we will see why in the next section. Also, notice that every input component has the a:support tag as a child that will update the bean with the edited value at the onblur event (which means that every time you switch the focus to another component, the value of the last one is submitted). So, if you delete or add a field, you will now loose the edited values for other fields. It is also used for Ajax validation, as the user is informed that the value is not valid when it moves the cursor to another input. Here is a screenshot with the new feature in the edit box: Using a:support only for Ajax validation If you want to use the a:support tag only for validation purpose, remember to set its bypassUpdates attribute to true, so the process would be faster as the JSF Update Model and Invoke Application phases will not be invoked. Ajax containers While developing a web application with RichFaces, it's very useful to know how to use Ajax containers (such as the a:region component) in order to optimize Ajax requests. In this section, we'll discuss about the a:region component. It is a very important component of the framework—it can define Ajax areas to limit the part of the component tree to be processed during an Ajax request. Regions can be nested during an Ajax request and the closest one will be used. By setting to true the a:region attribute called regionRenderOnly, you can use this component to limit the elements' update—In this way, in fact, only the components inside the region can be updated. Another important attribute is selfRendered; setting this to true tells the framework to render the response basing on component tree without referring to the page code—it is faster, but all of the transient elements that are not saved in the tree (such as f:verbatim or HTML code written directly without using JSF components) will be lost at the first refresh, so you can't use them in this case. To summarize, it is very useful to control the rendering process and optimize it, in order to limit the elements of a form to send during an Ajax request without validation problems, to show different indicators for Ajax status. Example of using a:region: <h:form> <a:region> <h:inputText id="it1" value="#{aBean.text1}"> <a:support event="onkeyup" reRender="text1" /> </h:inputText> <h:inputText id="it2" value="#{aBean.text2}" /> </a:region> <h:inputText id="it3" value="#{aBean.text3}" /> <a:commandButton action="#{aBean.saveTexts}" reRender="text1,text2" /></h:form><h:outputText id="text1" value="#{aBean.text1}" /><h:outputText id="text2" value="#{aBean.text2}" /> In this example, while the user is typing in the text1 value of inputText, a:support sends an Ajax request containing only the it1 and it2 values of inputText. In this case, in fact, a:region limits the components sent by every Ajax request originated from inside the region. So, the Ajax request will only update aBean.text1 and aBean.text2. Wrapping only a component inside an Ajax region is the equivalent of using the ajaxSingle property set to true. If the user clicks on the a:commandButton aBean.text1, the aBean.text2 and aBean.text3 values will be updated by the Ajax request. Coming back to our application, as all the customized fields are inside the same form component, we surround each one with the a:region tag. In this way, the single field is submitted regardless of the other ones. For example, without using a:region, if the user empties the name input value and then tries to insert a new customized field, the process will fail because the name input is not validated. If we use the a:region component, the name field will not be processed and a new field will be inserted. Now that we know how to use the a:region tag, we can combine it with ajaxSingle and process in order to decide what to send at every request, and to better optimize Ajax interactions into the application.
Read more
  • 0
  • 0
  • 1402
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-papervision3d-external-models-part-2
Packt
17 Nov 2009
7 min read
Save for later

Papervision3D External Models: Part 2

Packt
17 Nov 2009
7 min read
Creating and loading models using SketchUp SketchUp is a free 3D modeling program, which was acquired by Google in 2006. It has been designed as a 3D modeling program that's easier to use than other 3D modeling programs. A key to its success is its easy learning curve compared to other 3D tools. Mac OS X, Windows XP, and Windows Vista operating systems are supported by this program that can be downloaded from http://sketchup.google.com/. Although there is a commercial SketchUp Pro version available, the free version works fine in conjunction with Papervision3D. An interesting feature for non-3D modelers is the integration with Google's 3D Warehouse. This makes it possible to search for models that have been contributed by other SketchUp users. These models are free of any rights and can be used in commercial (Papervision3D) projects. Exporting a model from Google's 3D Warehouse for Papervision3D There are several ways to load a model, coming from Google 3D Warehouse, into Papervision3D. One of them is by downloading a SketchUp file and exporting it to a format Papervision3D works with. This approach will be explained. The strength of Google 3D Warehouse is also its weakness. Anybody with a Google account can add models to the warehouse. This results in a variety of quality of the models. Some are very optimized and work fluently, whereas others reveal problems when you try to make them work in Papervision3D. Or they may not work at all, as they're made of too many polygons to run in Papervision3D. Take this into account while searching for a model in the 3D warehouse. For our example we're going to export a picnic table that was found on Google 3D Warehouse. Start Sketch Up. Choose a template when prompted. This example uses Simple Template – Meters, although there shouldn't be a problem with using one of the other templates. Go to File | 3D Warehouse | Get models to open 3D Warehouse inside SketchUp. Enter a keyword to search for. In this example that will be picnic table. Select a model of your choice. Keep in mind that it has to be low poly, which is something you usually find out by trial and error. Click on Download Model, to import the model into SketchUp and click OK when asked if you want to load the model directly into your Google SketchUp model. Place the model at the origin of the scene. To follow these steps, it doesn't have to be the exact origin, approximately is good enough. By default, a 2D character called Sang appears in the scene, which you do not necessarily have to remove; it will be ignored during export. Because the search returned a lot of picnic tables varying in quality, there is a SketchUp file (can be downloaded from http://www.packtpub.com/files/code/5722_Code.zip). This file has a picnic table already placed on the origin. Of course, you could also choose another picnic table, or any other object of your choice. Leave the model as it is and export it. Go to File | Export | 3D Model. Export it using the Google Earth (*.kmz) format and save it in your assets folder. T he file format we're exporting to originally was meant to display 3D objects in Google Earth. The file ends with .kmz as its extension, and is actually a ZIP archive that contains a COLLADA file and the textures. In the early days of Papervision3D, it was a common trick to create a model using SketchUp and then get the COLLADA file out of the exported Google Earth file, as the Google Earth KMZ file format wasn't still supported then. Importing a Google Earth model into Papervision3D Now that we have successfully exported a model from SketchUp, we will import it into Papervision3D. This doesn't really differ from loading a COLLADA or 3D Studio file. The class we use for parsing the created PicnicTable.kmz file is called KMZ and can be found in the parsers package. Add the following line to the import section of your document class: import org.papervision3d.objects.parsers.KMZ; Replace or comment the code that loads the animated COLLADA model and defines the animations from the previous example. In the init() method we can then instantiate the KMZ class, assign it to the model class property, and load the KMZ file. Make sure you have saved PicnicTable.kmz file into the assets folder of your project. model = new KMZ();model.addEventListener(FileLoadEvent.LOAD_COMPLETE,modelLoaded);KMZ(model).load("assets/PicnicTable.kmz"); ExternalModelsExample That looks familiar, right? Now let's publish the project and your model should appear on the screen. Notice that in many cases, the downloaded and exported model from Google 3D Warehouse might appear very small on your screen in Papervision3D. This is because they are modeled with other metric units than we use in Papervision3D. Our example application places the camera at a 1000 units away from the origin of the scene. Many 3D Warehouse models are made using units that define meters or feet, which makes sense if you were to translate them to real-world units. When a model is, for example, 1 meter wide in SketchUp, this equals to 1 unit in Papervision3D. As you can imagine, a 1 unit wide object in Papervision3D will barely be visible when placing the camera at a distance of 1000. To solve this you could use one of the following options: Use other units in Papervision3D and place your camera at a distance of 5 instead of 1000. Usually you can do this at the beginning of your project, but not while the project is already in progress, as this might involve a lot of changes in your code due to other objects, animations, and calculations that are made with a different scale. Scale your model inside SketchUp to a value that matches the units as you use them in Papervision3D. When the first option can't be realized, this option is recommended. Scale the loaded model in Papervision3D by changing the scale property of your model. model.scale = 20; Although this is an option that works, it's not recommended. Papervision3D has some issues with scaled 3D objects at the time of writing. It is a good convention to use the same units in Papervision3D and your 3D modeling tool. If you want to learn more about modeling with SketchUp, visit the support page on the SketchUp web site http://sketchup.google.com/support. You'll find help resources such as video tutorials and a help forum. Creating and loading models using Blender Blender is an open source, platform-independent 3D modeling tool, which was first released in 1998 as a shareware program. It went open source in 2002. Its features are similar to those in commercial tools such as 3ds Max, Maya, and Cinema4D. However, it has a reputation of having a difficult learning curve compared to other 3D modeling programs. Blender is strongly based on usage of keyboard shortcuts and not menus, which makes it hard for new users to find the options they're looking for. In the last few years, more menu-driven interfaces have been added. It's not in the scope of this article to teach you everything about the modeling tools that can be used with Papervision3D. This also counts for Blender. There are many resources such as online tutorials and books that cover how to work with Blender. A link to the Blender installer download can be found on its web site: www.blender.org.
Read more
  • 0
  • 0
  • 2020

article-image-providing-context-using-custom-text-upk-35
Packt
17 Nov 2009
10 min read
Save for later

Providing context using Custom Text in UPK 3.5

Packt
17 Nov 2009
10 min read
The Start Frame You may recall that the very first Frame in a Topic is the Start Frame. This is sometimes referred to as the Introduction frame, which is largely a throwback to OnDemand, as we shall see shortly. As this Frame is the first thing that a trainee will see when they carry out a Topic, coupled with the fact that this Frame is a "non-action" Frame, in that the user does not need to actively do anything (other than pressing Enter to continue), it is a good place to provide some additional information to the trainee. The first thing that you should explain in the Introduction frame is exactly what the trainee will be learning in the exercise. Certainly the title of the Topic should give them a clue, but this is not really detailed enough. A good source of information for this is the learning objectives of the course for which this exercise has been built, or the competencies, depending on your curriculum development process. For our sample exercise, we could use the bubble shown in the following screenshot: This is a good start, but we can do more. It is always useful, with training exercises, to use realistic business scenarios to explain what the trainee is doing, to put the keystrokes and mouse-clicks into a business context. Trainees are much more likely to remember information to which they can relate. Consider telling a story and walking the trainees through that story as they carry out the exercise. Although it is a fairly spurious example, we will continue with our sample exercise. Here, we could use the bubble shown in the following screenshot: So now the trainee has a good idea of what they will learn, and they have an example that they can relate to. The text is also directed at the trainee, so the trainee will feel actively involved. There is another strong argument for including a scenario in the form shown above. Consider the case where you are providing training for users in multiple locations (possibly countries) or departments, each of which has its own set of customers, products, and vendors. Users will always want to see exercises using their data: orders for their products, placed at their location, and so on. To keep everyone happy, you would need to develop a separate, customized Topic for each location or user group. If the basic process (and, most importantly for us, the Actions in the recording) is the same in each case, this is clearly inefficient. However, if you create a scenario, and say something like "You are a Customer Service Representative in the Tampa Service Center. Customer SunCo has phoned through an order for 1,000 gallons of regular gasoline. You need to record this order in the system so that it can be fulfilled by Fuel Services." then trainees who are not at the Tampa Service Center will at least understand that this is role play. It is make believe, and they shouldn't be concerned that they don't see products that they don't supply at their own location. So set the scene with a scenario in the Introduction pane, and then build on this throughout the exercise. Introduction Text: Version differences At this point, it is worth highlighting some key differences between the way the Introduction pane was handled prior to UPK 3.5, and the way that the Introduction pane is used in UPK 3.5. If you open an Outline Element in the Outline Editor, and select a Topic in the navigation tree, you will see that the lower-right portion of the screen is labeled Introduction, as shown in the following screenshot: The Outline Editor is used to organize content objects into the structure that the trainee will see. However, if we look at the published version of the Outline above, we will see the following: Where has the Introduction Frame gone? Put simply. UPK 3.5 does not display the Introduction Frame in the Player any more. Users of UPK 2.x or OnDemand 9.x will recall that the Introduction Frame certainly used to be displayed, as shown in the following screenshot, which is taken from OnDemand 9.1.5: Quite why Oracle decided to change the way the Introduction pane is displayed (or not) in version 3.5 is a mystery (maybe they felt that with all that screen space required for the new Oracle branding, there just wasn't the space left to include the Introduction any more). However, it does have some important implications on the way that we are planning on using it. This is because the effect of a publishing option associated with the Introduction pane has changed significantly. In the Publishing Wizard, the options for the Player package include an option to Show introduction text. In OnDemand version 9.1, this option determined whether the Introduction text appeared in the Outline as well as on the first Frame of the Player. However, in UPK 3.5, the Introduction text is never displayed in the Outline and the Show introduction text determines whether the Introduction text appears in the Player at all (effectively, it controls whether the Start Frame is included in the Player or not. Version Difference The Content Development manual for OnDemand 9.1.5 describes the Show introduction text option as working the way described for UPK 3.5. It doesn't work that way; it works the way described for OnDemand 9.1, above. This is clearly a rare case of the documentation being updated before the software. For our purposes, therefore, we need to make sure that the Show introduction text option is always selected when we publish. This option can be found in the Player category of your Options, as shown in the next screenshot: Action Frames It is possible to add Custom Text to the Topic's Bubbles, either in addition to, or instead of, the Template Text. Using the Template Text has several significant advantages, especially when localizing your content or providing sound. However, the Template Text will only ever be able to describe the mechanics of what the user is doing, it cannot provide business context. You should always try to teach more than just key-strokes and mouse-clicks. Specifically, you should always take the opportunity to add business context yourself, through the liberal use of Custom Text in Action Frames. Consider the following example that uses solely the default template texts: Certainly the trainee can carry out the required action and work their way through the exercise, but are they really learning anything? What is the Ext.Ref field, and what is the significance of the value ZBW002342? Should they always enter this value, or are other values possible? Here, we should help the trainee out and teach them something by providing some more information through the use of Custom Text. A better version is shown in the following screenshot: Now the trainee knows exactly what they are entering in the exercise, and understands the business context so they can perform the action correctly when they are doing their actual job. Note that here, we have retained the Template Text (we did not insert the Template Text as Custom Text) which will aid in the translation (although the custom text will still need to be manually translated). We simply added the first paragraph that you see in the Bubble above as Custom Text, and positioned it before the Template Text (the Show custom text first button () is selected by default; you can deselect this if required, to have the Template Text displayed first, but for our purposes we want the Custom Text first).   UPK will run the Template Text in the next line immediately after the Custom Text, so you need to insert an extra line break at the end of the Custom Text if you want the two texts to appear as separate paragraphs. In this example, note that we have continued the scenario that we described in the Introduction pane through into this exercise, by mentioning the customer's name. Again, it is always useful to use a scenario so that the trainee can better relate the exercise to their actual jobs. Note that the text For this exercise...is ZBW002342 will need to be tagged to appear only in See It! and Try It! modes. UPK will run the Template Text in the next line immediately after the Custom Text, so you need to insert an extra line break at the end of the Custom Text if you want the two texts to appear as separate paragraphs. In this example, note that we have continued the scenario that we described in the Introduction pane through into this exercise, by mentioning the customer's name. Again, it is always useful to use a scenario so that the trainee can better relate the exercise to their actual jobs. Note that the text For this exercise...is ZBW002342 will need to be tagged to appear only in See It! and Try It! modes. Whenever practical, you should try to provide some more information, whether this is business context, or a continuation of the scenario you are using, even if this is on every Frame. If you intend for your simulations being used outside of a classroom environment, then you should consider providing exactly the same level of information as the instructor would provide in a classroom. Think about what you would say to the trainee, what additional information or guidance you would give them if you sat next to them, talking them through the simulation, and then add that information into the Bubbles as Custom Text. Remember: training is the effective transfer of knowledge, and if that knowledge is incomplete, then the trainees have not been adequately trained. The End Frame The End Frame is always displayed as the final Frame in the simulation. There is no End Frame equivalent of the Show introduction text option to avoid having this Frame displayed. This is a good thing, as it means that we can use this Frame to provide some final information to the user. This should be seen as a companion to the Start Frame, and should confirm the information presented in the Start Frame. In the Start Frame above, we told the trainee what they would learn. In the End Frame, we should confirm that they have learned this. (This much is standard training theory.) If you have described a scenario in the Start Frame, and followed this through the Action Frames, then you should make reference to this, as well. Suitable End Frame text for our ongoing exercise on SAP user options could be: Although the scenario information is again fairly spurious in this example, it does at least give you an idea of the kind of information that can usefully be included in the End Frame. Again, this information should be tagged for See It! and Try It! modes only. Note that in this example we have also included a message of You have now completed this exercise. This is a nice courtesy, and confirms to the trainee that they have reached the end of the simulation.
Read more
  • 0
  • 0
  • 3854

article-image-setting-your-moodle-grade-book
Packt
17 Nov 2009
3 min read
Save for later

Setting up your Moodle Grade Book

Packt
17 Nov 2009
3 min read
Set your classes up with no effort on your part Why would you need to "set your classes up"? Surely if the course is enroll able, your students can just enter and take part in all the activities? They can indeed, but unless you put a password (enrolment key) on the course then any students on your Moodle could enroll and take a look around. That might be fine if you have an "open door" policy to your course – but in the real world, I wouldn't expect extra students to come in and join my Advanced Level French class, and so the same might apply in Moodle.  Likewise, although my colleague and I share teaching resources and experiences, we don't actually mark each other's students' work – we have separate grade books. The same should apply to Moodle. If more than one class is sharing a Moodle course, it’s important that the classes are on different pages of the grade book rather than everyone all listed together. So how do we do this without having manually to add every single student?  If your Moodle admin hasn't done this for you already, then log in to your course and click on Groups in the course administration block: Now click on the button Create group as in the next screenshot: Write the name of your class in the Group Name box, as it might say in their timetable for example. If you wish to put a description of this class you may do so, but it isn’t necessary. Scroll down to the Enrolment key box and enter a password for this class only. (Hint: it might be easier for them to recall if you make it the name of their class) If you click Unmask you will be able to see what you are typing: Click the Save changes button, and you will be returned to the Groups page where you will see your class with (0) next to its name. That's telling you there are no students in there yet! Repeat the process with all the other classes sharing your Moodle course. Your Groups page might end up like this: And finally… click to go back to your main course page and then, in the course administration block, click on Settings. In the page that comes up next, set Group mode to Visible or Separate and in  Availability  set another password (enrollment key). This enrollment key never gets used! It is simply there to keep unwanted students out! It doesn’t matter what you set it to or even whether you yourself remember it or not.
Read more
  • 0
  • 0
  • 1701

article-image-create-quick-application-cakephp-part-1
Packt
17 Nov 2009
9 min read
Save for later

Create a Quick Application in CakePHP: Part 1

Packt
17 Nov 2009
9 min read
The ingredients are fresh, sliced up, and in place. The oven is switched on, heated, and burning red. It is time for us to put on the cooking hat, and start making some delicious cake recipes. So, are you ready, baker? In this article, we are going to develop a small application that we'll call the "CakeTooDoo". It will be a simple to-do-list application, which will keep record of the things that we need to do. A shopping list, chapters to study for an exam, list of people you hate, and list of girls you had a crush on are all examples of lists. CakeTooDoo will allow us to keep an updated list. We will be able to view all the tasks, add new tasks, and tick the tasks that are done and much more. Here's another example of a to-do list, things that we are going to cover in this article: Make sure Cake is properly installed for CakeTooDoo Understand the features of CakeTooDoo Create and configure the CakeTooDoo database Write our first Cake model Write our first Cake controller Build a list that shows all the tasks in CakeTooDoo Create a form to add new tasks to CakeTooDoo Create another form to edit tasks in the to-do list Have a data validation rule to make sure users do not enter empty task title Add functionality to delete a task from the list Make separate lists for completed and pending Tasks Make the creation and modification time of a task look nicer Create a homepage for CakeTooDoo Making Sure the Oven is Ready Before we start with CakeTooDoo, let's make sure that our oven is ready. But just to make sure that we do not run into any problem later, here is a check list of things that should already be in place: Apache is properly installed and running in the local machine. MySQL database server is installed and running in the local machine. PHP, version 4.3.2 or higher, is installed and working with Apache. The latest 1.2 version of CakePHP is being used. Apache mod_rewrite module is switched on. AllowOverride is set to all for the web root directory in the Apache configuration file httpd.conf. CakePHP is extracted and placed in the web root directory of Apache. Apache has write access for the tmp directory of CakePHP. In this case, we are going to rename the Cake directory to it CakeTooDoo. CakeTooDoo: a Simple To-do List Application As we already know, CakeTooDoo will be a simple to-do list. The list will consist of many tasks that we want to do. Each task will consist of a title and a status. The title will indicate the thing that we need to do, and the status will keep record of whether the task has been completed or not. Along with the title and the status, each task will also record the time when the task has been created and last modified. Using CakeTooDoo, we will be able to add new tasks, change the status of a task, delete a task, and view all the tasks. Specifically, CakeTooDoo will allow us to do the following things: View all tasks in the list Add a new task to the list Edit a task to change its status View all completed tasks View all pen Delete a task A homepage that will allow access to all the features. You may think that there is a huge gap between knowing what to make and actually making it. But wait! With Cake, that's not true at all! We are just 10 minutes away from the fully functional and working CakeTooDoo. Don't believe me? Just keep reading and you will find it out yourself. Configuring Cake to Work with a Database The first thing we need to do is to create the database that our application will use. Creating database for Cake applications are no different than any other database that you may have created before. But, we just need to follow a few simple naming rules or conventions while creating tables for our database. Once the database is in place, the next step is to tell Cake to use the database. Time for Action: Creating and Configuring the Database Create a database named caketoodoo in the local machine's MySQL server. In your favourite MySQL client, execute the following code: CREATE DATABASE caketoodoo; In our newly created database, create a table named tasks, by running the following code in your MySQL client: USE caketoodoo; CREATE TABLE tasks ( id int(10) unsigned NOT NULL auto_increment, title varchar(255) NOT NULL, done tinyint(1) default NULL, created datetime default NULL, modified datetime default NULL, PRIMARY KEY (id) ); Rename the main cake directory to CakeTooDoo, if you haven't done that yet. Move inside the directory CakeTooDoo/app/config. In the config directory, there is a file named database.php.default. Rename this file to database.php. Open the database.php file with your favourite editor, and move to line number 73, where we will find an array named $default. This array contains database connection options. Assign login to the database user you will be using and password to the password of that user. Assign database to caketoodoo. If we are using the database user ahsan with password sims, the configuration will look like this: var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'port' => '', 'login' => 'ahsan', 'password' => 'sims', 'database' => 'caketoodoo', 'schema' => '', 'prefix' => '', 'encoding' => '' ); Now, let us check if Cake is being able to connect to the database. Fire up a browser, and point to http://localhost/CakeTooDoo/. We should get the default Cake page that will have the following two lines: Your database configuration file is present and Cake is able to connect to the database, as shown in the following screen shot. If you get the lines, we have successfully configured Cake to use the caketoodoo database. What Just Happened? We just created our first database, following Cake convention, and configured Cake to use that database. Our database, which we named caketoodoo, has only one table named task. It is a convention in Cake to have plural words for table names. Tasks, users, posts, and comments are all valid names for database tables in Cake. Our table tasks has a primary key named id. All tables in Cake applications' database must have id as the primary key for the table. Conventions in CakePHPDatabase tables used with CakePHP should have plural names. All database tables should have a field named id as the primary key of the table. We then configured Cake to use the caketoodoo database. This was achieved by having a file named database.php in the configuration directory of the application. In database.php, we set the default database to caketoodoo. We also set the database username and password that Cake will use to connect to the database server. Lastly, we made sure that Cake was able to connect to our database, by checking the default Cake page. Conventions in Cake are what make the magic happen. By favoring convention over configuration, Cake makes productivity increase to a scary level without any loss to flexibility. We do not need to spend hours setting configuration values to just make the application run. Setting the database name is the only configuration that we will need, everything else will be figured out "automagically" by Cake. Throughout this article, we will get to know more conventions that Cake follows.   Writing our First Model Now that Cake is configured to work with the caketoodoo database, it's time to write our first model. In Cake, each database table should have a corresponding model. The model will be responsible for accessing and modifying data in the table. As we know, our database has only one table named tasks. So, we will need to define only one model. Here is how we will be doing it: Time for Action: Creating the Task Model Move into the directory CakeTooDoo/app/models. Here, create a file named task.php. In the file task.php, write the following code: <?php class Task extends AppModel { var $name = 'Task'; } ?> Make sure there are no white spaces or tabs before the <?php tag and after the ?> tag. Then save the file. What Just Happened? We just created our first Cake model for the database table tasks. All the models in a CakePHP application are placed in the directory named models in the app directory. Conventions in CakePHP: All model files are kept in the directory named models under the app directory. Normally, each database table will have a corresponding file (model) in this directory. The file name for a model has to be singular of the corresponding database table name followed by the .php extension. The model file for the tasks database table is therefore named task.php. Conventions in CakePHP: The model filename should be singular of the corresponding database table name. Models basically contain a PHP class. The name of the class is also singular of the database table name, but this time it is CamelCased. The name of our model is therefore Task. Conventions in CakePHP: A model class name is also singular of the name of the database table that it represents. You will notice that this class inherits another class named AppModel. All models in CakePHP must inherit this class. The AppModel class inherits another class called Model. Model is a core CakePHP class that has all the basic functions to add, modify, delete, and access data from the database. By inheriting this class, all the models will also be able to call these functions, thus we do not need to define them separately each time we have a new model. All we need to do is to inherit the AppModel class for all our models. We then defined a variable named $name in the Task'model, and assigned the name of the model to it. This is not mandatory, as Cake can figure out the name of the model automatically. But, it is a good practice to name it manually.
Read more
  • 0
  • 0
  • 3547
article-image-using-web-pages-upk-35
Packt
16 Nov 2009
12 min read
Save for later

Using Web Pages in UPK 3.5

Packt
16 Nov 2009
12 min read
Using Web Pages in the Concept pane The most common use of Web Pages is to provide context information for Topics. Look at the following image of the Outline for our example course: You will see that the upper-right section of the Outline window contains a pane labeled Concept. If you want any information to be displayed in this pane, then you need to create a Web Page and attach it to the content object in the Outline. Version Difference Although the Concept pane always appears in the Outline view, if it is empty, then it does not appear in the Player. This is, thankfully, a new feature in UPK 3.5. In previous versions, the Concept pane always appeared in the Player, often appearing as a blank frame, where developers couldn't be bothered to provide any concepts. To create a new Web Page and attach it to a content object, carry out the following steps: Open the Outline containing the content object to which you want to attach the Web Page, in the Outline Editor. Click on the content object to select it. Although in this example we are attaching a Web Page to the concept pane for a Topic, Modules, and Sections also have concept panes, so you can also attach Web Pages to these as well. Click on the Create new web page button () in the Concept pane. The Save As dialog box is displayed. Navigate to the folder in which you want to save the Web Page (we will use an Assets sub-folder for all of our Web Pages), enter a name for the Web Page (it makes sense to use a reference to the content object to which the Web Page relates), and then click on the Save button. The Web Page Editor is opened on the Developer screen, as shown in the next screenshot: Enter the information that you want to appear in the Concept pane in the editor (as has already been done in the previous example). You can set the font face and size; set text to bold, italics, and underlined; change the text color, and change change the background color (again, as has been done in the earlier example). You can also change the paragraph alignment, and format numbered and bulleted lists. Once you have entered the required text, click on the Save button () to save your changes, and then close the Web Page Editor tabbed page. You are returned to the Outline Editor. Now, the Concept pane shows the contents of the Web Page, as shown in the next screenshot: Version Difference In UPK 3.5 (and OnDemand 9.1) you can only attach a single Web Page to the Concept pane. This is a change from OnDemand 8.7, where you could attach multiple Infoblocks and they would be displayed sequentially. (But note that if you convert content from OnDemand 8.7 where there are multiple Infoblocks in a single Concept pane, then all of the attached Infoblocks will be converted to a single Web Page in UPK 3.5.) The above steps explain how to attach a Web Page to the Concept pane for an exercise from the Outline. Although this is done from the Outline, the Web Page is attached to the Topic content object and not to the outline element. If you subsequently insert the same Topic in another Outline, the same Web Page will be used in the Concept pane of the new Outline. You can also attach a Web Page to the Concept pane for an exercise from within the Topic Editor. This is a useful option if you want to create concept Web Page but have not yet built an Outline to house the Topic. To do this, follow these steps: Open the Topic in the Topic Editor. Select menu option View|Concept Properties. The Concept Properties dialog box is displayed. This is very similar to the Concept pane seen in the Overview. It contains the same buttons. Create and save the Web Page as described above. When you have finished, and the Web Page is displayed in the Concept Properties dialog box, click on the OK button. Using images in Web Pages As stated above, a Web Page can contain an image. This can be instead of, or in addition to, any text (although if you only wanted to include a single image in the Web Page you could always use a Package, as explained later in this article). Images are a nice way of adding interest to a Web Page (and therefore to your training), or of providing additional information that is better explained graphically (after all, a picture is worth a thousand words). However, if you are using images in the Concept pane, then you should consider the overall size of the image and the likely width of the Concept pane, bearing in mind that the trainee may run the Player in a smaller window than the one you design. For our sample exercise, given that we are providing simulations for the SAP system, we will include a small SAP logo in the Web Page that appears in the Concept pane for our course Module. For the sake of variety, we will do this from the Library, and not from the Outline Editor. To add an image to a Web Page, carry out the steps described below. In the Library, locate the folder containing the Web Page to which you want to add the image. Double-click on the Web Page, to open it in the Web Page Editor. As before, this is opened in a separate tab in the main UPK Developer window, as can be seen in the next screenshot: Within the Web Page, position the cursor at the place in the text where you want the image to appear. Select menu option Insert|Image. The Insert Image dialog box is displayed, as shown in the next screenshot: In the Link to bar on the leftmost side of the dialog box, select the location of the image file that you want to insert into the Web Page. You can insert an image that you have already imported into your Library (for example, in a Package), an image that is located on your computer (or any attached drive) (option Image on My Computer), or an image from the Internet (option URL). For our sample exercise, we will insert an image from our computer. In the rightmost side of the dialog box, navigate to and select the image file that you want to insert into the Web Page. Click on OK. The image is inserted, as shown in the following screenshot: Save the Web Page, and close the Web Page Editor, if you have finished editing this Web Page. (We have not; we want to adjust the image, as explained below). Of course, this doesn't look too pretty. Thankfully, we can do something about this, because UPK provides some rudimentary features for adjusting images in Web Pages. To adjust the size or position of an image in a Web Page, carry out the following steps: With the Web Page open in the Web Page Editor, right-click on the image that you want to adjust, and then select Image Properties from the context menu. The Image Properties dialog box is displayed, as shown in the next screenshot: In the Alternative Text field, enter a short text description of the image. This will be used as the ToolTip in some web browsers. Under Size, select whether you want to use the original image size or not, and specify a new height and width if you choose not. Under Appearance, select a border width and indicate where the image should be aligned on the Web Page. Your choices are: Top, Middle, Bottom, Left, and Right. The first three of these (the vertical options) control the position of the image relative to the line of text in which it is located. The last two (the horizontal options) determine whether the image is left-aligned or right-aligned within the overall Web Page. Although these are two independent things (vertical and horizontal), you can only select one, so if you want the image to be right-aligned and halfway down the page, you can't do it. Click on OK to confirm your changes. Save and close the Web Page. For our sample exercise, we resized the image, set it to be right-aligned, and added a 1pt border around it (because it looked odd with a white background against the blue of the Web Page, without a border). A better option would be to use an image with a transparent background. In this example we have used an image with a solid background just for the purposes of illustration. These settings are as shown in the previous screenshot. This gives us a final Web Page as shown in the next screenshot. Note that this screenshot is taken from the Player, so that you can see how images are handled in the Player. Note that the text flows around the image. You will see that there is a little more space to the right of the image than there is above it. This is to leave room for a scrollbar. Creating independent Web Pages In the previous section, we looked at how to use a Web Page to add information to the Concept pane of a content object. In this section, we will look at how to use Web Pages to provide information in other areas. Observant readers will have noticed that a Web Page is in fact an independent content object itself. When you created a Web Page to attach to a Concept pane, you edited this Web Page in its own tabbed editor and saved it to its own folder (our Assets folder). Hopefully, you also noticed that in addition to the Create new web page button (), the Concept pane also has a Create link button () that can be used to attach an existing Web Page to the Concept pane. It should, therefore, come as no surprise to learn that Web Pages can be created independently of the Concept pane. In fact, the Concept pane is only one of several uses of Web Pages. To create a Web Page that is independent of a Concepts pane (or anything else), carry out these steps. In the Library, navigate to the folder in which you want to store the Web Page. In our example, we are saving all of the Web Pages for a module in a sub-folder called Assets within the course folder. Select menu option File|New|Web Page. A Web Page Editor tabbed page is opened up on the Developer screen. The content and use of this is exactly as described above in the explanation of how to create a Web Page from within an Outline. Enter the required information into the Web Page, and format it as required. We have already covered most of the available options, above. Once you have made your changes, click on the Save button (). You will be prompted to select the destination folder (which will default to the folder selected in Step 1, although you can change this) and a file name. Refer to the description of the Save As dialog box above for additional help if necessary. Close the Web Page Editor. You have now created a new Web Page. Now let's look at how to use it. Using Web Pages in Topics If you recall our long-running exercise on maintaining your SAP user profile, you will remember that we ask the user to enter their last name and their first name. These terms may be confusing in some countries—especially in countries where the "family name" actually comes before the "given name"—so we want to provide some extra explanation of some common name formats in different countries, and how these translate into first name and last name. We'll provide this information in a Web Page, and then link to this Web Page at the relevant place(s) within our Topic. First, we need to create the Web Page. How to do this is explained in the section Creating independent Web Pages, above. For our exercise, our created Web Page is as follows: There are two ways in which you can link to a Web Page from a Topic. These are explained separately, below. Linking via a hyperlink With a hyperlink, the Web Page is linked from a word or phrase within the Bubble Text of a Frame. (Note that it is only possible to do this for Custom Text because you can't select the Template Text to hyperlink from.) To create a hyperlink to a Web Page from within a Frame in a Topic, carry out the steps described below: Open up the Topic in the Topic Editor. Navigate to the Frame from which you want to provide the hyperlink. In our exercise, we will link from the Explanation Frame describing the Last name field (this is Frame 5B). In the Bubble Properties pane, select the text that you want to form the hyperlink (that is, the text that the user will click on to display the Web Page). Click on the Bubble text link button () in the Bubble Properties pane. The Bubble Text Link Properties dialog box is displayed. Click on the Create link button () to create a link to an existing Web Page (you could also click on the Create new web page button () to create a new Web Page if you have not yet created it). The Insert Hyperlink dialog box is displayed, as shown in the next screenshot: Make sure that the Document in Library option is selected in the Link to: bar. In the Look in: field, navigate to the folder containing the Web Page (the Assets folder, in our example). In the file list, click on the Web Page to which you want to create a hyperlink. Click on the Open button. Back in the Bubble Text Link Properties dialog box, click on OK. This hyperlink will now appear as follows, in the Player: Note that there is no ToolTip for this hyperlink. There was no opportunity to enter one in the steps above, so UPK doesn't know what to use. Version Difference In OnDemand 8.7 the Infoblock name was used as the ToolTip, but this is not the case from OnDemand 9.x onwards.
Read more
  • 0
  • 0
  • 3648

article-image-apache-geronimo-logging
Packt
16 Nov 2009
8 min read
Save for later

Apache Geronimo Logging

Packt
16 Nov 2009
8 min read
We will start by briefly looking at each of the logging frameworks mentioned above, and will then go into how the server logs events and errors and where it logs them to. After examining them, we will look into the different ways in which we can configure application logging. Apache log4j: Log4j is an open source logging framework that is developed by the Apache Software Foundation. It provides a set of loggers, appenders, and layouts, to control which messages should be logged at runtime, where they should be logged to, and in what format they should be logged. The loggers are organized in a tree hierarchy, starting with the root logger at the top of the hierarchy. All loggers except the root logger are named entities and can be retrieved by their names. The root logger can be accessed by using the Logger.getRootLogger() API, while all other loggers can be accessed by using the Logger.getLogger() API. The names of the loggers follow the rule that the name of the parent logger followed by a '.' is a prefix to the child logger's name. For example, if com.logger.test is the name of a logger, then its direct ancestor is com.logger, and the prior ancestor is com. Each of the loggers may be assigned levels. The set of possible levels in an ascending order are—TRACE, DEBUG, INFO, WARN, ERROR, and FATAL. If a logger is not assigned a level, then it inherits its level from its closest ancestor. A log statement makes a logging request to the log4j subsystem. This request is enabled only if its logging level is higher than or equal to its logger's level. If it is lower than the log, then the message is not output through the configured appenders. Log4j allows logs to be output to multiple destinations. This is done via different appenders. Currently there are appenders for the console, files, GUI components, JMS destinations, NT, and Unix system event loggers and remote sockets. Log4j is one of the most widely-used logging frameworks for Java applications, especially ones running on application servers. It also provides more features than the other logging framework that we are about to see, that is, the Java Logging API. Java Logging API: The Java Logging API, also called JUL, from the java.util.logging package name of the framework, is another logging framework that is distributed with J2SE from version 1.4 onwards. It also provides a hierarchy of loggers such as log4j, and the inheritance of properties by child loggers from parents just like log4j. It provides handlers for handling output, and formatters for configuring the way that the output is displayed. It provides a subset of the functionality that log4j provides, but the advantage is that it is bundled with the JRE, and so does not require the application to include third-party JARS as log4j does. SLF4J: The Simple Logging Facade for Java or SLF4J is an abstraction or facade over various logging systems. It allows a developer to plug in the desired logging framework at deployment time. It also supports the bridging of legacy API calls through the slf4j API, and to the underlying logging implementation. Versions of Apache Geronimo prior to 2.0 used Apache Commons logging as the facade or wrapper. However, commons logging uses runtime binding and a dynamic discovery mechanism, which came to be the source of quite a few bugs. Hence, Apache Geronimo migrated to slf4j, which allows the developer to plug in the logging framework during deployment, thereby eliminating the need for runtime binding. Configuring Apache Geronimo logging Apache Geronimo uses slf4j and log4j for logging. The log4j configuration files can be found in the <GERONIMO_HOME>/var/log directory. There are three configuration files that you will find in this directory, namely: client-log4j.properties deployer-log4j.properties server-log4j.properties Just as they are named, these files configure log4j logging for the client container (Java EE application client), deployer system, and the server. You will also find the corresponding log files—client.log, deployer.log, and server.log. The properties files, listed above, contain the configuration of the various appenders, loggers, and layouts for the server, deployer, and client. As mentioned above, log4j provides a hierarchy of loggers with a granularity ranging from the entire server to each class on the server. Let us examine one of the configuration files: the server-log4j.properties file: This file starts with the line log4j.rootLogger=INFO, CONSOLE, FILE. This means that the log4j root logger has a level of INFO and writes log statements to two appenders, namely, the CONSOLE appender and the FILE appender. These are the appenders that write to the console and to files respectively. The console appender and file appenders are configured to write to System.out and to <GERONIMO_HOME>/var/log/geronimo.log. Below this section, there is a finer-grained configuration of loggers at class or package levels. For example, log4j.logger.openjpa.Enhance=TRACE. It configures the logger for the class log4j.logger.openjpa.Enhance to the TRACE level. Note that all of the classes that do not have a log level defined will take on the log level of their parents. This applies recursively until we reach the root logger and inherit its log level (INFO in this case). Configuring application logging We will be illustrating how applications can log messages in Geronimo by using two logging frameworks, namely, log4j and JUL. We will also illustrate how you can use the slf4j wrapper to log messages with the above two underlying implementations. We will be using a sample application, namely, the HelloWorld web application to illustrate this. Using log4j We can use log4j for logging the application log to either a separate logfile or to the geronimo.log file. We will also illustrate how the logs can be written to a separate file in the <GERONIMO_HOME>/var/log directory, by using a GBean. Logging to the geronimo.log file and the command console Logging to the geronimo.log file and the command console is the simplest way to do application logging in Geronimo. For enabling this in your application, you only need to add logging statements to your application code. The HelloWorld sample application has a servlet called HelloWorldServlet, which has the following statements for enabling logging. The servlet is shown below. package com.packtpub.hello;import java.io.*;import javax.servlet.ServletException;import javax.servlet.http.*;import org.apache.log4j.Logger;public class HelloWorldServlet extends HttpServlet{ Logger logger = Logger.getLogger(HelloWorldServlet.class.getName()); protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.print("<html>"); logger.info("Printing out <html>"); out.print("<head><title>Hello World Application</title></head>"); logger.info("Printing out <head><title>Hello World Application</title></head>"); out.print("<body>"); logger.info("Printing out <body>"); out.print("<b>Hello World</b><br>"); logger.info("Printing out <b>Hello World</b><br>"); out.print("</body>"); logger.info("Printing out </body>"); out.print("</html>"); logger.info("Printing out </html>"); logger.warn("Sample Warning message"); logger.error("Sample error message"); }} Deploy the sample HelloWorld-1.0.war file, and then access http://localhost:8080/HelloWorld/. This servlet will log the following messages in the command console, as shown in the image below: The geronimo.log file will have the following entries: 2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <html>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <head><title>Hello World Application</title></head>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <body>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <b>Hello World</b><br>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out </body>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out </html>2009-02-02 20:01:38,906 WARN [HelloWorldServlet] Sample Warning message2009-02-02 20:01:38,906 ERROR [HelloWorldServlet] Sample error message Notice that only the messages with a logging level of greater than or equal to WARN are being logged to the command console, while all the INFO, ERROR, and WARN messages are logged to the geronimo.log file. This is because in server-log4j.properties the CONSOLE appender's threshold is set to the value of the system property, org.apache.geronimo.log.ConsoleLogLevel, as shown below: log4j.appender.CONSOLE.Threshold=${org.apache.geronimo.log.ConsoleLogLevel} The value of this property is, by default, WARN. All of the INFO messages are logged to the logfile because the FILE appender has a lower threshold, of TRACE, as shown below: log4j.appender.FILE.Threshold=TRACE Using this method, you can log messages of different severity to the console and logfile to which the server messages are logged. This is done for operator convenience, that is, only high severity log messages, such as warnings and errors, are logged to the console, and they need the operator's attention. The other messages are logged only to a file.
Read more
  • 0
  • 0
  • 2894

article-image-glossary-upk-35
Packt
16 Nov 2009
9 min read
Save for later

Glossary in UPK 3.5

Packt
16 Nov 2009
9 min read
A Glossary in UPK effectively associates Web Pages to key terms. UPK will then turn any instance of that key term within a Topic (or other content object) to which that Glossary is assigned into a hyperlink to the associated Web Page. You could do this manually by creating Web Pages (as explained above) and then creating hyperlinks to these manually, but the advantage of using the Glossary functionality is that UPK will 'automatically' find each occurrence of the glossary term and create the hyperlink. I say 'automatically' in quotation marks, as UPK does not do this completely automatically. You have to tell it to go and create the hyperlinks, but once you tell it to do so, it will dutifully go off and find 'every' occurrence of all of the terms in the glossary and create the hyperlinks to the definition. And I say 'every' in quotation marks, because UPK won't necessarily link every instance of the term. UPK allows you to choose (at the Library level) whether it turns literally every occurrence of the term found in a location into a hyperlink, or only the first occurrence. A location here is effectively a single Bubble or Web Page. One could well ask why UPK doesn't provide the option to create a Glossary link only for the first occurrence of the term in a Topic. The simple answer is that it does not necessarily know which occurrence is the first, because the Topic could include Alternative Paths. So UPK takes an over-cautious approach, and considers each block of information separately. This option is specified in the Options panel (menu option Tools|Options), under the Content Defaults|Glossary section, as shown in the following screenshot: Creating a glossary To create a Glossary in UPK, follow these steps: From the main Library screen, click on the folder within which you want to create the Glossary. You can create the Glossary in any folder, but it makes sense to have a single folder that contains the Glossary file itself, and all of the Web Pages used for the terms. Select menu option File|New|Glossary. The Glossary Editor is opened in a new tab within the UPK Developer window, as shown in the following screenshot: To create glossary entries from within the Glossary, follow these steps: Enter the glossary term in the first free Glossary Term field. Click in the Definition Link field on the same line. An ellipsis (...) is displayed on the rightmost side of the field. Click on the ellipsis. The Edit Definition Link dialog box is displayed. Click on the Create New Web Page field. The Save As dialog box is displayed. Navigate to the directory in which you want to save the Glossary definition. Again, it makes sense to save these in the same folder as the Glossary itself. The Name field will default to the glossary term specified in the Glossary Term field. It is recommended that you keep this default, and use the definition term as the Web Page name. You could use any other name, if you wanted to (UPK does not use this name to locate the definition; it uses its own, internal identifier) but using the same name will allow you to easily locate the term Web Page if you need to. Click on the Save button. A new tab is opened for the Web Page. Enter the Glossary description into this page. The Web Page will use the default font and colors. You can override these defaults, if required. Close the Web Page by clicking on the x on the rightmost side of the open tab list. You are passed back to the Glossary Editor. Enter a suitable ToolTip text for the glossary entry in the Tooltip field. This text will be displayed when the user hovers the mouse over the hyperlinked glossary term. If only whole instances of the glossary term should be turned into hyperlinks (for example, if the term is Order then "Orders" and "Ordering" will not be hyperlinked), then select the Match Whole Word option. Otherwise, make sure that this option is not selected. If only text that matches the case of the term should be turned into hyperlinks (for example, if the term is Order then "order" will not be hyperlinked), then select the Match Case field. Otherwise, make sure that this option is not selected. Repeat Steps 1 to 14 for all additional terms that you want to add to the Glossary. To create a glossary entry that uses an existing Web Page for the glossary term, follow these steps: Enter the glossary term in the first free Glossary Term field. Click in the Definition Link field on the same line. An ellipsis (...) is displayed on the rightmost side of the field. Click on the ellipsis. The Edit Definition Link dialog box is displayed. Click on the Create Link button. The Insert Hyperlink dialog box is displayed. Navigate to, and select, the Web Page that contains the glossary description. Click on OK. The Edit Definition Link dialog box is redisplayed. You can edit the Web Page directly from this dialog box, by clicking on the Edit Web Page icon. Click on OK. You are returned to the Glossary tabbed page. Once you have defined all required Glossary entries, save and close the Glossary, by following the steps shown below: Click the Save button to save your changes to the Glossary. The Save As dialog box is displayed. Navigate to the directory in which you want to save the Glossary. Enter a suitable name for the Glossary in the Name field. Close the Glossary Editor by clicking on the x on the rightmost side of the open tab list. An example of a partially-populated Glossary is shown in the next screenshot: You can see from the Definition Link column above that all of the Glossary definition files are stored in the same, single folder, called Glossary. This is the same folder that the Glossary object itself is stored in. Personally, I find it useful to keep all of the content objects for the Glossary in the same single folder. This is not strictly necessary, but it does keep things organized. You will also note that the Tooltip is the same in every case. I tend to always use the tooltip Glossary so that the user knows that the hyperlink links to the Glossary, and not to another form of Web Page. Assigning a Glossary to content objects Creating a Glossary is only half the story. You need to manually assign the Glossary to each object that you want to use that Glossary (that is, for which you want the terms specified in the Glossary to be hyperlinked to the Glossary definitions). Version Difference In OnDemand Version 8.7 and earlier, a single Glossary was created for a Title, and was automatically applied to all content objects within that Title. In UPK 3.5, it is possible to have multiple Glossaries within a single Library, so it is necessary to specify which content objects should use which Glossary. This assignment is done via the content object's Properties, as shown in the screenshot below. The Glossary property is available for Modules, Sections, Topics, and Web Pages. This means that you could potentially assign one Glossary to a Module, another Glossary to a Section within this Module, and a third Glossary to a Topic within that Section. Not that you're very likely to want to do this. But you could. The further implication of the requirement to assign a Glossary to each content object individually is that you can define multiple Glossaries, and assign different Glossaries to different objects. I'd question the wisdom of assigning one glossary to a Module, and then an entirely different Glossary to a Topic within that Module, but I can certainly see the benefit of having multiple Glossaries available in a Library that contained (for example) simulations for multiple applications; you could create application-specific Glossaries (as you no doubt do at the moment) and then assign each Glossary to only the content objects for the relevant application. The downside (for those of us who favor modularization and reuse) is that it is only possible to assign a single Glossary to any given object. So it is not possible to, say, create a company-wide Glossary and separate application-specific Glossaries, and then assign the company-wide Glossary and the relevant application-specific glossary to a single Topic. But more resourceful readers will have already worked out how to get around this limitation. Need a clue? Glossary entries are just Web Pages, and any given Web Page can be reused in multiple places. Need more help? A Web Page can be included in more than one Glossary. So you should first define Web Pages for all of your Glossary terms. You can then create multiple Glossaries (for example, one for each application), and include whichever terms' Web Pages you need to in each of the glossaries. If a term applies to two applications, then simply include the Web Page for that term in the Glossaries for both applications. Simple! Of course there is some slight duplication of effort as you need to create the entry in the actual Glossary content object twice (once in each glossary), but you are reusing the individual definitions, so it could be worse. Unfortunately, unlike Templates, it is not possible to specify a default Glossary in your user defaults. This means that this assignment must be done separately for each content object. However, there are a couple of shortcuts that UPK provides which avoid the need to assign the Glossary to content objects one by one. First, if you select multiple content objects (Modules, Sections, Topics, or Web Pages) and display the Properties pane, then you can assign the Glossary to all of the selected objects in one fell swoop. Second, if you assign a Glossary to a Module, then any new Sections or Titles that you create within that Module – that is, from within the Outline Editor – will inherit this Glossary assignment. However, it is important to note that this will only apply to new content objects. If you assign a Glossary to an outline element and then insert pre-existing content objects into this outline element, then these pre-existing content objects will not inherit the Glossary assignment. Regenerating the Glossary links Glossary links are not created (or updated) automatically. You need to tell UPK to go and search through your content objects and turn any instances of the Glossary terms into links to the Glossary definitions. This is good in that you can at least have control over when it does this, but bad in that it is easy to forget to do so.
Read more
  • 0
  • 0
  • 1713
article-image-session-and-user-joomla-15-part-1
Packt
16 Nov 2009
9 min read
Save for later

The Session and the User with Joomla! 1.5: Part 1

Packt
16 Nov 2009
9 min read
Introduction When a user starts browsing a Joomla! web site, a PHP session is created. Hidden away in the session is user information, this information will either represent a known registered user or a guest. We can interact with the session using the session handler, a JSession object. When we work with the session in Joomla!, we must not use the global PHP $_SESSION variable or any of the PHP session functions. Getting the session handler To interact with the session we use the session handler; this is a JSession object that is globally available via the static JFactory interface. It is imperative that we only use the global JSession object to interact with the PHP session. Directly using $_SESSION or any of the PHP session functions could have unintended consequences. How to do it... To retrieve the JSession object we use JFactory. As JFactory returns an object, we must use =& when assigning the object to a variable. If we do not and our server is running a PHP version prior to PHP 5, we will inadvertently create a copy of the global JSession object. $session =& JFactory::getSession(); How it works... If we look at the JSession class, we will notice that there is a getInstance() method. It is tempting to think of this as synonymous with the JFactory::getSession() method. There is, however, an important difference, the JSession::getInstance() method requires configuration parameters. The JFactory::getSession() method accepts configuration parameters, but they are not required. The first time the JFactory::getSession() method is executed, it is done by the JApplication object (often referred to as mainframe). This creates the session handler. It is the application and JFactory that deal with the configuration of the session. Subsequent usage of the JFactory::getSession() method will not require the creation of the object, and thus simply returns the existing object. The following sequence diagram shows how this process works the first time it is executed by the JApplication object: When the JFactory::getSession() method is subsequently executed, because session will already exist, the _createSession() method is not executed. The diagram is a simplification of the process; additional complexity has not been included because it is outside the scope of this recipe. See also For information about setting and retrieving values in the session, refer to the next two recipes, Adding data to the session and Getting session data. Adding data to the session Data that is set in the session is maintained between client requests. For example, we could display announcements at the top of all our pages and include an option to hide the announcements. Once a user opts to hide the announcements, by setting a value in the session, we would be able to 'remember' this throughout the user's visit. To put this into context, we would set a session value hideAnnouncements to true when a user opts to hide announcements. In subsequent requests, we will be able to retrieve the value of hideAnnouncements from the session and its state will remain the same. In Joomla!, session data is maintained using a JSession object, which we can retrieve using JFactory. This recipe explains how to set data in the session using this object instead of using the global PHP $_SESSION variable. Getting ready We must get the session handler, a JSession object. For more information, refer to the first recipe in this article, Getting the session handler. $session =& JFactory::getSession(); How to do it... The JSession::set() method is used to set a value in the session. The first parameter is the name of the value we want to set; the second is the value itself. $session->set('hideAnnouncements', $value); The JSession::set() method returns the previous value. If no value was previously set, the return value will be null. // set the new value and retrieve the old$oldValue = $session->set('hideAnnouncements', $value);echo 'Hide Announcement was ' . ($oldValue ? 'true' : 'false');echo 'Hide Announcement is now ' . ($value ? 'true' : 'false'); Lastly, we can remove data from the session by setting the value to null. // remove something$session->set('hideAnnouncements', null); How it works... The session contains a namespace-style data structure. Namespaces are required by JSession and by default all values are set in the default namespace. To set a value in a different namespace, we use the optional JSession::set() third parameter. $session->set('something', $value, 'mynamespace'); Sessions aren't just restricted to storing basic values such as strings and integers. The JUser object is a case in point—every session includes an instance of JUser that represents the user the session belongs to. If we add objects to the session, we must be careful. All session data is serialized. To successfully unserialize an object, the class must already be known when the session is restored. For example, the JObject class is safe to serialize because it is loaded prior to restoring the session. $value = new JObject();$session->set('aJObject', $value); If we attempt to do this with a class that is not loaded when the session is restored, we will end up with an object of type __PHP_Incomplete_Class. To overcome this, we can serialize the object ourselves. // serialize the object in the session$session->set('anObject', serialize($anObject)); To retrieve this, we must unserialize the object after we have loaded the class. If we do not do this, we will end up with a string that looks something like this O:7:"MyClass":1:{s:1:"x";s:10:"some value";}. // load the classinclude_once(JPATH_COMPONENT . DS . 'myclass.php');// unserialize the object from the session$value = unserialize($session->get('anObject')); There's more... There is an alternative way of setting data in the session. User state data is also part of the session, but this data allows us to save session data using more complex hierarchical namespaces, for example com_myextension.foo.bar.baz. To access this session data, we use the application object instead of the session handler. // get the application$app =& JFactory::getApplication();// set some data$app->setUserState('com_myextsion.foo.bar.baz, $value); An advantage of using user state data is that we can combine this with request data. For more information refer to the next recipe, Getting session data. The JApplication::setUserState() method is documented as returning the old value. However, a bug prevents this from working; instead the new value is returned. See also For information about retrieving values from the session, refer to the next recipe, Getting session data. Getting session data Data that is set in the session is maintained between client requests. For example if during one request we set the session value of hideAnnouncements to true, as described in the previous recipe, in subsequent requests we will be able to retrieve the value of hideAnnouncements and its state will remain the same. In Joomla!, session data is maintained using the global JSession object. This recipe explains how to get data from the session using this object instead of from the normal global PHP $_SESSION variable. Getting ready We must get the session handler, a JSession object. For more information, refer to the first recipe in this article, Getting the session handler. $session =& JFactory::getSession(); How to do it... We use the JSession::get() method to retrieve data from the session. $value = $session->get('hideAnnouncements'); If the value we attempt to retrieve is not set in the session, the value null is returned. It is possible to specify a default value, which will be returned in instances where the value is not currently set in the session. $defaultValue = false;$value = $session->get('hideAnnouncements', $defaultValue); How it works... The session contains a namespace-style data structure. Namespaces are required by JSession and by default all values are retrieved from the default namespace. To get a value from a different namespace, we use the optional third JSession::get() parameter. $value = $session->get('hideAnnouncements', $defaultValue, 'mynamespace'); It is possible to store objects in the session. However, these require special attention when we extract them from the session. For more information about storing objects in the session, refer to the previous recipe, Adding data to the session. There's more... There is an alternative way of getting data from the session. User state data is also part of the session. The user state data allows us to store session data using more complex hierarchical namespaces, for example com_myextension.foo.bar.baz. To access user state data, we use the application object instead of the session handler. // get the application (this is the same as $mainframe)$app =& JFactory::getApplication();// get some user state data$value = $app->getUserState('com_myextsion.foo.bar.baz'); User state data is usually combined with request data. For example, if we know the request may include a value that we want to use to update the user state data, we use the JApplication::getUserStateFromRequest() method. // get some user state data and update from request$value = $app->getUserStateFromRequest( 'com_myextsion.foo.bar.baz', 'inputName', $defaultValue, 'INTEGER'); The four parameters we provide this method with are the path to the value in the state data, the name of the request input from which we want to update the value, the default value (which is used if there is no value in the request), and the type of value. This method is used extensively for dealing with display state data, such as pagination. // get global default pagination limit$defaultListLimit = $app->getCfg('list_limit');// get limit based on user state data / request data$limit = $app->getUserStateFromRequest( 'global.list.limit', 'limit', $defaultListLimit, 'INTEGER'); See also For information about setting values in the session, refer to the previous recipe, Adding data to the session.
Read more
  • 0
  • 0
  • 3337

article-image-session-and-user-joomla-15-part-2
Packt
16 Nov 2009
8 min read
Save for later

The Session and the User with Joomla! 1.5: Part 2

Packt
16 Nov 2009
8 min read
Getting the user's group ID and type The following organized list describes the user groups in Joomla! 1.5 in a way in which we are all probably familiar. Each of these groups has an ID and a name; these are the group's ID and type respectively. This recipe explains how to find the group ID and group type of the current user. Note that the hard coded group IDs should not generally be used for access control; for this it is best to take advantage of the JAuthorization class. For more information, refer to the Joomla! API site: http://api.joomla.org/. Dynamic permissions There is no administrative functionality that allows the modification of groups. It is, however, possible to programmatically modify groups and group permissions. Most of the time a Joomla! 1.5 extension will opt to implement its own permission management rather than use the incomplete Joomla! solution. Joomla! 1.6, in development at the time of writing, promises a complete GACL (Group Access Control List) implementation. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe covered earlier in this article. $user =& JFactory::getUser(); How to do it... The group ID is held in the gid field in the #__users table. We use the JUser::get() method to extract the user's group ID. // determine group ID$groupID = $user->get('gid'); The group type is held in the usertype field in the #__users table. Again, we use the JUser::get() method to extract the user's group ID. $usertype = $user->get('usertype'); How it works... Apart from the obvious format of the data, there is a subtle difference between the gid and usertype fields. The gid field is always populated, where as the usertype field is populated only if the user is not blocked. When dealing with the current user, the user will never be blocked, but when dealing with other users there is a possibility that the usertype field will not be populated. Therefore, we need to be careful that we select the most appropriate field, depending on the context of what we are doing. The user groups that we have mentioned are maintained in the #__core_acl_aro_groups table, or as it can also be called, the Access Control List Access Request Object Groups table. This table holds a tree structure, as indicated in the introduction of the recipe. What is noticeable is that Public Frontend and Public Backend are both technically user groups. As this is a tree structure, we can use the left and right values to perform all sorts of neat tricks. The #__core_acl_aro_groups table employs the nested set model: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html. See also The next recipe explains how to use the Public, Registered, and Special access levels. Restricting a user's access using Public, Registered, and Special In Joomla! we often define access based on three simple access levels. These levels relate to the user groups described in the previous recipe, Getting the user's group ID and type. This recipe explains how to use these access levels to restrict access to resources. Public (0) Registered (1) Special (2) Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe covered earlier in this article. $user =& JFactory::getUser(); How to do it... The JUser::get() method can be used to retrieve the user's aid. This is the access level number, as shown in braces in the recipe introduction. // get access level$aid = $user->get('aid'); For each resource we want to restrict access to, we must define the required access level. This is normally done in a database TINYINT(3) field named access. This can then be used to restrict the access using a database query (used when listing)... // get the DBO$db =& JFactory::getDBO();// prepare access field name$access = $db->nameQuote('access');// restrict query by access level$where = "WHERE $access <= " . intval($aid); ...or when viewing a single record. Note that ALERTNOTAUTH is a core translation string, which in en-GB is equivalent to You are not authorised to view this resource. // make sure the user has the necessary access rightsif ($table->get('access') > $aid) { JError::raiseError(403, JText::_('ALERTNOTAUTH')); jexit();} How it works... A good example of this in action is the content component. If we take a look at the article manager, we can see that there is an Access Level associated with each article that determines which users can access the article. These access levels are relatively primitive and we don't have any bona fide control over them. So how do they translate into concrete user groups? The following table describes the seven user groups and the guest group, and shows how these relate to the access levels: User Group User Group ID Access Level Access Level ID None (Guest) 0 Public 0 Registered 18 Registered 1 Author 19 Special 2 Editor 20 Special 2 Publisher 21 Special 2 Manager 23 Special 2 Administrator 24 Special 2 Super Administrator 25 Special 2 See also The previous recipe explains how to work with the user group ID and group type. This can also be useful for dealing with access control. Getting the user's parameters User parameters provide a mechanism for including additional user data without the need to modify the database. The core user parameters are defined in the XML files located in the administratorcomponentscom_usersmodels folder. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe. $user =& JFactory::getUser(); How to do it... To retrieve a parameter we must first know the name of the parameter we want to retrieve. One example of a common parameter is timezone. This is an integer that defines the hours offset from UTC (Coordinated Universal Time), also known as GMT (Greenwich Mean Time) and Z (Zulu). To retrieve a parameter, we can use the JUser::getParam() method. $timezone = $user->getParam('timezone'); It is also possible to provide a default value. This is useful especially if the user we are dealing with is a guest because guest users do not have any parameters defined by default. $timezone = $user->getParam('timezone', '0'); How it works... A user's parameters are represented as a JParameter object. We do not have to interact directly with this object because JUser will do this for us. A minor problem with this method is the default value. Technically the XML files define the default values, but by default these XML files are not loaded by the JUser class. Therefore, the XML defined default values are not employed. One way to overcome this is to interact directly with the JParameter object. The next section explains how to do this. There's more... The JUser::getParamters() method allows us to directly access the user's JParameter object. Note that we must use =& when assigning the return value to a variable. If we do not and we are using a PHP version prior to PHP 5, we will inadvertently create a copy of the returned JParameter object. // get parameters with XML definition file loaded$params =& $user->getParameters(true); When we retrieve the JParameter object there are two optional parameters. The first parameter $loadsetupfile determines whether or not the XML file that defines the user's parameters should be loaded. Loading this file gives meaning to the data and also provides default values for defined data. For information about the second optional parameter, refer to the recipe, Extending and editing user parameters. To retrieve a value, we use the JParameter::get() method. We pass two parameters to this method—the name of the parameter we want the value of, and the default value to return if the parameter does not exist. // get timezone (hours UTC offset)$timezone = $params->get('timezone', '0'); See also The next recipe, Setting the user's parameters, explains how to set a value in the user's parameters. Setting the user's parameters User parameters provide a mechanism for including additional user data without the need to modify the database. The parameters are defined in the XML files located in the administratorcomponentscom_usersmodels folder. User parameters are not restricted to the parameters defined in the XML files. It is perfectly acceptable to add additional parameters. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe. $user =& JFactory::getUser(); How to do it... To set the value of a parameter, we use the JUser::setParam() method. This method requires two parameters—the name of the parameter we want to set and the value to which we want to set the parameter. For example, we could change the user's editor preference to use no editor. // set value of someparameter to some value$user->setParam('editor', 'none'); There's more... If we have retrieved the JParamter object directly from the user, we can alternatively use that object to set the user's parameters. For information about retrieving the user's parameters, refer to the previous recipe, Getting the user's parameters. To set data in the JParameter object, we use the JParameter::set() method. // set value of someparameter to some value$params->set('editor', 'none'); See also The previous recipe explains how to get a value from the user's parameters.
Read more
  • 0
  • 0
  • 1604
Modal Close icon
Modal Close icon