Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 60 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
60
Dung lượng
1,51 MB
Nội dung
Figure 8-1. Displaying a summary of posts from the current month Displaying the Monthly Summary Now that we are displaying a summary of posts from the current month, we need a way to dis- play posts from the other months. In Listing 8-6 we created the GetMonthlySummary() method, which gives us an array of months and the number of posts belonging to that month. We will now create a Smarty plug-in to retrieve this data and assign it to the template. We could have generated this data in the indexAction() method and then assigned it directly; however, the problem with this occurs when we want to show the same data on another page. We would have to retrieve and assign the data on every page on which we wanted to display it. This means if we decided to change the layout of the pages, we would need to make changes to the PHP code, not just the templates. Using a Smarty plug-in allows us to get the data when- ever we like. To bring the data from GetMonthlySummary(), we are going to use Smarty code as follows: {get_monthly_blog_summary user_id=$identity->user_id assign=summary} Effectively what this code means is that we are going to create a custom Smarty function called get_monthly_blog_summary. This function will take two arguments: the ID of the user the summary is being fetched for and the name of the template variable to assign the summary to (meaning we will be able to access the $summary variable in the template after this function has been called). CHAPTER 8 ■ EXTENDING THE BLOG MANAGER 279 9063Ch08CMP2 11/11/07 12:35 PM Page 279 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ■Note The reason we pass in the user ID instead of automatically retrieving it within the plug-in is that by doing it this way we can use this plug-in when displaying users’ public home pages. Since the ID in that case is dependent on the page being looked at and not which user is viewing the page, we specify the ID using the function argument. Listing 8-12 shows the code for this plug-in. We save this code to a file called function.get_ monthly_blog_summary.php, which we store in the ./include/Templater/plugins directory. Listing 8-12. A Custom Smarty Plug-in to Retrieve the Blog Summary (function.get_monthly_blog_summary.php) <?php function smarty_function_get_monthly_blog_summary($params, $smarty) { $options = array(); if (isset($params['user_id'])) $options['user_id'] = (int) $params['user_id']; $db = Zend_Registry::get('db'); $summary = DatabaseObject_BlogPost::GetMonthlySummary($db, $options); if (isset($params['assign']) && strlen($params['assign']) > 0) $smarty->assign($params['assign'], $summary); } ?> The first thing this plug-in does is to check for the user_id parameter. If it is set, it adds it to the $options array. We must fetch the $db object from the application registry because it is required to make the call to GetMonthlySummary(). Finally, we determine the variable name to use for assigning the data back to the template. As you saw earlier, we’ll use a variable called $summary. After calling get_monthly_blog_summary, we can simply loop over the $summary array in the template as we would with any other array. ■Note You could argue that this technique is using application logic within a template, which as discussed in Chapter 2 is a bad thing. To some degree this is application logic, although technically speaking we are doing it only for the purpose of the view—we are not causing any application side effects. Additionally, sometimes you need to make minor sacrifices in the way code is structured in order to provide flexibility. Calling the Smarty Plug-in in the Side Columns We are now going to use the plug-in we just created to output the monthly summary in the left column of the site template. By using the plug-in, we have made it very easy to include this CHAPTER 8 ■ EXTENDING THE BLOG MANAGER280 9063Ch08CMP2 11/11/07 12:35 PM Page 280 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com data on other pages also. The one problem we now run into is that to add content to either of the side columns, we must alter the footer.tpl template. Since we don’t want to include this data site-wide, we must make some enhancements to our template structure to allow us to include these additions to the left column only when required. To do this, we’ll pass two optional parameters when we include the footer.tpl template. The first parameter will specify a template to use to generate content for the left column, while the second parameter will specify a template for generating content in the right column. First, let’s create the template that calls the get_monthly_blog_summary plug-in and out- puts its data. This is the template we will pass to footer.tpl to output. Listing 8-13 shows the left-column.tpl template, which we store in the ./templates/blogmanager/lib directory. Note that we use the class name .box, because this is the class we defined earlier for styling content areas in the side columns. Listing 8-13. Outputting the Data from the get_monthly_blog_summary Plug-in (left-column.tpl) {get_monthly_blog_summary user_id=$identity->user_id assign=summary} {if $summary|@count > 0} <div id="preview-months" class="box"> <h3>Your Blog Archive</h3> <ul> {foreach from=$summary key=month item=numPosts} <li> <a href="{geturl controller='blogmanager'}?month={$month}"> {$month|date_format:'%B %Y'} </a> ({$numPosts} post{if $numPosts != 1}s{/if}) </li> {/foreach} </ul> </div> {/if} Second, we must modify the index.tpl template (from ./templates/blogmanager) to tell footer.tpl to use this template. Listing 8-14 shows the change we make to the bottom {include} call. Listing 8-14. Specifying the Template to Use in the Left Column of the Site (index.tpl) {include file='header.tpl' section='blogmanager'} {if $totalPosts == 1} <p> There is currently 1 post in your blog. </p> {else} <p> CHAPTER 8 ■ EXTENDING THE BLOG MANAGER 281 9063Ch08CMP2 11/11/07 12:35 PM Page 281 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com There are currently {$totalPosts} posts in your blog. </p> {/if} <form method="get" action="{geturl controller='blogmanager' action='edit'}"> <div class="submit"> <input type="submit" value="Create new blog post" /> </div> </form> <div id="month-preview"> {include file='blogmanager/lib/month-preview.tpl' month=$month posts=$recentPosts} </div> {include file='footer.tpl' leftcolumn='blogmanager/lib/left-column.tpl'} You should also make the same change to the edit.tpl and preview.tpl templates from the blog manager controller. The final change is to make footer.tpl recognize the $leftcolumn and $rightcolumn parameters and include the templates accordingly. Listing 8-15 shows the new version of footer.tpl, which now includes the left and right templates if required. Note that for the left column we can use the else block to display some default content. I haven’t worried about this for the right column, since there is always authentication data shown (whether logged in or not). Listing 8-15. Including the Template to Generate Left and Right Column Content (footer.tpl) </div> </div> <div id="left-container" class="column"> {if isset($leftcolumn) && $leftcolumn|strlen > 0} {include file=$leftcolumn} {else} <div class="box"> Left column placeholder </div> {/if} </div> <div id="right-container" class="column"> <! // status messages box // authentication box > CHAPTER 8 ■ EXTENDING THE BLOG MANAGER282 9063Ch08CMP2 11/11/07 12:35 PM Page 282 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com {if isset($rightcolumn) && $rightcolumn|strlen > 0} {include file=$rightcolumn} {/if} </div> <div id="footer"> <! // other code > </div> </body> </html> Including Additional Data in the Side Column Sometimes In certain instances you will want different combinations of data included in the side columns. For example, you might want to show the blog summary and the authentication data in the same column—but only on a particular page. To achieve this, you would make a new template that outputs this data accordingly and then pass this new template in as the value to $leftcolumn or $rightcolumn. The recommended way to do this is to not include multiple content boxes in a single tem- plate but to keep them all in separate templates and then to create an additional wrapper template to bring them together. For example, you might store the monthly blog summary in blog-summary-box.tpl, and you might keep authentication data in authentication-box.tpl. You would then create another template called some-template.tpl that might look as follows: {include file='blog-summary-box.tpl'} {include file='authentication-box.tpl'} You would then use some-template.tpl as the value for $leftcolumn. To keep the code rel- atively simple, I have chosen not to break up the templates to this degree. Ajaxing the Blog Monthly Summary In the previous section, we wrote code to output blog posts in the blog manager for the selected month, with a list of all months that have posts in the side column. The way it works now is that if a month is clicked by the user, the page reloads, displaying the posts from that month. We’ll now enhance this system. Instead of reloading the page for the newly selected month, we’ll make the blog manager index page fetch the posts in the background using Ajax and then display them on the page. This code will still be accessible for non-JavaScript users, because the solution we have already implemented does not rely on JavaScript. This new functionality will be built on top of the existing functionality, meaning those who use it will have an improved experience but those who don’t will not suffer. The only other consideration we must make is that we’re also listing the monthly sum- mary on the edit and preview pages. If one of the months is clicked from these pages, we will not use Ajax to fetch the new page content but instead navigate normally to the page as we would without this Ajax functionality. CHAPTER 8 ■ EXTENDING THE BLOG MANAGER 283 9063Ch08CMP2 11/11/07 12:35 PM Page 283 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Creating the Ajax Request Output Before we add any JavaScript code, we will create the necessary changes to generate the Ajax request data. We can reuse the indexAction() method from BlogmanagerController.php with- out any changes to code. All we need to do is to change its corresponding template so the page header and footer aren’t included when the controller action is requested via Ajax. To help with this, we’ll make a minor addition to the CustomControllerAction class. In Chapter 6 we discussed how the isXmlHttpRequest() method worked with the Zend_ Controller_Request_Http class. This method is a simple way to determine whether the current request was initiated using XMLHttpRequest. We’ll assign the value of this function call to all templates. Listing 8-16 shows the changes we make to the CustomControllerAction.php file in the ./include directory. Listing 8-16. Adding Ajax Request Detection to Templates (CustomControllerAction.php) <?php class CustomControllerAction extends Zend_Controller_Action { // other code public function postDispatch() { // other code $this->view->isXmlHttpRequest = $this->getRequest()->isXmlHttpRequest(); } // other code } ?> Next we modify the template for the BlogmanagerController’s indexAction() method. All we do in this template now is check the value of the $isXmlHttpRequest variable that is auto- matically assigned. If this value is false, then the template will generate output as previously, whereas if it’s true, then we won’t include the page header and footer. Listing 8-17 shows the changes we make to the index.tpl file in ./templates/blogmanager. Listing 8-17. Altering the Output for Ajax Requests (index.tpl) {if $isXmlHttpRequest} {include file='blogmanager/lib/month-preview.tpl' month=$month posts=$recentPosts} {else} {include file='header.tpl' section='blogmanager'} {if $totalPosts == 1} <p> CHAPTER 8 ■ EXTENDING THE BLOG MANAGER284 9063Ch08CMP2 11/11/07 12:35 PM Page 284 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com There is currently 1 post in your blog. </p> {else} <p> There are currently {$totalPosts} posts in your blog. </p> {/if} <form method="get" action="{geturl controller='blogmanager' action='edit'}"> <div class="submit"> <input type="submit" value="Create new blog post" /> </div> </form> <div id="month-preview"> {include file='blogmanager/lib/month-preview.tpl' month=$month posts=$recentPosts} </div> {include file='footer.tpl' leftcolumn='blogmanager/lib/left-column.tpl'} {/if} The BlogMonthlySummary JavaScript Class To initiate the background HTTP request to fetch the monthly summary data (using XMLHttpRequest), we need to attach some JavaScript code to each of the links in the month listing. To do this, we’ll create a JavaScript class called BlogMonthlySummary. This class will be loaded and instantiated automatically when we include the left- column.tpl template we created earlier this chapter, as you will see shortly. Using some of the Prototype techniques you learned in Chapter 5, we can create a class to encapsulate all the functionality we need. The general algorithm for this class is as follows: 1. Check for the existence of the link container (where the month links are listed) and the content container (where the blog posts are listed). If either one doesn’t exist, stop exe- cution (meaning clicking the month links will just load the respective page as normal). 2. Observe the click event for each of the links found in the link container. 3. When a link is clicked, initiate an Ajax request using the Ajax.Updater class. This class is built on top of the Ajax.Request class and is used specifically to update an element with the results from XMLHttpRequest. 4. Cancel the click event so the browser doesn’t follow the link href. We use the Event.stop() method in the event handler to achieve this. Listing 8-18 shows the contents of the BlogMonthlySummary.class.js file, which we store in the ./htdocs/js directory. CHAPTER 8 ■ EXTENDING THE BLOG MANAGER 285 9063Ch08CMP2 11/11/07 12:35 PM Page 285 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 8-18. The BlogMonthlySummary JavaScript Class (BlogMonthlySummary.class.js) BlogMonthlySummary = Class.create(); BlogMonthlySummary.prototype = { container : null, linkContainer : null, initialize : function(container, linkContainer) { this.container = $(container); this.linkContainer = $(linkContainer); if (!this.container || !this.linkContainer) return; this.linkContainer.getElementsBySelector('a').each(function(link) { link.observe('click', this.onLinkClick.bindAsEventListener(this)); }.bind(this)); }, onLinkClick : function(e) { var link = Event.element(e); var options = { }; new Ajax.Updater(this.container, link.href, options); Event.stop(e); } }; After creating the class using Prototype’s Class.create() function, we define the con- structor for the class (the initialize() method), which accepts the content container as the first argument and the link container as the second argument. If both of these containers are found to exist, the code continues to add the click event handler to each of the links. This results in the onLinkClick() method being called if any of the links are clicked. ■Note Chapter 6 discusses the Prototype event handling mechanism. You’ll also see how the bind() and bindAsEventListener() functions work in that chapter. CHAPTER 8 ■ EXTENDING THE BLOG MANAGER286 9063Ch08CMP2 11/11/07 12:35 PM Page 286 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com We begin the onLinkClick() method by determining exactly which link was clicked. This is achieved by calling the Event.element() function with the event object passed to onLinkClick(). We will use the href attribute of the link as the URL to pass to Ajax.Updater. Currently there are no extra options we need to pass to this Ajax request; however, we still define the options hash since we will be using it later in this chapter. The onLinkClick() method concludes by calling Event.stop(). This is to ensure the browser doesn’t follow the link, thereby defeating the point of using Ajax. Installing the BlogMonthlySummary Class Now we must update the left-column.tpl template to load and instantiate the BlogMonthlySummary JavaScript class. Listing 8-19 shows the updated version of left-column.tpl, which now loads and instan- tiates this JavaScript class. Once you reload your page, clicking these links while on the blog manager index will refresh the middle container without reloading the whole page! Listing 8-19. Instantiating the BlogMonthlySummary Class (left-container.tpl) {get_monthly_blog_summary user_id=$identity->user_id assign=summary} {if $summary|@count > 0} <div id="preview-months" class="box"> <h3>Your Blog Archive</h3> <ul> {foreach from=$summary key=month item=numPosts} <li> <a href="{geturl controller='blogmanager'}?month={$month}"> {$month|date_format:'%B %Y'} </a> ({$numPosts} post{if $numPosts != 1}s{/if}) </li> {/foreach} </ul> </div> <script type="text/javascript" <script type="text/javascript"> new BlogMonthlySummary('month-preview', 'preview-months'); </script> {/if} Notifying the User About the Content Update Although the code we have just implemented works well and updates the page as it should, the only problem with it is that it doesn’t give any feedback to the user. To fix this, we will use the messages container we created in Chapter 7 to notify the user that new content is being loaded. CHAPTER 8 ■ EXTENDING THE BLOG MANAGER 287 9063Ch08CMP2 11/11/07 12:35 PM Page 287 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com In this section, we will create two new functions: message_write(), which we use to write a new message to the message container (and then make the container appear if hidden), and message_clear(), which hides the message container. We will then update the BlogMonthlySummary JavaScript class to use these functions so the user knows when page content has been updated. Managing Message Containers The first thing we need to do is to create a new setting for the settings hash in the scripts.js file. When we implement the message_clear() function next, we’ll add a delay so the message is cleared only after the specified interval. This ensures the user has time to read the message before it disappears. Listing 8-20 shows the messages_hide_delay setting we add to scripts.js in ./htdocs/js. This value is the number of seconds before the message container is hidden. Listing 8-20. Adding the Delay Setting to the Application JavaScript Settings (scripts.js) var settings = { messages : 'messages', messages_hide_delay : 0.5 }; Next we define the message_write() and message_clear() functions, which can go after the Event.observe() call in the scripts.js file. Listing 8-21 shows these functions. Listing 8-21. Setting and Clearing Site Status Messages (scripts.js) function message_write(message) { var messages = $(settings.messages); if (!messages) return; if (message.length == 0) { messages.hide(); return; } messages.update(message); messages.show(); new Effect.Highlight(messages); } function message_clear() { setTimeout("message_write('')", settings.messages_hide_delay * 1000); } CHAPTER 8 ■ EXTENDING THE BLOG MANAGER288 9063Ch08CMP2 11/11/07 12:35 PM Page 288 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... our application file naming This also means it can be autoloaded with Zend_Loader # cd /var/www/phpweb20/htdocs/js/fckeditor # cp fckeditor _php5 .php /var/www/phpweb20/include/FCKeditor .php Now we create a new Smarty plug-in called wysiwyg, which we can call in our template using {wysiwyg} Listing 8-25 shows the contents of function.wysiwyg .php, which we store in /include/Templater/plugins Listing 8-25... access URL has been in the format http://phpweb20/controller/action; for example, the edit action of the blogmanager controller has a URL of http://phpweb20/blogmanager/edit If no action is specified, index is the default action used for a controller So in the case of blogmanager, the index action can be accessed using either http://phpweb20/blogmanager or http://phpweb20/blogmanager/index In UserController,... fckeditor.asp _upgrade.html fckeditor.cfc _whatsnew.html fckeditor.cfm editor/ fckeditor.js fckconfig.js fckeditor.lasso fckeditor .php fckeditor.pl fckeditor.py fckeditor _php4 .php fckeditor _php5 .php fckpackager.xml fckstyles.xml fcktemplates.xml htaccess.txt license.txt 9 063 Ch08CMP2 11/11/07 12:35 PM Page 293 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 8 ■ EXTENDING... code to load There is a PHP class bundled with FCKeditor to facilitate the generation of the HTML The FCKeditor class is located in the fckeditor _php5 .php file in the main FCKeditor directory (./htdocs/js/fckeditor) To keep our own code organized, we will copy this class to the application include directory Additionally, we will rename the file to FCKeditor .php to be consistent with our application file... these parameters, we instantiate the FCKeditor class Next we must tell the $fckeditor object where the editor code is stored relative to the web root (we stored it in http://phpweb20/js/fckeditor) Next we must tell it to use the new toolbar we just created (phpweb20) rather than the default toolbar (Default) We then pass in the default value to the class Finally, we call the CreateHtml() method to generate... http://www.fckeditor.net/download We will be storing the code in the /htdocs/js directory, just as we did with Prototype and Scriptaculous Once you have the FCKeditor_2.4.3.tar.gz file, extract it to that directory I have assumed you downloaded the file to /var/www/phpweb20/htdocs/js # cd /var/www/phpweb20/htdocs/js # tar -zxf FCKeditor_2.4.3.tar.gz # rm FCKeditor_2.4.3.tar.gz # cd fckeditor/ # ls _documentation.html... function to the CustomControllerAction .php file in /include Listing 9-5 shows the code for the getCustomUrl() method, which accepts the URL parameters as the first argument and the name of the route as the second argument As described in Chapter 6, we can access the helper using $this->_helper->url from within a controller 305 9 063 Ch09CMP2 11/13/07 8:08 PM Page 3 06 Simpo PDF Merge and Split Unregistered... we will be able to use the following code in the template: {geturl route='user' username='qz'} Listing 9 -6 shows the changes we will make to the function.geturl .php file in /include/Templater/plugins Listing 9 -6 Extending the geturl Smarty Plug-In to Support Custom Routes (function.geturl .php) < ?php function smarty_function_geturl($params, $smarty) { $action = isset($params['action']) ? $params['action']... shows the changes we will make to the User .php file in /include/DatabaseObject I have set the default value for num_posts to be 10, and I chose false as the default setting for blog_public You may prefer different values Listing 9-3 Assigning Default Settings for Users (User .php) < ?php class DatabaseObject_User extends DatabaseObject { // other code 301 9 063 Ch09CMP2 11/13/07 8:08 PM Page 302 Simpo... Create the FCKeditor in a Template (function.wysiwyg .php) < ?php function smarty_function_wysiwyg($params, $smarty) { $name = ''; $value = ''; if (isset($params['name'])) $name = $params['name']; if (isset($params['value'])) $value = $params['value']; $fckeditor = new FCKeditor($name); $fckeditor->BasePath = '/js/fckeditor/'; $fckeditor->ToolbarSet = 'phpweb20'; $fckeditor->Value = $value; return $fckeditor->CreateHtml(); . fckeditor _php4 .php license.txt editor/ fckeditor.js fckeditor _php5 .php fckconfig.js fckeditor.lasso fckpackager.xml CHAPTER 8 ■ EXTENDING THE BLOG MANAGER2 92 9 06 3Ch08CMP2 11/11 /07 12: 35 PM Page 29 2 Simpo. message_clear() { setTimeout("message_write('')", settings.messages_hide_delay * 100 0); } CHAPTER 8 ■ EXTENDING THE BLOG MANAGER288 9 06 3Ch08CMP2 11/11 /07 12: 35 PM Page 28 8 Simpo PDF Merge and Split Unregistered Version -. MANAGER2 82 9 06 3Ch08CMP2 11/11 /07 12: 35 PM Page 28 2 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com {if isset($rightcolumn) && $rightcolumn|strlen > 0} {include