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

How-To Tutorials - Web Development

1797 Articles
article-image-page-management-part-one-cms-design
Packt
14 Dec 2010
6 min read
Save for later

Page Management - Part One in CMS Design

Packt
14 Dec 2010
6 min read
  CMS Design Using PHP and jQuery Build and improve your in-house PHP CMS by enhancing it with jQuery Create a completely functional and a professional looking CMS Add a modular architecture to your CMS and create template-driven web designs Use jQuery plugins to enhance the "feel" of your CMS A step-by-step explanatory tutorial to get your hands dirty in building your own CMS         Read more about this book       (For more resources on this subject, see here.) How pages work in a CMS A "page" is simply the main content which should be shown when a certain URL is requested. In a non-CMS website, this is easy to see, as a single URL returns a distinct HTML file. In a CMS though, the page is generated dynamically, and may include features such as plugins, different views depending on whether the reader was searching for something, whether pagination is used, and other little complexities. In most websites, a page is easily identified as the large content area in the middle (this is an over-simplification). In others, it's harder to tell, as the onscreen page may be composed of content snippets from other parts of the site. We handle these differences by using page "types", each of which can be rendered differently on the frontend. Examples of types include gallery pages, forms, news contents, search results, and so on. In this article, we will create the simplest type, which we will call "normal". This consists of a content-entry textarea in the admin area, and direct output of that content on the front-end. You could call this "default" if you want, but since a CMS is not always used by people from a technical background, it makes sense to use a word that they are more likely to recognize. I have been asked before by clients what "default" means, but I've never been asked what "normal" means. At the very least, a CMS needs some way to create the simplest of web pages. This is why the "normal" type is not a plugin, but is built into the core. Listing pages in the admin area To begin, we will add Pages to the admin menu. Edit /ww.admin/header.php and add the following highlighted line: <ul> <li><a href="/ww.admin/pages.php">Pages</a></li> <li><a href="/ww.admin/users.php">Users</a></li> And one more thing—when we log into the administration part of the CMS, it makes sense to have the "front page" of the admin area be the Pages section. After all, most of the work in a CMS is done in the Pages section. So, we change /ww.admin/index.php so it is a synonym for /ww.admin/pages.php. Replace the /ww.admin/index.php file with this: <?php require 'pages.php'; Next, let's get started on the Pages section. First, we will create /ww.admin/pages.php: <?php require 'header.php'; echo '<h1>Pages</h1>'; // { load menu echo '<div class="left-menu">'; require 'pages/menu.php'; echo '</div>'; // } // { load main page echo '<div class="has-left-menu">'; require 'pages/forms.php'; echo '</div>'; // } echo '<style type="text/css"> @import "pages/css.css";</style>'; require 'footer.php'; Notice how I've commented blocks of code, using // { to open the comment at the beginning of the block, and // } at the end of the block. This is done because a number of text editors have a feature called "folding", which allows blocks enclosed within delimiters such as { and } to be hidden from view, with just the first line showing. For instance, the previous code example looks like this in my Vim editor: What the page.php does is to load the headers, load the menu and page form, and then load the footers. For now, create the directory /ww.admin/pages and create a file in it called /ww.admin/pages/forms.php: <h2>FORM GOES HERE</h2> And now we can create the page menu. Use the following code to create the file /ww.admin/pages/menu.php: <?php echo '<div id="pages-wrapper">'; $rs=dbAll('select id,type,name,parent from pages order by ord,name'); $pages=array(); foreach($rs as $r){ if(!isset($pages[$r['parent']]))$pages[$r['parent']]=array(); $pages[$r['parent']][]=$r; } function show_pages($id,$pages){ if(!isset($pages[$id]))return; echo '<ul>'; foreach($pages[$id] as $page){ echo '<li id="page_'.$page['id'].'">' .'<a href="pages.php?id='.$page['id'].'"'>' .'<ins>&nbsp;</ins>'.htmlspecialchars($page['name']) .'</a>'; show_pages($page['id'],$pages); echo '</li>'; } echo '</ul>'; } show_pages(0,$pages); echo '</div>'; That will build up a &ltul> tree of pages. Note the use of the "parent" field in there. Most websites follow a hierarchical "parent-child" method of arranging pages, with all pages being a child of either another page, or the "root" of the site. The parent field is filled with the ID of the page within which it is situated. There are two main ways to indicate which page is the "front" page (that is, what page is shown when someone loads up http://cms/ with no page name indicated). You can have one single page in the database which has a parent of 0, meaning that it has no parent—this page is what is looked for when http://cms/ is called. In this scheme, pages such as http://cms/pagename have their parent field set to the ID of the one page which has a parent of 0. You can have many pages which have 0 as their parent, and each of these is said to be a "top-level" page. One page in the database has a flag set in the special field which indicates that this is the front page. In this scheme, pages named like http://cms/pagename all have a parent of 0, and the page corresponding to http://cms/ can be located anywhere at all in the database. Case 1 has a disadvantage, in that if you want to change what page is the front page, you need to move the current page under another one (or delete it), then move all the current page's child-pages so they have the new front page's ID as a parent, and this can get messy if the new front-page already had some sub-pages—especially if there are any with equal names. Case 2 is a much better choice because you can change the front page whenever you want, and it doesn't cause any problems at all. When you view the site in your browser now, it looks like this:
Read more
  • 0
  • 0
  • 1994

article-image-yui-2x-using-event-component
Packt
14 Dec 2010
7 min read
Save for later

YUI 2.X: Using Event Component

Packt
14 Dec 2010
7 min read
Yahoo! User Interface Library 2.x Cookbook Over 70 simple incredibly effective recipes for taking control of Yahoo! User Interface Library like a Pro Easily develop feature-rich internet applications to interact with the user using various built-in components of YUI library Simple and powerful recipes explaining how to use and implement YUI 2.x components Gain a thorough understanding of the YUI tools Plenty of example code to help you improve your coding and productivity with the YUI Library Hands-on solutions that take a practical approach to recipes In this article, you will learn how to use YUI to handle JavaScript events, what special events YUI has to improve the functionality of some JavaScript events, and how to write custom events for your own application. Using YUI to attach JavaScript event listeners When attaching events in JavaScript most browsers use the addEventListener function , but the developers of IE use a function called attachEvent. Legacy browsers do not support either function, but instead require developers to attach functions directly to element objects using the 'on' + eventName property (for example myElement.onclick=function(){...}). Additionally, the execution context of event callback functions varies depending on how the event listener is attached. The Event component normalizes all the cross-browser issues, fixes the execution context of the callback function, and provides additional event improvements. This recipe will show how to attach JavaScript event listeners, using YUI. How to do it... Attach a click event to an element: var myElement = YAHOO.util.Dom.get('myElementId'); var fnCallback = function(e) { alert("myElementId was clicked"); }; YAHOO.util.Event.addListener(myElement, 'click', fnCallback); Attach a click event to an element by its ID: var fnCallback = function(e) { alert("myElementId was clicked"); }; YAHOO.util.Event.addListener('myElementId','click',fnCallback) Attach a click event to several elements at once: var ids = ["myElementId1", "myElementId2", "myElementId3"]; var fnCallback = function(e) { var targ = YAHOO.util.Event.getTarget(e); alert(targ.id + " was clicked"); }; YAHOO.util.Event.addListener(ids, 'click', fnCallback); When attaching event listeners, you can provide an object as the optional fourth argument, to be passed through as the second argument to the callback function: var myElem = YAHOO.util.Dom.get('myElementId'); var fnCallback = function(e, obj) { alert(obj); }; var obj = "I was passed through."; YAHOO.util.Event.addListener(myElem,'click',fnCallback,obj); When attaching event listeners, you can change the execution context of the callback function to the fourth argument, by passing true as the optional fifth argument: var myElement = YAHOO.util.Dom.get('myElementId'); var fnCallback = function(e) { alert('My execution context was changed.'); }; var ctx = { /* some object to be the execution context of callback */ }; YAHOO.util.Event.addListener( myElement, 'click', fnCallback, ctx, true); How it works... The addListener function wraps the native event handling functions, normalizing the cross- browser differences. When attaching events, YUI calls the correct browser specific function, or defaults to legacy event handlers. Before executing the callback function, the Event component must (in some browsers) find the event object and adjust the execution context of the callback function. The callback function is normalized by wrapping it in a closure function that executes when the browser event fires, thereby allowing YUI to correct the event, before actually executing the callback function. In legacy browsers, which can only have one callback function per event type, YUI attaches a callback function that iterates through the listeners attached by the addListener function There's more... The addListener function returns true if the event listener is attached successfully and false otherwise. If the element to listen on is not available when the addListener function is called, the function will poll the DOM and wait to attach the listener when the element becomes available. Additionally, the Event component also keeps a list of all events that it has attached. This list is maintained to simplify removing events listeners, and so that all event listeners can be removed when the end-user leaves the page. Find all events attached to an element: var listeners = YAHOO.util.Event.getListeners('myElementId'); for (var i=0,j=listeners.length; i<j; i+=1) { var listener = listeners[i]; alert(listener.type); // event type alert(listener.fn); // callback function alert(listener.obj); // second argument of callback alert(listener.adjust); // execution context } Find all events of a certain type attached to an element: // only click listeners var listeners = YAHOO.util.Event.getListeners('myElementId', 'click'); The garbage collector in JavaScript does not always do a good job cleaning up event handlers. When removing nodes from the DOM, remember to remove events you may have added as well. More on YAHOO.util.Event.addListener The addListener function has been aliased by the shorter on function: var myElement = YAHOO.util.Dom.get('myElementId'); var fnCallback = function(e) { alert("myElementId was clicked"); }; YAHOO.util.Event.on(myElement, 'click', fnCallback); By passing an object in as the optional fifth argument of addListener, instead of a Boolean, you can change the execution context of the callback to that object, while still passing in an another object as the optional fourth argument: var myElement = YAHOO.util.Dom.get('myElementId'); var fnCallback = function(e, obj) { // this executes in the context of 'ctx' alert(obj); }; var obj = "I was passed through."; var ctx = { /* some object to be the execution context of callback */ }; YAHOO.util.Event.addListener( myElement,'click',fnCallback,obj, ctx); Lastly, there is an optional Boolean value that can be provided as the sixth argument of addListener, which causes the callback to execute in the event capture phase, instead of the event bubbling phase. You probably won't ever need to set this value to true, but if you want to learn more about JavaScript event phases see: http://www.quirksmode.org/js/events_order.html Event normalization functions The event object, provided as the first argument of the callback function, contains a variety of values that you may need to use (such as the target element, character code, etc.). YUI provides a collection of static functions that normalizes the cross-browser variations of these values. Before trying to use these properties, you should read this recipe, as it walks you through each of those functions. How to do it... Fetch the normalized target element of an event: var fnCallback = function(e) { var targetElement = YAHOO.util.Event.getTarget(e); alert(targetElement.id); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); Fetch the character code of a key event (also known as the key code): var fnCallback = function(e) { var charCode = YAHOO.util.Event.getCharCode(e); alert(charCode); }; YAHOO.util.Event.on('myElementId', 'keypress', fnCallback); Fetch the x and y coordinates of a mouse event: var fnCallback = function(e) { var x = YAHOO.util.Event.getPageX(e); var y = YAHOO.util.Event.getPageY(e); alert("x-position=" + x + " and x-position= " + y); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); Fetch both the x and y coordinates of a mouse event, using: var fnCallback = function(e) { var point = YAHOO.util.Event.getXY(e); alert("x-position="+point[0]+" and x-position= "+point[1]); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); Fetch the normalized related target element of an event: var fnCallback = function(e) { var targetElement = YAHOO.util.Event.getRelatedTarget(e); alert(targetElement.id); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); Fetch the normalized time of an event: var fnCallback = function(e) { var time = YAHOO.util.Event.getTime(e); alert(time); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); Stop the default behavior, propagation (bubbling) of an event, or both: var fnCallback = function(e) { // prevents the event from bubbling up to ancestors YAHOO.util.Event.stopPropagation(e); // prevents the event's default YAHOO.util.Event.preventDefault(e); // prevents the event's default behavior and bubbling YAHOO.util.Event.stopEvent(e); }; YAHOO.util.Event.on('myElementId', 'click', fnCallback); How it works... All of these functions test to see if a value exists on the event for each cross-browser variation of a property. The functions then normalize those values and return them. The stopPropogation and preventDefault functions actually modify the equivalent cross-browser property of the event, and delegate the behavior to the browsers.
Read more
  • 0
  • 0
  • 5498

article-image-getting-started-spring-python
Packt
10 Dec 2010
12 min read
Save for later

Getting started with Spring Python

Packt
10 Dec 2010
12 min read
Spring Python for Python developers You have already picked one of the most popular and technically powerful dynamic languages to develop software, Python. Spring Python makes it even easier to solve common problems encountered by Python developers every day. Exploring Spring Python's non-invasive nature Spring Python has a non-invasive nature, which means it is easy to adopt the parts that meet your needs, without rewriting huge blocks of code. For example, Pyro (http://pyro.sourceforge.net) is a 3rd party library that provides an easy way to make remote procedure calls. In order to demonstrate the Spring way of non-invasiveness, let's code a simple service, publish it as a web service using Pyro's API, and then publish it using Spring Python's wrapper around Pyro. This will show the difference in how Spring Python simplifies API access for us, and how it makes the 3rd party library easier to use without as much rework to our own code. First, let's write a simple service that parses out the parameters from a web request string: class ParamParser(object): def parse_web_parms(self, parm): return [tuple(p.split("=")) for p in parm.split("&")] Now we can write a simple, functional piece of code that uses our service in order to have a working version. parser = ParamParser() parser.parse_web_parms("pages=5&article=Spring_Python") This is just instantiating the ParamParser and accessing the function. To make this a useful internet service, it needs to be instantiated on a central server and should be configured to listen for calls from clients. The next step is to advertise it as a web service using the API of Pyro. This will make it reachable by multiple Pyro clients. To do this, we define a daemon which will host our service on port 9000 and initialize it. daemon = Pyro.core.Daemon(host="localhost", port="9000") Pyro.core.initServer() Next, we create a Pyro object instance to act as proxy to our service as well as an instance of our ParamParser. We configure the proxy to delegate all method calls to our service. pyro_proxy = Pyro.core.ObjBase() parser = ParamParser() pyro_proxy.delegateTo(parser) Finally, we register the pyro_proxy object with the daemon, and startup a listen-dispatch loop so that it's ready to handle requests: daemon.connect(pyro_proxy, "mywebservice") daemon.requestLoop(True) When we run this server code, an instance of our ParamParser will be created and advertised at PYROLOC://localhost:9000/mywebservice. To make this service complete, we need to create a Pyro client that will call into our service. The proxy seamlessly transfers Python objects over the wire using the Pyro library, in this case the tuple of request parameters. url_base = "PYROLOC://localhost:9000" client_proxy = Pyro.core.getProxyForURI( url_base + "/mywebservice") print client_proxy.parse_web_parms( "pages=5&article=Spring_Python") The Pyro library is easy to use. One key factor is how our ParamParser never gets tightly coupled to the Pyro machinery used to serve it to remote clients. However, it's very invasive. What if we had already developed a simple application on a single machine with lots of methods making use of our utility? In order to convert our application into a client-server application, we would have to rewrite it to use the Pyro client proxy pattern everywhere that it was called. If we miss any instances, we will have bugs that need to be cleaned up. If we had written automated tests, they would also have to be rewritten as well. Converting a simple, one-machine application into a multi-node application can quickly generate a lot of work. That is where Spring Python comes in. It provides a different way of creating objects which makes it easy for us to replace a local object with a remoting mechanism such as Pyro. Let's utilize Spring Python's container to create our parser and also to serve it up with Pyro. from springpython.config import PythonConfig from springpython.config import Object from springpython.remoting.pyro import PyroServiceExporter from springpython.remoting.pyro import PyroProxyFactory class WebServiceContainer(PythonConfig): def __init__(self): super(WebServiceContainer, self).__init__() @Object(lazy_init=True) def my_web_server(self): return PyroServiceExporter(service=ParamParser(), service_name="mywebservice", service_port=9000) @Object(lazy_init=True) def my_web_client(self): myService = PyroProxyFactory() myService.service_url="PYROLOC://localhost:9000/mywebservice" return myService With this container definition, it is easy to write both a server application as well as a client application. To spin up one instance of our Pyro server, we use the following code: from springpython.context import ApplicationContext container = ApplicationContext(WebServiceContainer()) container.get_object("my_web_server") The client application looks very similar. from springpython.context import ApplicationContext container = ApplicationContext(WebServiceContainer()) myService = container.get_object("my_web_client") myService.parse_web_parms("pages=5&article=Spring_Python") The Spring Python container works by containing all the definitions for creating key objects. We create an instance of the container, ask it for a specific object, and then use it. This easily looks like just as much (if not more) code than using the Pyro API directly. So why is it considered less invasive? Looking at the last block of code, we can see that we are no longer creating the parser or the Pyro proxy. Instead, we are relying on the container to create it for us. The Spring Python container decouples the creation of our parser, whether its for a local application, or if it uses Pyro to join them remotely. The server application doesn't know that it is being exported as a Pyro service, because all that information is stored in the WebServiceContainer. Any changes made to the container definition aren't seen by the server application code. The same can be said for the client. By putting creation of the client inside the container, we don't have to know whether we are getting an instance of our service or a proxy. This means that additional changes can be made inside the definition of the container of Spring Python, without impacting our client and server apps. This makes it easy to split the server and client calls into separate scripts to be run in separate instances of Python or on separate nodes in our enterprise. This demonstrates how it is possible to mix in remoting to our existing application. By using this pattern of delegating creation of key objects to the container, it is easy to start with simple object creation, and then layer on useful services such as remoting. Later in this book, we will also see how this makes it easy to add other services like transactions and security. Due to Spring Python's open ended design, we can easily create new services and add them on without having to alter the original framework. Adding in some useful templates In addition to the non-invasive ability to mix in services, Spring Python has several utilities that ease the usage of low level APIs through a template pattern. The template pattern involves capturing a logical flow of steps. What occurs at each step is customizable by the developer, while still maintaining the same overall sequence. One example where a template would be useful is for writing a SQL query. Coding SQL queries by hand using Python's database API (http://www.python.org/dev/peps/pep-0249) is very tedious. We must properly handle errors and harvest the results. The extra code involved with connecting things together and handling issues is commonly referred to as plumbing code. Let's look at the following code to see how Python's database API functions. The more plumbing code we have to maintain, the higher the cost. Having an application with dozens or hundreds of queries can become unwieldy, even cost prohibitive to maintain. Using Python's database API, we only have to write the following code once for setup. ### One time setup import MySQLdb conn = MySQLdb.connection(username="me", password"secret", hostname="localhost", db="springpython") Now let's use Python's database API to perform a single query. ### Repeated for every query cursor = conn.cursor() results = [] try: cursor.execute("""select title, air_date, episode_number, writer from tv_shows where name = %s""", ("Monty Python",)) for row in cursor.fetchall(): tvShow = TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) results.append(tvShow) finally: try: cursor.close() except Exception: pass conn.close() return results   The specialized code we wrote to look up TV shows is contained in the execute statement and also the part that creates an instance of TvShow. The rest is just plumbing code needed to handle errors, manage the database cursor, and iterate over the results.   This may not look like much, but have you ever developed an application with just one SQL query? We could have dozens or even hundreds of queries, and having to repeatedly code these steps can become overwhelming. Spring Python's DatabaseTemplate lets us just inject the query string and and a row mapper to reduce the total amount of code that we need to write. We need a slightly different setup than before. """One time setup""" from springpython.database.core import * from springpython.database.factory import * connectionFactory = MySQLConnectionFactory(username="me", password="secret", hostname="localhost", db="springpython") We also need to define a mapping to generate our TvShow objects. class TvShowMapper(RowMapper): def map_row(self, row, metadata=None): return TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) With all this setup, we can now create an instance of DatabaseTemplate and use it to execute the same query with a much lower footprint. dt = DatabaseTemplate(connectionFactory) """Repeated for each query""" results = dt.query("""select title, air_date, episode_number, writer from tv_shows where name = %s""", ("Monty Python",), TvShowMapper()) This example shows how we can replace 19 lines of code with a single statement using Spring Python's template solution. Object Relational Mappers (ORMs) have sprung up in response to the low level nature of ANSI SQL's protocol. Many applications have simple object persistence requirements and many of us would prefer working on code, and not database design. By having a tool to help do the schema management work, these ORMs have been a great productivity boost. But they are not necessarily the answer for every use case. Some queries are very complex and involve looking up information spread between many tables, or involve making complex calculations and involve decoding specific values. Also, many legacy systems are denormalized and don't fit the paradigm that ORMs were originally designed to handle. The complexity of these queries can require working around, or even against, the ORM-based solutions, making them not worth the effort. To alleviate the frustration of working with SQL, Spring Python's DatabaseTemplate greatly simplifies writing SQL, while giving you complete freedom in mapping the results into objects, dictionaries, and tuples. DatabaseTemplate can easily augment your application, whether or not you are already using an ORM. That way, simple object persistence can be managed with ORM, while complex queries can be handed over to Spring Python's DatabaseTemplate, resulting in a nice blend of productive, functional code. Other templates, such as TransactionTemplate, relieve you of the burden of dealing with the low level idioms needed to code transactions that makes them challenging to incorporate correctly. Later in this book, we will learn how easy it is to add transactions to our code both programmatically and declaratively. Applying the services you need and abstracting away low level APIs is a key part of the Spring way and lets us focus our time and effort on our customer's business requirements instead of our own technical ones. By using the various components we just looked at, it isn't too hard to develop a simple Pyro service that serves up TV shows from a relational database. from springpython.database.factory import * from springpython.config import * from springpython.remoting.pyro import * class TvShowMapper(RowMapper): def map_row(self, row, metadata=None): return (title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) class TvShowService(object): def __init__(self): self.connFactory = MySQLConnectionFactory(username="me", password="secret", hostname="localhost", db="springpython") self.dt = DatabaseTemplate(connFactory) def get_tv_shows(self): return dt.query("""select title, air_date, episode_number, writer from tv_shows where name = %s""", ("Monty Python",), TvShowMapper()) class TvShowContainer(PythonConfig): def __init__(self): super(TvShowContainer, self).__init__() @Object(lazy_init=True) def web_server(self): return PyroServiceExporter(service=TvShowService(), service_name="tvshows", service_port=9000) @Object(lazy_init=True) def web_client(self): myService = PyroProxyFactory() myService.service_url="PYROLOC://localhost:9000/tvshows" return myService if __name__ == "__main__": container = ApplicationContext(TvShowContainer()) container.get_object("web_server") By querying the database for TV shows and serving it up through Pyro, this block of code demonstrates how easy it is to use these powerful modules without mixing them together. It is much easier to maintain software over time when things are kept simple and separated. We just took a quick walk through SQL and Pyro and examined their low level APIs. Many low level APIs require a certain sequence of steps to properly utilize them. We just looked at a SQL query. The need for templates also exists with database transactions and LDAP calls. By capturing the flow of the API in a Spring Python template and allowing you to insert your custom code, you can get out of writing plumbing code and instead work on your application's business logic.
Read more
  • 0
  • 0
  • 5868
Visually different images

article-image-plugins-cms-design
Packt
10 Dec 2010
15 min read
Save for later

Plugins in CMS Design

Packt
10 Dec 2010
15 min read
  CMS Design Using PHP and jQuery Build and improve your in-house PHP CMS by enhancing it with jQuery Create a completely functional and a professional looking CMS Add a modular architecture to your CMS and create template-driven web designs Use jQuery plugins to enhance the "feel" of your CMS A step-by-step explanatory tutorial to get your hands dirty in building your own CMS         Read more about this book       (For more resources on this subject, see here.) After completing this article, the CMS could be considered "complete", in that almost every other requested feature can be supplied by writing a plugin for it. However, it should be noted that a CMS never is actually complete, because each new website may bring a new request that is not yet catered for. Having said that, using plugins lets you at least complete a "core" engine and concentrate on providing hooks that allow further development to be done, outside that core. What are plugins A plugin is a module of code that can be dropped into a directory and enabled, to give a CMS extra capabilities. Plugins need to be able to change the output and do other tasks, so it is necessary to add various "hooks" throughout the code where the plugins can apply their code. A very important reason for adding a plugin architecture to a CMS is that it lets you stabilize the core code. The core is basically the code that will be available in every instance of the CMS, as opposed to plugin code, which may or may not be present in any particular instance of the CMS. With a core piece of code that is deemed "complete", it becomes easier to manage bugs. Because you are not always adding to the core code, you are not actively adding to the potential number of bugs. In a CMS which does not have a stable core, any change to the central code can affect just about anything else. You really need to get your CMS to a stage where you are no longer developing the central engine. Instead, you are working mostly on external plugins and maybe occasional bug fixes to the core, as they are found. In my case, for example, I worked for years on building up a CMS before getting around to building in plugins. Every change that was requested was built into the core code. Usually, only the fully-tested code at that time would be the new code, so very often we would miss a problem that the new code would have caused somewhere else in the CMS. Often, this problem would not show up for weeks, so it would not be obvious what the problem was related to! When all the development of a CMS is shifted to plugins, it becomes less likely that the core is at fault when a problem occurs. Because plugins, by their nature, tend to be isolated pieces of code, if a bug does appear, it is very likely the bug is within the plugin's code and not anywhere else. Also, because plugins allow a person to develop without touching the core engine, it is possible for the external teams or individuals to create their own plugins that they can use with the engine, without needing to understand all the parts of the core engine. One more advantage is that if the plugin architecture is solid, it is possible for development to continue on the core completely separately from the plugins, knowing that plugins from one version of the CMS will most likely work with a core from another version. Events in the CMS One example of a hook is event triggers. In JavaScript (and therefore jQuery), there is the concept of events, where you can set a block of code to run when a certain trigger happens. For example, when you move your mouse over an element, there are a number of potential trigger points—onmouseover, onmouseenter, onmousemove (and possibly others, depending on the context). Obviously, PHP does not have those events, as it's a server-side language. But it is possible to conceive of triggers for your CMS that you could potentially hook onto. For example, let's say you've just finished figuring out the page content. At this point, you may want to trigger a page-content-created event. This could (and will, in this article) be used by a Page Comments plugin to tack on the comments thread, and any required forms, to the end of that page content. Another example: Let's say you want to create a custom log for your own purposes. You would then be interested in a start trigger that can be used to initialize certain values, such as a timer. After the output has been sent, a finish trigger that can be used to tally up a number of figures (compilation time, memory used, size of rendered output, and so on) and record them in a file or database before the script finishes. Page types In some cases, you will want the page content to be totally converted. Instead of showing a page body as normal, you may want to show an image gallery or a store checkout. In this case, you would need to create a "page type" block of code, which the frontend will use instead of the usual page data render() call. In the admin area, this might also require using a customized form instead of the usual rich text editor. Admin sections The admin area may need to have new sections added by a plugin. In the Events section, we described a logging plugin. A perfect complement to that is a graphing log viewer, which would be shown as a completely new admin section and have its own entry in the admin menu. Page admin form additions You may also want to add extra forms to all the Page forms in the admin, regardless of what page type it is. For example, if you create a security plugin and want to protect various pages depending on who is viewing it, you will need to be able to choose which users or groups have access and what to display if the current user does not have full access. This requires an additional form in the Page admin. It is very difficult to describe all the possible plugin uses, and the number of triggers that may be required. The easiest way to proceed is to just adjust the engine as required. If it turns out you forgot to add an event trigger at some point, it should be a small matter to just add it in at that point without affecting the core code beyond that addition. Example plugin configuration Create a directory called /ww.plugins. Each plugin you create will be placed in a directory—one directory per plugin. For our first example, we're going to build a Page Comments plugin, which will allow visitors to your site to leave comments on your pages. On the admin side, we will need to provide methods to maintain the submitted comments per page and for the whole site. Anyway, create a directory to hold the plugin called /ww.plugins/page-comments. The CMS will expect the plugin configuration for each plugin to be in a file named plugin.php. So the configuration for the Page Comments plugin /ww.plugins/page-comments/plugin.php is as follows: <?php $plugin=array( 'name' => 'Page Comments', 'description' => 'Allow your visitors to comment on pages.', 'version' => '0', 'admin' => array( 'menu' => array( 'Communication>Page Comments' => 'comments' ) , 'page_tab' => array( 'name' => 'Comments', 'function' => 'page_comments_admin_page_tab' ) ), 'triggers' => array( 'page-content-created' => 'page_comments_show' ) ); The plugin.php files at least contain an array named $plugin, which describes the plugin. For now, let's look at what the current example says. All of these options, except the first two, are optional. First, we define a name, "Page Comments". This is only ever used in the admin area, when you are choosing your plugins. The same is true of the description field. The version field is used by the CMS to tell whether a plugin is up-to-date or if some automatic maintenance is needed. This will be explained in more detail later in this article. Next, we have the admin array, which holds details of the admin-only functions. The menu array is used to edit the admin menu, in case you need to add an admin section for the plugin. In this case, we will add an admin section for Page Comments, which will let you set site-wide settings and view comments site-wide. If a new tab is to be added to the page admin section, this tab is described in the page_tab array. name is what appears in the tab header, and function is the name of a PHP function that will be called to generate the tab content. Finally, the triggers array holds details of the various triggers that the plugin should react to. Each trigger calls a function. Obviously, this is not a complete list, and it is not possible to ever have a complete list, as each new circumstance you are requested to write for may bring up a need for a trigger or plugin config setting that you had not thought of. However, there are less and less additions, as the plugin architecture becomes more complete. From the plugin configuration, you can see that there are some functions named, which we have not defined. You should define those functions in the same file: function page_comments_admin_page_tab($PAGEDATA){ require_once SCRIPTBASE.'ww.plugins/page-comments/' .'admin/page-tab.php'; return $html; } function page_comments_show($PAGEDATA){ if(isset($PARENTDATA->vars->comments_disabled) && $PARENTDATA->vars->comments_disabled=='yes') return; require_once SCRIPTBASE.'ww.plugins/page-comments/' .'frontend/show.php'; } The functions are prefixed with an identifier to make sure that they don't clash with the functions from other plugins. In this case, because the plugin is named Page Comments, the prefix is page_comments_. The functions here are essentially stubs. Plugins will be loaded every time any request is made to the server. Because of this, and the obvious fact that not all the functions would be needed in every request, it makes sense to keep as little code in it as possible in the plugin.php files. In most cases, triggers will be called with just the $PAGEDATA object as a parameter. Obviously, in cases in the admin area where you're not editing any particular page this would not make sense, but for most plugins, to keep the function calls consistent, the only parameter is $PAGEDATA. Enabling plugins We have defined a plugin. We could make it such that when you place a plugin in the /ww.plugins directory, it is automatically enabled. However, if you are creating a CMS that you intend to reuse for a lot of other clients, it is a lot easier to simply copy the entire CMS source and reconfigure, than to copy the CMS source and then clear out the existing plugins and repopulate carefully with new ones that you would download from a repository that you keep somewhere else. So, what we do is we give the admin a maintenance page where they choose the plugins they want to load. The CMS then only loads those and does not even look at the other directories. Edit the /ww.admin/header.php file and add a new link (highlighted) to the plugin admin section: <a href="/ww.admin/themes.php">Themes</a>     Plugins     Log Out   We will be changing the admin menu later in this article to make it customizable more easily, but for now, add in that link manually. Now create the /ww.admin/plugins.php file: <?php require 'header.php'; echo ' Plugin Management '; echo ' '; echo 'Users'; echo 'Themes'; echo 'Plugins'; echo ' '; echo ' '; echo ' Plugin Management '; require 'plugins/list.php'; echo ' '; require 'footer.php'; You'll have noticed that this is similar to the /ww.admin/themes.php and /ww.admin/users.php files. They're all related to site-wide settings, so I've placed links to them all in the left-menu. Edit those files and add in the new Plugins link to their menus. Before we create the page for listing the enabled plugins, we must first set up the array of enabled plugins in /ww.incs/basics.php, by adding this to the end of the file: // { plugins $PLUGINS=array(); if (isset($DBVARS['plugins'])&&$DBVARS['plugins']) { $DBVARS['plugins']=explode(',',$DBVARS['plugins']); foreach($DBVARS['plugins'] as $pname){ if (strpos('/',$pname)!==false) continue; require SCRIPTBASE . 'ww.plugins/'.$pname.'/plugin.php'; $PLUGINS[$pname]=$plugin; } } else $DBVARS['plugins']=array(); // } As you can see, we are again referencing the $DBVARS array in the /.private/config.php. Because we already have a function for editing that, all we need to do to change the list of enabled or disabled plugins, and create and maintain the $DBVARS['plugins'] array, making sure to resave the config file after each change. What the code block does is that it reads in the plugin.php file for each enabled plugin, and saves the $plugin array from each file into a global $PLUGINS array. The $DBVARS['plugins'] variable is an array, but we'll store it as a comma-delimited string in the config file. Edit config_rewrite() in the same file and add this highlighted line: $tmparr=$DBVARS; $tmparr['plugins']=join(',',$DBVARS['plugins']); $tmparr2=array(); We'll enhance the plugin loader in a short while. In the meantime, let's finish the admin plugin maintenance page. Create the directory /ww.admin/plugins, and in it, add /ww.admin/plugins/list.php: <?php echo ' '; echo ' '; // { list enabled plugins first foreach($PLUGINS as $name=>$plugin){ echo '', '', '', ''; } // } // { then list disabled plugins $dir=new DirectoryIterator(SCRIPTBASE . 'ww.plugins'); foreach($dir as $plugin){ if($plugin->isDot())continue; $name=$plugin->getFilename(); if(isset($PLUGINS[$name]))continue; require_once(SCRIPTBASE.'ww.plugins/'.$name.'/plugin.php'); echo '', '', '', '', ''; } // } echo ' Plugin Name Description ',htmlspecialchars(@$plugin['name']),' ',htmlspecialchars(@$plugin['description']),' disable ',htmlspecialchars($plugin['name']),' ',htmlspecialchars($plugin['description']),' enable '; When viewed in a browser, it displays like this: The script displays a list of already-enabled plugins (we have none so far), and then reads the /ww.plugins directory for any other plugins and adds them along with an "enable" link. Now we need to write some code to do the actual selection/enabling of the plugins. While it would be great to write some jQuery to do it in an Ajaxy way (so you click on the enable link and the plugin is enabled in the background, without reloading the page), there are too many things that might cause problems. For instance, if the plugin caused new items to appear in the menu, we'd have to handle that. If the plugin changed the theme, or did anything else that caused a layout change, we'd have to handle that as well. So instead, we'll do it the old-fashioned PHP way—you click on enable or disable, which does the job on the server, and then reloads the plugin page so you can see the change. Create the /ww.admin/plugins/enable.php file: <?php require '../admin_libs.php'; if(!in_array($_REQUEST['n'],$DBVARS['plugins'])){ $DBVARS['plugins'][]=$_REQUEST['n']; config_rewrite(); } header('Location: /ww.admin/plugins.php'); It simply adds the requested plugin to the $DBVARS['plugins'] array, then rewrites the config and redirects the browser back to the plugins page. When clicked, the page apparently just reloads, and the plugin's link changes to disable. The opposite script is just as simple. Write this code block in the file /ww.admin/plugins/disable.php: <?php require '../admin_libs.php'; if(in_array($_REQUEST['n'],$DBVARS['plugins'])){ unset($DBVARS['plugins'][ array_search($_REQUEST['n'],$DBVARS['plugins']) ]); config_rewrite(); } header('Location: /ww.admin/plugins.php'); In this case, all we needed to do was to remove the plugin name from $DBVARS['plugins'] by unsetting its position in the array. Plugins are now very simply set up. Here's a screenshot of that page with a number of plugins enabled and disabled. I copied some plugins from a more mature copy of the CMS that I have. We will be looking at a few of them, and building one or two others: The enabled plugins are moved to the top of the list to make them more visible and the rest are shown below them.
Read more
  • 0
  • 0
  • 1116

article-image-building-admin-interface-drupal-7
Packt
03 Dec 2010
11 min read
Save for later

Building an admin interface in Drupal 7

Packt
03 Dec 2010
11 min read
The User Warn module In this article we will be creating the User Warn module. This module allows administrators to send users a warning via e-mail when that user violates a site's terms of service or otherwise behaves in a way that is inappropriate. The User Warn module will implement the following features: The module will expose configuration settings to site administrators, including default mail text This e-mail will include Drupal tokens, which allow the admin to replace and/or add site-specific variables to the e-mail Site administrators will be able to send a user mail via a new tab on their user profile page Warning e-mails will be sent using Drupal's default mail implementation Starting our module We will begin by creating a new folder for our module called user_warn in the sites/default/modules directory in our Drupal installation. We can then create a user_warn.info file as shown in the following: ;$Id$ name = User Warn description = Exposes an admin interface to send behavior warning e-mails to users. core = 7.x package = Drupal 7 Development files[] = user_warn.module You should be pretty familiar with this now. We will also create our user_warn.module file and add an implementation of hook_help() to let site administrators know what our module does. http://api.drupal.org/api/function/hook_menu/7 The example is as follows: /** * Implement hook_menu(). */ function user_warn_menu() { $items = array(); $items['admin/config/people/user_warn'] = array( 'title' => 'User Warn', 'description' => 'Configuration for the User Warn module.', 'page callback' => 'drupal_get_form', 'page arguments' => array('user_warn_form'), 'access arguments' => array('administer users'), 'type' => MENU_NORMAL_ITEM, ); $items['user/%/warn'] = array( 'title' => 'Warn', 'description' => 'Send e-mail to a user about improper site behavior.', 'page callback' => 'drupal_get_form', 'page arguments' => array('user_warn_confirm_form', 1), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_TASK, ); return $items; } Like many Drupal hook implementations, hook_menu() returns a structured associative array with information about the menu items being defined. The first item in our example defines the module configuration page, and the second one defines the user tab where administrators can go to send the actual e-mail. Let's look at the first item in more detail. Menu items are keyed off their path. This is an internal Drupal path with no leading or trailing slashes. This path not only defines the location of a page, but also its place in the menu hierarchy, with each part of the URL being a child of the last. In this example, people is a child of config which is itself a child of admin. If a requested path does not exist, Drupal will work its way up the hierarchy until it encounters a page that does exist. You can see this in action by requesting admin/config/people/xyzzy which displays the page at admin/config/people. If you are creating a menu item for site administration it must begin with admin. This places it into Drupal's administrative interface and applies the admin theme defined by the site settings. Module-specific settings should always be present under admin/config. Drupal 7 offers several categories which module developers should use to better organize their settings according to Drupal functional groups like People and Permissions or Content Authoring. The value associated with this key is itself an associative array with several keys that define what action should be taken when this URL is requested. We can now look at those in detail. The first item defines your page title: 'title' => 'User Warn', This is used in a variety of display contexts—as your page's heading, in the HTML &lttitle&gt tag and as a subheading in the administration interface in combination with the description (if you are defining an administrative menu item). 'description' => 'Configuration for the User Warn module.', The description is just that—a longer text description of the page that this menu item defines. This should provide the user with more detailed information about the actions they can take on this page. This description is also used as the title tag when you hover over a link. Menu item titles and descriptions are passed through t() internally by Drupal, so this is one case where we don't need to worry about doing that ourselves. For an administration page, these two items define how your page is listed in Drupal's admin area as shown in the following: The next two items define what will happen when your page is requested: 'page callback' => 'drupal_get_form', 'page arguments' => array('user_warn_form'), 'page callback' defines the function that will get called (without the parentheses) and 'page arguments' contains an array of arguments that get passed to this function. Often you will create a custom function that processes, formats, and returns specific data. However, in our case we are calling the internal Drupal function drupal_get_form() that returns an array as defined by Drupal's Form API. As an argument we are passing the form ID of the form we want to display. The fifth item controls who can access your page. 'access arguments' => array('administer users'), 'access arguments' takes an array containing a permissions strings. Any user who has been assigned one of these permissions will be able to access this page. Anyone else will be given an Access denied page. Permissions are defined by modules using hook_permission(). You can see a full list of the currently defined permissions at admin/people/permissions as shown: You can see the 'administer users' permission at the bottom of this list. In the preceding example, only the Administrator role has this permission, and as a result only those users assigned this role will be able to access our page. Note that the titles of the permissions here do not necessarily match what you will need to enter in the access arguments array. Unfortunately, the only good way to find this information is by checking the hook_perm() implementation of the module in question. The final item defines what type of menu item we are creating: 'type' => MENU_NORMAL_ITEM, The 'type' is a bitmask of flags that describe what features we want our menu item to have (for instance, whether it is visible in the breadcrumb trail). Drupal defines over 20 constants for menu items that should cover any situation developers will find themselves in. The default type is MENU_NORMAL_ITEM, which indicates that this item will be visible in the menu tree as well as the breadcrumb trail. This is all the information that is needed to register our path. Now when Drupal receives a request for this URL, it will return the results of drupal_get_form(user_warn_form). Drupal caches the entire menu, so new/updated menu items will not be reflected immediately. To manually clear the cache, visit Admin | Configuration | Development | Performance and click on Clear all caches. Using wildcards in menu paths We have created a simple menu item, but sometimes simple won't do the job. In the User Warn module we want to have a menu item that is tied to each individual user's profile page. Profile pages in Drupal live at the path user/&ltuser_id&gt, so how do we create a distinct menu item for each user? Fortunately the menu system allows us to use wildcards when we define our menu paths. If you look at the second menu item defined in the preceding example, you will see that its definition differs a bit from our first example. $items['user/%/warn'] = array( 'title' => 'Warn', 'description' => 'Send e-mail to a user about improper site behavior.', 'page callback' => 'drupal_get_form', 'page arguments' => array('user_warn_confirm_form', 1), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_TASK, ); The first difference is that the path is defined with % as one of the path entries. This indicates a wildcard; anything can be entered here and the menu item's hierarchy will be maintained. In Drupal, that will always be a user's ID. However, there is nothing stopping any user from entering a URL like user/xyzzy/warn or something else potentially more malicious. Your code should always be written in such a way as to handle these eventualities, for instance by verifying that the argument actually maps to a Drupal user. This would be a good improvement. The other difference in this example is that we have added 1 as an additional argument to be passed to our page callback. Each argument in a menu item's path can be accessed as an argument that is available to be passed to our page callback, starting with 0 for the root argument. So here the string user is item 0, and the user's ID is item 1. To use the user's ID as a page callback argument, we reference it by its number. The result in this case is that the user's ID will be passed as an additional argument to drupal_get_form(). We have one other difference in this second menu item: 'type' => MENU_LOCAL_TASK, We have defined our type as MENU_LOCAL_TASK. This tells Drupal that our menu item describes actions that can be performed on the parent item. In this example, Warn is an action that can be performed on a user. These are usually rendered as an additional tab on the page in question, as you can see in the following example user profile screen: Having defined the paths for our pages through hook_menu(), we now need to build our forms. Form API In standard web development, one of the most tedious and unrewarding tasks is defining HTML forms and handling their submissions. Lay out the form, create labels, write the submission function, figure out error handling, and the worst part is that from site to site much of this code is boilerplate—it's fundamentally the same, differing only in presentation. Drupal's Form API is a powerful tool allowing developers to create forms and handle form submissions quickly and easily. This is done by defining arrays of form elements and creating validation and submit callbacks for the form. In past versions of Drupal, Form API was commonly referred to as FAPI. However, Drupal 7 now has three APIs which could fit this acronym—Form API, Field API and File API. We will avoid using the acronym FAPI completely, to prevent confusion, but you will still encounter it widely in online references. Form API is also a crucial element in Drupal's security. It provides unique form tokens that are verified on form submission, preventing Cross-site Request Forgery attacks, and automatically validating required fields, field lengths, and a variety of other form element properties. Using drupal_get_form() In our first menu implementation seen earlier, we defined the page callback as drupal_get_form(). This Form API function returns a structured array that represents an HTML form. This gets rendered by Drupal and presented as an HTML form for the user. drupal_get_form() takes a form ID as a parameter. This form ID can be whatever you want, but it must be unique within Drupal. Typically it will be &ltmodule_name&gt_&ltdescription&gt_form. The form ID is also the name of the callback function drupal_get_form() will call to build your form. The specified function should return a properly formatted array containing all the elements your form needs. Since the form ID also serves as the form's callback function, it must be a valid PHP variable name. Spaces and hyphens are not allowed. All form IDs should be prefaced by the name of your module followed by an underscore, in order to prevent name collision. Other parameters can be passed into drupal_get_form() in addition to the form ID. These extra parameters simply get passed through to the callback function for its own use. In Drupal 6, drupal_get_form() returned a fully rendered HTML form. This has been changed in Drupal 7 in order to allow more flexibility in theming and easier form manipulation. drupal_get_form() now returns an unrendered form array which must be passed to drupal_render() for final output. In the preceding example the menu system handles the change transparently, but other code converted from Drupal 6 may need to be changed.
Read more
  • 0
  • 0
  • 3487

article-image-creating-content-drupal-7
Packt
03 Dec 2010
3 min read
Save for later

Creating Content in Drupal 7

Packt
03 Dec 2010
3 min read
Drupal 7 First Look Learn the new features of Drupal 7, how they work and how they will impact you Get to grips with all of the new features in Drupal 7 Upgrade your Drupal 6 site, themes, and modules to Drupal 7 Explore the new Drupal 7 administration interface and map your Drupal 6 administration interface to the new Drupal 7 structure Complete coverage of the DBTNG database layer with usage examples and all API changes for both Themes and Modules         Read more about this book       (For more resources on Drupal 7, see here.) Creating content for your site is at the core of any Content Management System like Drupal. The primary changes for Drupal 7 relate to an updated interface. Let's look at the new interface in detail. Selecting a content type to create To create content in Drupal 7, first log in to your site and then click on Content from the site toolbar. Drupal will now display the new Content Administration page. Move mouse over the image to enlarge it. In Drupal 6, this page could be displayed by selecting Administer | Content Management | Content from the Navigation menu. From here, click on the Add new content link. Drupal will now display a page allowing you to select the type of content you want to create. Depending on the modules you have installed and enabled, you will have different content types available. In previous versions of Drupal, this page could be reached by selecting the Create Content link from the Navigation menu. You can also select the type of content to add using the shortcut bar. You can access the shortcut bar by clicking on the toggle at the far right of the toolbar: The shortcut bar has a list of links in it that can be used to quickly access commonly used functionality, and it appears as follows: You can customize the links in the shortcut bar and users can use either the default set of shortcuts or they can have their own. Now select the type of content you want to create. For this example, we will use the Basic page type. Content UI The interface to create content has been altered drastically from Drupal 6. Let's go through the interface in detail. The top section of the page should be familiar to experienced editors. This is the place to enter your title as well as the full text of the page. In a departure from previous versions, the node summary, which is used when multiple nodes are displayed on a page, is an entirely separate optional field.
Read more
  • 0
  • 1
  • 3368
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 €14.99/month. Cancel anytime
article-image-user-interface-production
Packt
01 Dec 2010
10 min read
Save for later

User Interface in Production

Packt
01 Dec 2010
10 min read
  Liferay User Interface Development Develop a powerful and rich user interface with Liferay Portal 6.0 Design usable and great-looking user interfaces for Liferay portals Get familiar with major theme development tools to help you create a striking new look for your Liferay portal Learn the techniques and tools to help you improve the look and feel of any Liferay portal A practical guide with lots of sample code included from real Liferay Portal Projects free for use for developing your own projects         Read more about this book       (For more resources on this subject, see here.) This section will introduce how to add workflow capabilities to any custom assets in plugins. Knowledge Base articles will be used as an example of custom assets. In brief, the Knowledge Base plugin enables companies to consolidate and manage the most accurate information in one place. For example, a user manual is always updated; users are able to rate, to add workflow and to provide feedback on these Knowledge Base articles. Preparing a plugin—Knowledge Base First of all, let's prepare a plugin with workflow capabilities, called Knowledge Base. Note that the plugin Knowledge Base here is used as an example only. You can have your own plugin as well. What's Knowledge Base? The plugin Knowledge Base allows authoring articles and organize them in a hierarchy of navigable categories. It leverages Web Content articles, structures, and templates; allows rating on articles; allows commenting on articles; allows adding hierarchy of categories; allows adding tags to articles; exports articles to PDF and other formats; supports workflow; allows adding custom attributes (called custom fields); supports indexing and advanced search; allows using the rule engine and so on. Most importantly the plugin Knowledge Base supports import of a semantic markup language for technical documentation called DocBook. DocBook enables its users to create document content in a presentation-neutral form that captures the logical structure of the content; that content can then be published in a variety of formats, such as HTML, XHTML, EPUB, and PDF, without requiring users to make any changes to the source. Refer to http://www.docbook.org/. In general, the plugin Knowledge Base provides four portlets inside: Knowledge Base Admin (managing knowledge base articles and templates), Knowledge Base Aggregator (publishing knowledge base articles), Knowledge Base Display (displaying knowledge base articles) and Knowledge Base Search (the ability to search knowledge base articles). Structure The plugin Knowledge Base has following folder structure under the folder $PLUGIN_SDK_HOME/knowledge-base-portlet. admin: View JSP files for portlet Admin aggregator: View JSP files for portlet Aggregator display: View JSP files for portlet Display icons: Icon images files js: JavaScript files META-INF: Contains context.xml search: View JSP files for portlet Search WEB-INF: Web info specification; includes subfolders classes, client, lib, service, SQL, src, and tld As you can see, JSP files such as init.jsp and css_init.jsp are located in the folder $PLUGIN_SDK_HOME/knowledge-base-portlet. Services and models As you may have noticed, the plugin Knowledge Base has specified services and models with the package named com.liferay.knowledgebase. You would be able to find details at $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/service.xml. Service-Builder in Plugins SDK will automatically generate services and models against service.xml, plus XML files such as portlet-hbm.xml, portlet-model-hints.xml, portlet-orm.xml, portlet-spring.xml, base-spring.xml, cluster-spring.xml,dynamic-data-source-spring.xml, hibernate-spring.xml, infrastructure-spring.xml, messaging-spring.xml, and shard-data-source-spring.xml under the folder $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/src/META-INF. The service.xml specified Knowledge Base articles as entries: Article, Comment, and Template. The entry Article included columns: article Id as primary key, resource Prim Key, group Id, company Id, user Id, user Name, create Date, modified Date, parent resource Prim Key, version, title, content, description, and priority; the entry Template included columns: template Id as primary key, group Id, company Id, user Id, user Name, create Date, modified Date, title, content, and description; while the entity Comment included columns: comment Id as primary key, group Id, company Id, user Id, user Name, create Date, modified Date, class Name Id, class PK, content and helpful. As you can see, the entity Comment could be applied on either any core assets or custom assets like Article and Template by using class Name Id and class PK. By the way, the custom SQL scripts were provided at $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/src/custom-sql/default.xml. In addition, resource actions, that is, permission actions specification—are provided at $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/src/resource-actions/default.xml. Of course, you can use Ant target build-wsdd to generate WSDD server configuration file $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/server-config.wsdd and to use Ant target build-client plus namespace-mapping.properties to generate web service client JAR file like $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/client/known-base-portlet-client.jar. In brief, based on your own custom models and services specified in service.xml, you can easily build service, WSDD, and web service client in plugins of Liferay 6 or above version. Adding workflow instance link First, you have to add a workflow instance link and its related columns and finder in $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/service.xml as follows. <column name="status" type="int" /> <column name="statusByUserId" type="long" /> <column name="statusByUserName" type="String" /> <column name="statusDate" type="Date" /> <!-- ignore details --> <finder name="R_S" return-type="Collection"> <finder-column name="resourcePrimKey" /> <finder-column name="status" /> </finder> <!-- ignore details --> <reference package-path="com.liferay.portal" entity="WorkflowInstance Link" /> As shown in the above code, the column element represents a column in the database, four columns like status, statusByUserId, statusByUserName, and statusDate are required for Knowledge Base workflow, storing workflow related status, and user info; the finder element represents a generated finder method, the method finder R_S is defined as Collection (an option) for return type with two columns, for example, resourcePrimkey and status; where the reference element allows you to inject services from another service.xml within the same class loader. For example, if you inject the Resource (that is, WorkflowInstanceLink) entity, then you'll be able to reference the Resource services from your service implementation via the methods getResourceLocalService and getResourceService. You'll also be able to reference the Resource services via the variables resourceLocalService and resourceService. Then, you need to run ANT target build-service to rebuild service based on newly added workflow instance link. Adding workflow handler Liferay 6 provides pluggable workflow implementations, where developers can register their own workflow handler implementation for any entity they build. It will appear automatically in the workflow admin portlet so users can associate workflow entities with available permissions. To make it happen, we need to add the Workflow Handler in $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/WEB-INF/liferay-portlet.xml of plugin as follows. <workflow-handler>com.liferay.knowledgebase.admin.workflow. ArticleWorkflowHandler</workflow-handler> As shown in the above code, the workflow-handler value must be a class that implements com.liferay.portal.kernel.workflow.BaseWorkflowHandler and is called when the workflow is run. Of course, you need to specify ArticleWorkflowHandler under the package com.liferay.knowledgebase.admin.workflow. The following is an example code snippet. public class ArticleWorkflowHandler extends BaseWorkflowHandler { public String getClassName(){/* get target class name */}; public String getType(Locale locale) {/* get target entity type, that is, Knowledge base article*/}; public Article updateStatus( int status, Map<String, Serializable> workflowContext) throws PortalException, SystemException {/* update workflow status*/}; protected String getIconPath(ThemeDisplay themeDisplay) {/* find icon path */ return ArticleLocalServiceUtil.updateStatus(userId, resourcePrimKey, status, serviceContext); }; } As you can see, ArticleWorkflowHandler extends BaseWorkflowHandler and overrode the methods getClassName, getType, updateStatus, and getIconPath. That's it. Updating workflow status As mentioned in the previous section, you added the method updateStatus in ArticleWorkflowHandler. Now you should provide implementation of the method updateStatus in com.liferay.knowledgebase.service.impl.ArticleLocalServiceImpl.java. The following is some example sample code: public Article updateStatus(long userId, long resourcePrimKey, int status, ServiceContext serviceContext) throws PortalException, SystemException { /* ignore details */ // Article Article article = getLatestArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY); articlePersistence.update(article, false); if (status != WorkflowConstants.STATUS_APPROVED) { return article; } // Articles // Asset // Social // Indexer // Attachments // Subscriptions } As shown in above code, it first gets latest article by resourcePrimKey and WorkflowConstants.STATUS_ANY. Then, it updates the article based on the workflow status. And moreover, it updates articles display order, asset tags and categories, social activities, indexer, attachments, subscriptions, and so on. After adding new method at com.liferay.knowledgebase.service.impl.ArticleLocalServiceImpl.java, you need to run ANT target build-service to build services. Adding workflow-related UI tags Now it is time to add workflow related UI tags (AUI tags are used as an example) at $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/admin/edit_article.jsp. First of all, add the AUI input workflow action with value WorkflowConstants.ACTION_SAVE_DRAFT as follows. <aui:input name="workflowAction" type="hidden" value=" <%= WorkflowConstants.ACTION_SAVE_DRAFT %>" /> As shown in above code, the default value of AUI input workflowAction was set as SAVE DRAFT with type hidden. That is, this AUI input is invisible to end users. Afterwards, it would be better to add workflow messages by UI tag liferay-ui:message, like a-new-version-will-be-created-automatically-if-this-content-is-modified for WorkflowConstants.STATUS_APPROVED, and there-is-a-publication-workflow-in-process for WorkflowConstants.STATUS_PENDING. <% int status = BeanParamUtil.getInteger(article, request, "status", WorkflowConstants.STATUS_DRAFT); %> <c:choose> <c:when test="<%= status == WorkflowConstants.STATUS_APPROVED %>"> <div class="portlet-msg-info"> <liferay-ui:message key="a-new-version-will-be-created- automatically-if-this-content-is-modified" /> </div> </c:when> <c:when test="<%= status == WorkflowConstants.STATUS_PENDING %>"> <div class="portlet-msg-info"> <liferay-ui:message key="there-is-a-publication-workflow-in-process" /> </div></c:when> </c:choose> And then add AUI workflow status tag aui:workflow-status at $PLUGIN_SDK_HOME/knowledge-base-portlet/docroot/admin/edit_article.jsp. <c:if test="<%= article != null %>"> <aui:workflow-status id="<%= String.valueOf(resourcePrimKey) %>" status="<%= status %>" version="<%= GetterUtil.getDouble(String. valueOf(version)) %>" /> </c:if> As you can see, aui:workflow-status is used to represent workflow status. Similarly you can find other AUI tags like a button, button_row, column, field_wrapper, fieldset, form, input, layout, legend, option, panel, script, and select. Finally you should add the JavaScript to implement the function publishArticle() as follows. function <portlet:namespace />publishArticle() { document.<portlet:namespace />fm.<portlet:namespace />workflowAction.value = "<%= WorkflowConstants.ACTION_PUBLISH %>"; <portlet:namespace />updateArticle(); } As you can see, the workflow action value is set as WorkflowConstants.ACTION_PUBLISH. You have added workflow capabilities on Knowledge Base articles in plugins. From now on, you will be able to apply workflow on Knowledge Base articles through Control Panel.
Read more
  • 0
  • 0
  • 1202

article-image-creating-custom-themes-and-zen-drupal-6
Packt
01 Dec 2010
9 min read
Save for later

Creating Custom Themes and Zen in Drupal 6

Packt
01 Dec 2010
9 min read
Drupal 6 Theming Cookbook Over 100 clear step-by-step recipes to create powerful, great-looking Drupal themes Take control of the look and feel of your Drupal website Tips and tricks to get the most out of Drupal's theming system Learn how to customize existing themes and create unique themes from scratch Part of Packt's Cookbook series: Each recipe is a carefully organized sequence of instructions to complete the task as efficiently as possible         Read more about this book       (For more resources on Drupal, see here.) Introduction Sub-themes of core and contributed themes are convenient and efficient in modifying and reusing elements of their base themes, circumstances often require a completely unique approach specific to our site. Custom themes are the solution for websites which demand a fresh look, using complex layouts, or need so much customization that it would be prudent to start with a clean slate. Custom themes are the equivalent of handcrafted pieces of art as the themer controls every piece of the puzzle from a design or implementational point of view. This includes setting up the theme using .info files, choosing the layout, implementing it in a page template, adding regions, styling nodes using node templates, blocks using block templates, and so on. But over time, developers have identified a list of common tasks, characteristic layouts, and file and folder hierarchies which are logical, efficient, and promote reuse. This has evolved into what have been dubbed starter themes, themes upon which custom themes are built, usually as sub-themes. The most popular starter theme for Drupal is Zen. As advertised on its project page, the Zen theme is a flexible standards-compliant and semantically correct XHTML theme that can be highly modified through CSS and an enhanced version of Drupal's template system. It is designed in modular fashion making it straightforward to change layouts, override templates and theme functions, and to add or remove features. Additionally, the Zen theme comes with extensive documentation within each file which make things all the more convenient. With respect to CSS, Zen maintains a number of well documented CSS files segregated by functionality or location. For example, layout rules are contained within a dedicated layout.css (or similar) file and page backgrounds are styled within page-backgrounds.css and so on. This makes it convenient when it comes to managing and tracking code changes. A Zen-based theme contains the following file and folder structure: File/folder name Purpose template.php A file where theme overrides and other theme and engine-related code is placed. theme-settings.php A file where settings particular to a theme can be placed. These settings are usually exposed on the theme's configuration page. css/ A folder to store stylesheets. images/ A folder to store images used in the theme. images-source/ The folder where the source files for the optimized images in the images folder are available. js/ A folder to store JavaScript files. templates/ A folder where tpl.php template files are to be placed. There are a number of other starter themes available on drupal.org. Some of the more popular ones include Fusion (http://drupal.org/project/fusion), Blueprint (http://drupal.org/project/blueprint), Ninesixty (http://drupal.org/ project/ninesixty), and Adaptivetheme (http://drupal.org/project/ adaptivetheme). We will be looking only at the Zen starter theme in this article. Clearing the theme registry Before we begin, we need to familiarize ourselves with a seemingly trivial yet crucial task that needs to be performed on a routine basis during theme development—clearing the theme registry. The theme registry is essentially a table that Drupal uses to list and track the files and features of a theme, as well as the theme functions which are being exposed by modules and the theme itself. While it is recommended practice to turn on Drupal's cache feature only for production sites, the theme registry is built and cached regardless of other caching options. As a result, any changes that affect the structure of the theme will necessitate the clearing of the theme registry. Getting ready Rebuilding the registry is an intensive operation which is required only when changes have been made to the theme's files. How to do it... There are a number of ways of clearing the registry. In a stock Drupal installation, visiting admin/settings/performance (Home | Administer | Site configuration | Performance) and clicking on the Clear cached data button will clear all cached data, including the registry, and force a rebuild. A shortcut It is sometimes handy to know that the cache and registry can also be cleared by visiting admin/build/themes (Home | Administer | Site building | Themes) and just clicking the Save configuration button. However, during development or debugging, we will want to clear the registry with great regularity. Rather than having to do so manually, it is often handy to be able to instruct Drupal to perform this operation automatically on every page load. Some themes, including the Zen-based theme which we will be familiarizing ourselves with later in this article, offer an option on their configuration pages to rebuild the registry on every page load. While this is certainly convenient, the recommended method of managing this and other development-oriented operations is through the use of the Devel module. As the name suggests, the Devel module is one which is tailor-made for use during development. It can be downloaded from http://drupal.org/project/devel. Once the module has been downloaded and installed, navigate to admin/settings/devel (Home | Administer | Site configuration | Devel settings) where the option to Rebuild the theme registry on every page load can be enabled. How it works... Drupal maintains a cache of all .info files, template files, and theme functions in the theme registry. This registry is a part of the cache table in the Drupal database. When we click on the Clear cache data button in the performance settings page, all Drupal is doing is clearing this entry in the cache table, which automatically forces a rebuild of the registry. The Devel module does the same thing when the Rebuild the theme registry on every page load setting is enabled, except that it does this automatically on every page view. It is important to keep in mind that rebuilding the registry, or for that matter, clearing any of the caches is an expensive operation which adversely affects the performance of the site. Therefore, it is recommended that this setting only be enabled during development and not in production sites. Clearing the registry is an important factor to keep in mind during development and especially during debugging. There's more... The Devel module also provides a block with handy shortcuts to oft-used areas of the site. Clearing the cache using the Development block The Devel module provides a Development block which can be enabled via the block management page at admin/build/blocks (Home | Administer | Site building | Blocks). Once enabled, the block lists, as in the following screenshot, a number of links to perform operations such as emptying the Drupal cache, rebuilding the menu cache, and even reinstalling modules. Emptying the cache will also force a rebuild of the theme registry. Creating a theme from scratch While we have previously looked at installing contributed themes and extending base themes using sub-themes, this recipe will outline the steps required to create a custom theme. Getting ready It is assumed that the sites/all/themes folder has already been created. This is the recommended location to place custom and contributed themes. How to do it... While creating a brand new custom theme, files such as page.tpl.php will need to be explicitly defined and there is no base theme. Create a folder with the new theme's name inside the sites/all/themes folder. In this example, we are going to call our theme mytheme. Create a file named mytheme.info and open it in an editor. Add details about the theme as follows: name = My themedescription = My custom themecore = 6.xengine = phptemplate Save the file. Visit the theme administration page at admin/build/themes (Home | Administer | Site building | Themes) and we should be able to see a new entry for our theme. Enable the theme by checking its Enabled checkbox. Also, set it as the default theme by selecting its Default radio button. Click the Save configuration button at the bottom of the page to save the changes. How it works... Just as with other themes, Drupal scans the sites/all/themes folder looking for .info files which indicate the presence of a theme. Seeing mytheme.info, it parses the file and loads the details of the theme, and saves them in the database. When the new theme is enabled, what we will see is largely unstyled content not unlike the following screenshot. The problem here is that we have not specified any CSS stylesheets to ay out the page. The only styles being loaded are those that are module-specific as opposed o theme-specific. The styles being used in the preceding screenshot are as follows: <link type="text/css" rel="stylesheet" media="all"href="/mysite/modules/node/node.css?u" /><link type="text/css" rel="stylesheet" media="all"href="/mysite/modules/system/defaults.css?u" /><link type="text/css" rel="stylesheet" media="all"href="/mysite/modules/system/system.css?u" /><link type="text/css" rel="stylesheet" media="all"href="/mysite/modules/system/system-menus.css?u" /><link type="text/css" rel="stylesheet" media="all"href="/mysite/modules/user/user.css?u" /> As we can see, the only stylesheets in evidence are those belonging to core modules and none from our theme. In addition, Drupal has noticed that we do not have any template files in our theme, most notably, page.tpl.php. Therefore, it has loaded an inbuilt page template file from modules/system/page.tpl.php and used it instead. Similarly, it is using the node.tpl.php file from modules/node/ as the basis for each node's layout. In other words, we have a lot of work ahead of us in getting things up and running especially if our eventual requirements are going to be complicated. As we will see in the next recipe, this is one of the reasons why most themers prefer to use a starter theme and hit the ground running.
Read more
  • 0
  • 0
  • 1196

article-image-vaadin-portlets-liferay-user-interface-development
Packt
01 Dec 2010
1 min read
Save for later

Vaadin Portlets in Liferay User Interface Development

Packt
01 Dec 2010
1 min read
Vaadin portlets are developed with Vaadin framework. The Vaadin framework can also be used to develop standalone web applications. Liferay portal supports the Vaadin portlets. In this section, we will write a Vaadin portlet for Liferay portal using the Vaadin Eclipse plugin. Required software Install the following software for the development environment, if they are not already there: Eclipse Java EE IDE Liferay portal 6.x.x with Tomcat 6.0.x Configuring Tomcat 6.0 in Eclipse If you have not already done so, configure Tomcat 6.0 in Eclipse as follows: Start Eclipse. Click on Window | Preferences. Expand Server. Click on Runtime Environment. Click on Add .... Select Apache Tomcat v6.0. Click on Next. Click on Browse and open the tomcat-6.0.x directory. Click on Finish. Installing Vaadin Eclipse plugin You can automatically create a Vaadin portlet prototype for Liferay portal with the Vaadin Eclipse plugin. Here is how you can install it: Assuming that Eclipse is open. Click on Help. Select Install New Software ... Click on Add .... Input Name: Vaadin, Location: http://vaadin.com/eclipse. Click on OK. Click on Finish. The Vaadin Eclipse plugin will be installed. It will take several minutes.
Read more
  • 0
  • 0
  • 1572

article-image-advanced-theme-liferay-user-interface-development
Packt
01 Dec 2010
16 min read
Save for later

Advanced Theme in Liferay User Interface Development

Packt
01 Dec 2010
16 min read
Liferay User Interface Development Develop a powerful and rich user interface with Liferay Portal 6.0 Design usable and great-looking user interfaces for Liferay portals Get familiar with major theme development tools to help you create a striking new look for your Liferay portal Learn the techniques and tools to help you improve the look and feel of any Liferay portal A practical guide with lots of sample code included from real Liferay Portal Projects free for use for developing your own projects Changing theme.parent property in theme Liferay provides four different theme implementations out-of-box in the ${PORTAL_ROOT_HOME}/html/themes/ folder as shown in the following screenshot: When you create a new theme in the themes/ directory of the Plugins SDK, the Ant script will copy some files to the new theme from one of these three folders, _styled/, _unstyled/, and classic/, which can be specified in the ${PLUGINS_SDK_HOME}/themes/build-common-theme.xml file. For example, you can go to the ${PLUGINS_SDK_HOME}/themes/ directory and run create.bat palmtree "Palm-Tree Publications theme" on Windows or ./create.sh palmtree "Palm-Tree Publications theme" on Linux, respectively. Or you can use Liferay IDE to create same New Liferay Theme Plugin Project. This will create a theme folder named palmtree-theme in the Liferay Plugins SDK environment. When you run ant to build and deploy the theme and then apply the newly created theme to your page, you will find out that the look and feel of the page is messed up, as shown in the following screenshot: If you are familiar with theme development in Liferay Portal 5.2, you may wonder what has happened to the theme created in Liferay Portal 6 Plugins SDK because you would expect a fully working theme with two simple commands, create and ant. This is because the following build.xml file in your theme specifies _styled as the value for the theme.parent property: <?xml version="1.0"?> <project name="palmtree-theme" basedir="." default="deploy"> <import file="../build-common-theme.xml" /> <property name="theme.parent" value="_styled" /> </project> This means that when your newly created theme is built, it will copy all the files from the _styled/ folder in the ${PORTAL_ROOT_HOME}/html/themes/ directory to the docroot/ folder of your theme. The default _styled/ folder does not have enough files to create a completely working theme and that is why you see a messed-up page when the theme is applied to a page. The reason why this default _styled/ folder does not include enough files is that some Liferay users prefer to have minimal set of files to start with. You can modify the build.xml file for your theme in the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/ folder by changing value of the theme.parent property from _styled to classic, if you prefer to use the Classic theme as the basis for your theme modification. <property name="theme.parent" value="classic" /> Now you will see that your page looks exactly the same as that with the Classic theme after you build and apply your theme to the page (refer to the following screenshot): Adding color schemes to a theme When you create a theme in the Plugins SDK environment, the newly created theme by default supports one implementation and does not automatically have color schemes as variations of the theme. This fits well if you would like to have a consistent look and feel, especially in terms of the color display, across all the pages that the theme is applied to. In your portal application, you might have different sites with slightly different look and feel for different groups of users such as physicians and patients. You might also need to display different pages such as public pages with one set of colors and the private pages with a different set of colors. However, you don't want to create several different themes for reasons such as easy maintenance. In this case, you might consider creating different color schemes in your theme. Color schemes are specified using a Cascading Style Sheets (CSS) class name, with which you are able to not only change the colors, but also choose different background images, border colors, and so on. In the previous section, we created a PalmTree Publication theme, which takes the Classic theme as its parent theme. Now we can follow the mentioned steps to add color schemes to this theme: Copy the ${PORTAL_ROOT_HOME}/webapps/palmtree-theme/WEB-INF/liferay-look-and-feel.xml file to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/WEB-INF/ folder. Open the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/WEB-INF/liferay-look-and-feel.xml file in your text editor. Change <theme id="palmtree" name="PalmTree Publication Theme" /> as shown in the highlighted lines here, and save the change: <look-and-feel> <compatibility> <version>6.0.5+</version> </compatibility> <theme id="palmtree" name="Palm Tree Publications Theme"> <color-scheme id="01" name="Blue"> <css-class>blue</css-class> <color-scheme-images-path>${images-path}/color_schemes/ blue</color-scheme-images-path> </color-scheme> <color-scheme id="02" name="Green"> <css-class>green</css-class> </color-scheme> <color-scheme id="03" name="Orange"> <css-class>orange</css-class> </color-scheme> </theme> </look-and-feel> Go to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/palmtree-theme/_diffs/ folder and create a css/ subfolder. Copy both custom.css and custom_common.css from the ${PORTAL_ROOT_HOME}/html/themes/classic/_diffs/css/ folder to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/css/ folder. This is to let the default styling handle the first color scheme blue. Create a color_schemes/ folder under the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/palmtree-theme/_diffs/css/ folder. Place one .css file for each of your additional color schemes. In this case, we are going to create two additional color schemes: green and orange. To make the explanation simpler, copy both the green.css and orange.css files from the ${PORTAL_ROOT_HOME}/html/themes/classic/_diffs/css/color_schemes/ folder to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/css/color_schemes/ folder. Copy all images from the ${PORTAL_ROOT_HOME}/html/themes/classic/_diffs/images/ folder to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/images/ folder. Add the following lines in your ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/css/custom.css file: @import url(color_schemes/green.css); @import url(color_schemes/orange.css); If you open either the green.css or the orange.css file, you will be able to identify the styling for the CSS by using a color prefix for each CSS definition. For example, in the orange.css file you would find that each item is defined like this: orange .dockbar { background-color: #AFA798; background-image: url(../../images/color_schemes/orange/dockbar /dockbar_bg.png); } .orange .dockbar .menu-button-active { background-color: #DBAC5C; background-image: url(../../images/color_schemes/orange/dockbar /button_active_bg.png); } In the green.css file, the style is defined like this: green .dockbar { background-color: #A2AF98; background-image: url(../../images/color_schemes/green/dockbar/ dockbar_bg.png); } .green .dockbar .menu-button-active { background-color: #95DB5C; background-image: url(../../images/color_schemes/green/dockbar/ button_active_bg.png); } Also, notice that the related images used for the different color schemes are included in the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/images/color_schemes/ folder. Re-build and deploy the PalmTree Publication theme. Log in as administrator and go to the Control Panel to apply this theme to the PalmTree Publications Inc. organization. You will be able to see three color schemes available under this theme. Select any of them to apply it to the Public Pages. Take a screenshot of the page when each of the color schemes is applied and save it as thumbnail.png in the corresponding blue, green, and orange subfolders in the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/images/color_scheme/ folder. Three screenshots are used to distinguish between the three color schemes in the Control Panel as seen in the following screenshot: The following screenshots shows how each of the three color schemes looks when applied to a portal page: As shown in above screenshot, color scheme blue has been applied on the Home page, by default. The following screenshot shows applying color scheme green on the current page. Of course, you would be able to apply color schemes blue or green in the entire site, if required. Similar to color schemes blue and green, you can apply color scheme orange as well on the current page or the entire site, as shown in following screenshot: So it works! The page background is with a hue of gray color. Now, what if we want to change the page background color to red for the green color schema? Update the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/css/color_schemes/green.css file as follows. The commented outline is the original content. The next line after the comment is the new content. The #FF0000 code is for the red color. body.green, .green .portlet { /*background-color: #F1F3EF;*/ background-color: #FF0000; } Re-deploy the PalmTree theme and refresh the portal page that uses the green color scheme. Now, you should be able to see the portal page with a red background color. As you can see, you can use theme color schemes to create some variants of the same theme without creating multiple themes. This is useful when you have different but related user roles such as physicians, nurses, and patients and you need to build a different site for each of them. You can use the color schemes to display each of these sites in a slightly different look and feel. Configurable theme settings There are many use cases where you would like to change some default settings in the theme so that you can modify the values after the theme is built and deployed. Fortunately, each theme can define a set of settings to make this configurable. The settings are defined as key-value pairs in the liferay-look-and-feel.xml file of the theme with the following syntax: <settings> <setting key="my-setting1" value="my-value1" /> <setting key="my-setting2" value="my-value2" /> </settings> These settings can then be accessed in the theme templates using the following code: $theme.getSetting("my-setting1") $theme.getSetting("my-setting2") For example, I need to create two themes—PalmTree Publications theme and AppleTree Publications theme. They are exactly the same except for some differences in the footer content that includes copyright, terms of use, privacy policy, and so on. Instead of creating two themes packaged as separate .war files, we create one set of two themes that share the majority of the code including CSS, JavaScript, images, and most templates; but with one configurable setting and two different implementations of the footer Velocity files. Here is how this can be done: Open ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/WEB-INF/liferay-look-and-feel.xml in the above sample PalmTree theme. Copy the PalmTree theme section and paste it in the same file but after this section. Rename the values of id and name from palmtree and PalmTree Publications theme to appletree and AppleTree Publications theme in the second section. Add the following setting to the palmtree section before the color-scheme definition: <settings> <setting key="theme-id" value="palmtree" /> </settings> Add the following setting to the appletree section before the color-scheme definition: <settings> <setting key="theme-id" value="appletree" /> </settings> Find the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/WEB-INF/liferay-look-and-feel.xml file as follows: <look-and-feel> // ignore details <theme id="palmtree" name="PalmTree Publications Theme"> <settings> <setting key="theme-id" value="palmtree" /> </settings> <color-scheme id="01" name="Blue"> // ignore details </color-scheme> </theme> <theme id="appletree" name="AppleTree Publications Theme"> <settings> <setting key="theme-id" value="appletree" /> </settings> <color-scheme id="01" name="Blue"> // ignore details </color-scheme> </theme> </look-and-feel> Copy the portal_normal.vm file of the Classic theme from the ${PORTAL_ROOT_HOME}/html/themes/classic/_diffs/templates/ folder to the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/templates/ folder. Open the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/templates/portal_normal.vm file Replace the default footer section with the following code: #set ($theme_id = $theme.getSetting("theme-id")) #if ($theme_id == "palmtree") #parse ("$full_templates_path/footer_palmtree.vm") #else #parse ("$full_templates_path/footer_appletree.vm") #end Create your own version of the two footer Velocity templates in the ${PLUGINS_SDK_HOME}/themes/palmtree-theme/docroot/_diffs/templates/ folder. Add related CSS definitions for your footer in your custom.css file. Build and deploy the theme .war file. Now you should be able to see both the PalmTree and AppleTree themes when you go to the Control Panel to apply either theme to your page. Based on the theme to be used, you should also notice that your footer is different. Of course, we can take other approaches to implement a different footer in the theme. For example, you can dynamically get the organization or community name and render the footer differently. However, the approach we explained previously can be expanded to control the UI of the other theme components such as the header, navigation, and portlets. Portal predefined settings in theme In the previous section, we discussed that theme engineers can add configurable custom settings in the liferay-look-and-feel.xml file of a theme. Liferay portal can also include some predefined out-of-the-box settings such as portlet-setup-show-borders-default and bullet-style-options in a theme to control certain default behavior of the theme. Let us use portlet-setup-show-borders-default as an example to explain how Liferay portal controls the display of the portlet border at different levels. If this predefined setting is set to false in your theme, Liferay portal will turn off the borders for all portlets on all pages where this theme is applied to. <settings> <setting key="portlet-setup-show-borders-default" value="false" /> </settings> By default, the value is set to true, which means that all portlets will display the portlet border by default. If the predefined portlet-setup-show-borders-default setting is set to true, it can be overwritten for individual portlets using the liferay-portlet.xml file of a portlet as follows: <liferay-portlet-app> // ignore details <portlet> <portlet-name>sample</portlet-name> <icon>/icon.png</icon> <use-default-template>false</use-default-template> <instanceable>true</instanceable> <header-portlet-css>/css/main.css</header-portlet-css> <footer-portlet-javascript>/js/main.js</footer-portlet-javascript> <css-class-wrapper>sample-portlet</css-class-wrapper> </portlet> // ignore details </liferay-portlet-app> Set the use-default-template value to true if the portlet uses the default template to decorate and wrap content. Setting it to false will allow the developer to maintain the entire output content of the portlet. The default value is true. The most common use of this is when you want the portlet to look different from the other portlets or if you want the portlet not to have borders around the output content. The use-default-template setting of each portlet, after being set to either true or false, in the liferay-portlet.xml file, can be further overwritten by the portlet's popup CSS setting. Users with the appropriate permissions can change it by going to the Look and Feel | Portlet Configuration | Show Borders checkbox of the portlet, as shown in the next screenshot: Embedding non-instanceable portlets in theme One common requirement in theme development is to add some portlets in different components of a theme. For example, you might want to add the Sign In portlet in the header of your theme, the Web Content Search portlet in the navigation area, and the Site Map portlet in the footer area. All the Liferay out-of-the-box portlets can be referred in the ${PORTAL_ROOT_HOME}/WEB-INF/liferay-portlet.xml file. How can this be achieved? Well, it can be quite easy or pretty tricky to embed an out-of-the-box portlet in a theme. Embedding Dockbar and Breadcrumb portlets in a theme The Dockbar portlet is embedded at the very top in the default Classic theme in the portal_normal.vm file as shown next: #if($is_signed_in) #dockbar() #end In the same way, the Breadcrumb portlet can be embedded in the content area of the Classic theme in the portal_normal.vm file: #breadcrumbs() Embedding Language and Web Content Search portlets in a theme Some other Liferay out-of-the-box portlets such as the Language and Web Content Search portlets can be embedded in a theme or a layout template easily. For example, the Web Content Search portlet can be added to the far right side of the horizontal navigation area of your theme as follows in the navigation.vm file of your theme. <div id="navbar"> <div id="navigation" class="sort-pages modify-pages"> <div id="custom"> $theme.journalContentSearch() </div> // ignore details </div> </div> In the same way, the Language portlet can be embedded in the portal_normal.vm Velocity template file of your theme: $theme.language() Again, you need to add the necessary CSS definitions in your custom.css file to control the look and feel and the location of your embedded portlet(s). Embedding Sign In portlet in the header area of a theme Sometimes the theme design requires that the Liferay Sign In portlet be in the header area. By default, the Sign In portlet has a portlet border but we need to disable it. The previously mentioned approaches for embedding a portlet in a theme through Velocity attributes in a template do not work in this case because we need to customize the default UI of the embedded portlet. Instead, we can add the following code to the header section in the portal_normal.vm file in our sample PalmTree theme: #if(!$is_signed_in) #set ($locPortletId = "58") $velocityPortletPreferences.setValue("portlet-setup-show-borders", "false") #set($locRenderedPortletContent = $theme.runtime($locPortletId, "", $velocityPortletPreferences.toString())) $locRenderedPortletContent $velocityPortletPreferences.reset() #end After the theme is re-built and re-deployed, we can see that the Sign In portlet is rendered in the header area underneath the logo without the portlet border. The next step for us is to modify the custom.css file and the related files by adding CSS definition to control the location and look and feel of this Sign In portlet. The following screenshot shows the Sign In portlet in the header area and the Web Content Search portlet in the navigation area in a working theme in the production environment:
Read more
  • 0
  • 0
  • 5984
article-image-tips-tricks-ext-js-3x
Packt
30 Nov 2010
3 min read
Save for later

Tips & Tricks for Ext JS 3.x

Packt
30 Nov 2010
3 min read
  Learning Ext JS 3.2 Build dynamic, desktop-style user interfaces for your data-driven web applications using Ext JS Learn to build consistent, attractive web interfaces with the framework components Integrate your existing data and web services with Ext JS data support Enhance your JavaScript skills by using Ext's DOM and AJAX helpers Extend Ext JS through custom components An interactive tutorial packed with loads of example code and illustrative screenshots           Read more about this book       (For more resources on Ext JS, see here.) Objective: Button focus and tab orders are built in. Tip: Button focus and tab orders are built into Ext JS Components. For the MessageBox Component, the OK or Yes button will be the default action, so pressing Enter on our keyboard when a MessageBox appears will trigger that button, and pressing Tab will move us through the buttons and other items in the MessageBox. Windows have the ESC key mapped to the Close tool. When using button bars in other Componenets, tabbing through the buttons is enabled by default and in Toolbars like the PagingToolbar tabbing is also built in. Objective: Duplicate Component ID's must be avoided. Tip: Having duplicate IDs in our document can lead to strange behavior, such as a widgets always showing up in the upper-left corner of the browser, and must therefore be avoided. Objective: Other uses of Button config on a MessageBox. Tip: The buttons config for a MessageBox can also specify the text to display on the button. Instead of passing a Boolean value, just pass it the desired text to display, for example, {yes: 'Maybe'}. Objective: Ext does not require any pre-existing markup. Tip: Ext does not require any pre-existing markup for it to function, this is because it generates everything it needs on its own. This let's us start with a very simple HTML document containing an empty body. Objective: Avoid using the built in JavaScript alert messages. Tip: Standard JavaScript alert messages pause code execution, which can cause unexpected results. You should not be using the built in JavaScript alert messages, and instead use Ext's MessageBox widget or console messages (when available), which does not pause that code execution. Objective: Creating form Field validation types (vType). Tip: A search of the Ext JS forum is likely to come back with a vType that someone else has created with exactly what you need, or close enough to use as a starting point for your own requirements, so search the Ext JS Forums before trying to write your own. Objective: Simple and quick static options for a ComboBox. Tip:; A quick way to specify a few static options for a ComboBox is to pass an array to the store config, which will auto-create the store for you. So if we wanted a ComboBox that had 'Yes' and 'No' as options, we would provide ['Yes','No'] as the store config value.
Read more
  • 0
  • 0
  • 3080

article-image-introduction-drupal-web-services
Packt
29 Nov 2010
13 min read
Save for later

Introduction to Drupal Web Services

Packt
29 Nov 2010
13 min read
  Drupal Web Services Integrate social and multimedia Web services and applications with your Drupal Web site. Explore different Web services and how they integrate with the Drupal CMS. Reuse the applications without coding them again using the Web services protocols on your Drupal site. Configure your Drupal site to consume various web services by using contributed Drupal modules for each specific task or application. Drive the content from your Drupal site to Facebook, Twitter and LinkedIn using effective Drupal Web services An easy to follow guide that opens up a method of easily sharing data and content resources between applications and machines that are running different platforms and architecture.       What are web services? In order for our Drupal site to communicate and interact with other web applications, such as Flickr, Amazon, Mollom, or Twitter, we need to use standard communication protocols in the web development world called web services. Web service protocols will allow applications that reside on external websites and servers to interact and communicate with our Drupal website that is running on our own server. Web services will also allow our Drupal site to pass along content and data to external web applications existing on remote servers. When we define web services, we need to point out that this type of communication provides for interoperability. This means that a web service communication can happen between two completely different environments but still work because we use standard protocols to make it happen. Web services allow us to call another application on a remote server. A good analogy to this is using your cell phone to call a friend or colleague. You have one type of cell phone using one type of cell phone service. You call your colleague's cell phone. They have another type of cell with a different service provider, but the call goes through and is successful because the two services communicate with each other using standard cell phone communication protocols. The web service call happens through coded protocols that are translated into a language and standard protocol that both computers can understand. Generally, this is done by using the XML language to translate the programmed request into the other external applications. Web applications have a standard in which they can usually read XML files. XML is a text-based format, so nearly all computer systems and applications can work with the XML format. The web services protocol also uses a concept called remoting or Remote Procedure Calling (RPC) that allows one application to initiate or "call" a function inside of an application on a remote server. Our Drupal site can communicate with an application on a remote server and call a function in that application. For example, we might want to make our Drupal website call and interact with our Flickr photo gallery, or we may want to take all of our Drupal home page content and push it over to our Facebook account. We can do both of these actions using the web service protocols. XML and web services As mentioned above, the base foundation for web services is a protocol or code called XML. For our Drupal site residing on our server, to talk and interact with a website or application on another server, we need to use XML, which is a language commonly understood between different applications. Our site and server understands XML as does the application we want to communicate with. We can do this over the standard HTTP protocol for website communication, as HTTP is the most standard protocol for Internet communication. The reason we use XML for communication between the applications and the sites is because XML replaces the proprietary function (whether the function is in RPC or another programming language or interface) and formats it into the standard XML code format. This allows applications to understand each other easily. An analogy to this is: if we have two people, one from Germany and the other from France, speaking to one another, and neither person knows the other's language but both of them know English, then they must speak in English, as it's a language they both understand and can easily communicate in. It's a similar situation when XML is used to translate a web service's function into a commonly understood format. So first we need to send the function call to a remote application. Our calling application or website creates the XML document that will represent the programmed function we want to execute. The XML is then transmitted over HTTP to the remote application and it can then be interpreted and understood by the remote application. The remote application then executes the function based on the XML formatting. Some examples of web service's methods are SOAP (Simple Object Access Protocol), UDDI (Universal Description, Discovery and Integration), WSDL (Web Services Description Language), XML-RPC (XML Remote Procedure Call), JSON (JavaScript Object Notation), JSON-RPC, REST (Representational State Transfer), and AMF (Action Message Format). We are not going to look at these interfaces in detail now. For now, it's helpful for us to understand that these protocols and platforms exist and that our Drupal site can provide web services to other applications via these multiple interfaces and platforms. Here's a diagram that outlines a simple web service request and response. This is a request sent from our Drupal site (client) over HTTP to an external server to request data. The data exists on the server (remote) in the form of a URI (Uniform Resource Identifier) item. The response is sent back to our Drupal site through XML. The REST protocol Let's look briefly at one web service protocol and technology, and define it. As mentioned before, there are many technologies you can use to implement web services. REST (Representational State Transfer) is one such technology. The reason REST is a preferred technology within the web development and Drupal community is due to its flexibility and standards. REST allows us to do the following when we initiate a web service using its protocol: Use a standard method such as XML as our message format Send the message over standard protocol such as HTTP Provide or connect to specific resources where each resource (image, document, page, and node) is given a unique resource identifier (a URI) We can take this concept and try it out on our Drupal site by writing some PHP code that makes an HTTP request to another web application resource. For example, we may want to make a call to a Picasa photo gallery and feed a select number and type of photos back to our Drupal site and display the photos in a Drupal node on our site. The request targets this specific resource by making a GET request to the URI of the resource. The application we are communicating with sends a response back to us in XML format. That XML can then be integrated into our Drupal site using a module, for example. The request might be made to a user's Flickr or Picasa photo gallery. The request gets returned to our Drupal site as XML and we parse this XML into our Drupal site and the user's photos or gallery then get displayed on our site. This is just one protocol example. Greg Hines of pingVision provides a good introductory resource on REST and Drupal in the document titled RESTful Web Services and Drupal. The document is available on pingVision's website as a PDF download from: http://pingvision.com/files/restful_web_services_and_drupal.pdf Standards compliance As discussed in the REST protocol's example, web services and Drupal's use of web services follow specific standards. In order to maintain as much interoperability and flexibility as possible, all of the protocols used respond for the most part using XML as the standard response mechanism and format. Additionally, all the communication between services, in our example between a client and a server, happens over HTTP (the standard web protocol). This is a uniform protocol that is used for transport and communication of the service. All transports take place uniformly using GET, POST, PUT, and DELETE requests, for example. The HTTP requests are stateless, meaning that the request over HTTP happens once at one given moment and is isolated from all other activated requests. So the request stands alone. If it succeeds, it gets a response. If it fails, it gets no response from the server or application it's communicating with. The request can be repeated an infinite number of times. Finally, all of the resources we try and access are those that we are sending to another application using a unique resource identifier (URI) to identify and define what they are. So images on our site have unique identifiers as well as those residing in another web application. Each of these unique identifiers allows for addresses or locations for each node or file in question. So each resource in a web service's communication has an address. Each resource has one URI and each address has one URI. Some examples of this would be the following locations on my Drupal site: http://variantcube.com/ http://variantcube.com/recommended-drupal-resources http://variantcube.com/node/68 http://variantcube.com/search/node/podcast http://variantcube.com/rss.xml Another reason we want to be standards compliant, when writing or working with web services, is for simplicity. We do not need any special tools to program web services as long as we follow these standards. We can use the web application modules and PHP, and stick to these coding standards and protocols. Why are web services useful? Web services are useful for a number of reasons, especially when it comes to Drupal and Drupal's relationship and interaction with other web content management systems and applications. The web has a huge number of web applications, so web developers and content developers can pass their content to the web browsers and make it available to the web visitors. This is why the Internet is useful to us. We can go to a website and view the content. Whenever we do that, we're looking at content that is proprietary to a specific web application. In Drupal, our content is in the form of nodes, for example. We may want to share these nodes with other websites that are non-Drupal, such as a Wordpress-powered site. Web services are useful because they present us with an architecture where a resource on a site (an image, textual content, such as a node ID or block ID, a video or audio file) is given a unique identifier. For example, in Drupal, every node has an ID. Every file you upload to a Drupal site also has a unique path to it. This is extremely useful since all applications share this common semantic standard. We name things similarly on all of our web applications. We can then leverage this by writing code in PHP, for example, the one that calls these resources. The application server that houses the resource then responds to our request using an XML document. Why use web services in Drupal? With web services, we can take our Drupal content and share this content with other web applications and, essentially, with the web at large. Our content is no longer just our content and it is not specific to our Drupal website. It can be shared and integrated. Drupal's codebase is PHP-based and many of the popular web applications being used today, including Wordpress, Joomla!, and Flickr, are also PHP-based. So we have a common programming language we can work with and use to integrate these applications. Here are some concrete examples. Perhaps your Human Resources Department wants to integrate its job postings and applications with another web application such as Monster.com. Web services can allow this to happen. Your office's payroll department may want to connect to its bank account in order to pass data from the payroll reports over to its bank reporting mechanism. Web services can allow this to happen. You may want to take all of the photos you upload to your Drupal site in image galleries built with the Views module, and take these photos and send them to Flickr so that they automatically show up in your Flickr account or on Flickr's public site. Web services can make this happen. This leads to another advantage of using web services with Drupal and why we would choose to use Drupal in the first place. Instead of having to upload our photos twice—once to our Drupal site and then repeating the procedure to our Flickr site—web services allows us to upload the images to our Drupal site once and then automatically send that data over to Flickr without having to upload one (or even a batch of images) again. It saves us time and speeds up the entire process of generating web-based content. Additionally, there may be applications we want to use in our Drupal site, for example applications where we want to consume content without having to code again. We can just reuse these applications using the web services protocols and get this application content into our Drupal site. So we can consume web services. Examples of this would be converting currency on our site, feeding weather reports and other weather data into our site, feeding natural disaster scientific data into our site from services that provide it, feeding language translation services, feeding music podcasts, and more. Instead of having to reprogram this type of content, we can grab it from another web application and show it automatically on our site using web services Simply put, this opens up a method of easily sharing data and content resources between applications and machines that are running on different platforms and architecture. We have opened up a gold mine of capabilities here because we can talk to applications that run different software from our Drupal site and on different computing platforms. How Drupal uses web services Drupal can use web services following any of the protocols mentioned earlier, including XML-RPC, REST, and SOAP. Drupal can consume web services by requesting data from other web applications using RSS and XML-formatted requests. As a web developer, you can write your own service code in Drupal using PHP. You can also use the Services module as well as other service-specific contributed modules to create these web service requests. In this next section, we're going to look at both these examples. First, we'll see how Drupal works as a service consumer, where basically it is a client requesting data from an external server. We'll also look at how Drupal can provide services using the Services module, RSS, AMFPHP, and XML-RPC. Drupal as a service consumer Let's outline some brief examples of how Drupal consumes content and data from other web applications, including Mollom, Flickr, and Facebook. You can configure your Drupal site to consume various web services by using contributed Drupal modules for each specific task or application you are trying to consume. Drupal can consume services from applications that will help your website prevent spam, integrate photos, integrate taxonomy and tags, and enhance your Drupal free tagging and autotagging abilities, and integrate with applications such as Facebook and Twitter.
Read more
  • 0
  • 0
  • 2988

article-image-portal-and-drag-and-drop-features-ext-gwt
Packt
29 Nov 2010
5 min read
Save for later

Portal and Drag-and-Drop Features of Ext GWT

Packt
29 Nov 2010
5 min read
  Ext GWT 2.0: Beginner's Guide Portlet class The Portlet class extends ContentPanel to provide a special type of panel that can be repositioned in the Viewport by the user with a Portal container. It may appear similar to a window in a desktop application. Creating a Portlet is similar to creating other containers. This code: Portlet portlet = new Portlet(); portlet.setHeight(150); portlet.setHeading("Example Portlet"); creates a Portlet like this: A Portlet can be excluded from being repositioned by pinning it using: portal.setPinned(true); Apart from that, a Portlet inherits all the features of a standard ContentPanel. The Portal class A Portal is a special container for Portlet components. In fact, it is a Container containing a collection of LayoutContainer components arranged using ColumnLayout. Each of those LayoutContainer components in turn is able to contain Portlet components, arranged using a RowLayout. Portal also supports dragging and dropping of Portlet components, both in terms of changing the row it is in within a column and the column within the Portal. When creating a Portal, we need to set the number of columns the Portal should create in the constructor. We also need to set the widths of each column before using the setColumnWidth method of the Portal. So to create a Portal with two columns, (one using 30 percent of the width and the second 70 percent) we would define it as follows: Portal portal = new Portal(2); portal.setColumnWidth(0, 0.3); portal.setColumnWidth(1, 0.7); We can then add a Portlet to each column like this: Portlet portlet1 = new Portlet(); portlet1.setHeight(150); portlet1.setHeading("Example Portlet 1"); portal.add(portlet1, 0); Portlet portlet2 = new Portlet(); portlet2.setHeight(150); portlet2.setHeading("Example Portlet 2"); portal.add(portlet2, 1); This will produce the following output: Both Portlet components can be dragged and dropped into different positions. The Portlet turns into a blue box while being dragged as shown in the following screenshot: A Portlet will automatically resize and ft into the column in which it is dropped, as seen in the next screenshot: ToolButton Like ContentPanel that Portlet extends, we can add ToolButton components to the header. These can be very useful for making a Portlet look and behave even more like windows in a desktop application. portlet.getHeader().addTool(new ToolButton("x-tool-minimize")); portlet.getHeader().addTool(new ToolButton("x-tool-maximize")); portlet.getHeader().addTool(new ToolButton("x-tool-close")); The output can be seen as shown in the following screenshot: At the moment, we are using ContentPanel components in our example application and laying them out using a BorderLayout. We shall now see that it does not take much to change the ContentPanel components into Portlet components and manage them using a Portal. Portlet components are ideally suited to being independent, self-contained user interface elements that respond to the data passed to them. Rather than tying them into a Portal directly, we can use the MVC components to cause the Portal to respond to the creation of a new Portlet to preserve that independence. Time for action – creating a Portal Controller and a Portlet View The first thing we need to do is add a new EventType to the existing AppEvents class named NewPortletCreated. We will fire this when we create a new Portlet. public static final EventType NewPortletCreated = new EventType(); Create a new class named PortalController that extends Controller. public class PortalController extends Controller { Create a new class named PortalView that extends View. public class PortalView extends View { Create a constructor that sets the Controller of the PortalView. public PortalView(PortalController portalController) { super(portalController); } Returning to PortalController, create a variable to hold the PortalView and override the initialize method to set the view. private PortalView portalView; @Override public void initialize() { super.initialize(); portalView = new PortalView(this); } Create a constructor that registers each EventType the PortalController should observe, specifically NewPortletCreated creation and Error. public PortalController() { registerEventTypes(AppEvents.NewPortletCreated ); registerEventTypes(AppEvents.Error); } Override the handleEvent method to forward any events to the View apart from errors which for the time being we will just log to the GWT log. @Override public void handleEvent(AppEvent event) { EventType eventType = event.getType(); if (eventType.equals(AppEvents.error)) { GWT.log("Error", (Throwable) event.getData()); } else { forwardToView(portalView, event); } } Returning to PortalView, create a new portal field consisting of a Portal component with two columns. private final Portal portal = new Portal(2); Override the initialize method to set the width of the two columns, the first to 30 percent of the width of the Portal and the second to 70 percent. @Override protected void initialize() { portal.setColumnWidth(0, 0.3); portal.setColumnWidth(1, 0.7); } Now create a Viewport, set the layout to FitLayout, add the Portal, and then add the Viewport to GWT's RootPanel. @Override protected void initialize() { portal.setColumnWidth(0, 0.3); portal.setColumnWidth(1, 0.7); final Viewport viewport = new Viewport(); viewport.setLayout(new FitLayout()); viewport.add(portal); RootPanel.get().add(viewport); } We also need to implement the handleEvent method of the View. For now, we will catch the NewPortletCreated event, but we will not do anything with it yet. @Override protected void handleEvent(AppEvent event) { EventType eventType = event.getType(); if (eventType.equals(AppEvents.NewPortletCreated )) { } } Finally, go to the onModuleLoad method of the EntryPoint RSSReader class and instead of creating an AppController, create a PortalController, and remove the line that forwards an Init AppEvent, as we will not be using it. The onModuleLoad method will now look like this: public void onModuleLoad() { final FeedServiceAsync feedService = GWT.create(FeedService.class); Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); Dispatcher dispatcher = Dispatcher.get(); dispatcher.addController(new PortalController()); } What just happened? We created the basic framework for a Portal layout of our application. However, if we started it now, we would just get a blank screen. What we need to do is add Portlet components. The actual Portlet components are not too complicated. They will just act as wrappers.
Read more
  • 0
  • 0
  • 1438
article-image-replication-alert-monitor-monitoring-management
Packt
26 Nov 2010
10 min read
Save for later

Replication Alert Monitor: Monitoring Management

Packt
26 Nov 2010
10 min read
IBM InfoSphere Replication Server and Data Event Publisher Design, implement, and monitor a successful Q replication and Event Publishing project Covers the toolsets needed to implement a successful Q replication project Aimed at the Linux, Unix, and Windows operating systems, with many concepts common to z/OS as well A chapter dedicated exclusively to WebSphere MQ for the DB2 DBA Detailed step-by-step instructions for 13 Q replication scenarios with troubleshooting and monitoring tips Written in a conversational and easy to follow manner We use the ASNMCMD command to manage a running RAM monitor. When we issue the asnmcmd command, we need to specify: The monitor server name The monitor name And then one of the following keywords: chgparms <parameters>, reinit, status, stop, qryparms, suspend, resume. Where <parameters> can be: monitor_interval=<n>, autoprune= [yn], alert_prune_limit=<n>, trace_limit=<n>, max_notifications_per_alert=<n>, max_notifications_minutes=<n>| We can issue this command from a different screen from which the monitor was started. Checking which monitors are active To check which monitors are active, we can use the ASNMCMD command with the STATUS parameter. So to check the status of the monac1 monitor on mondb, we would issue: $ asnmcmd MONITOR_SERVER=mondb MONITOR_QUAL=monac1 STATUS ASN0600I "AsnMcmd" : "" : "Initial" : Program "asnmcmd 9.1.0" is starting. ASN0520I "AsnMcmd" : "MONAC1" : "Initial" : The STATUS command response: "HoldLThread" thread is in the "is resting" state. ASN0520I "AsnMcmd" : "MONAC1" : "Initial" : The STATUS command response: "AdminThread" thread is in the "is resting" state. ASN0520I "AsnMcmd" : "MONAC1" : "Initial" : The STATUS command response: "WorkerThread" thread is in the "is resting" state. If there is nothing running, then we get the following messages: ASN0600I "AsnMcmd" : "" : "Initial" : Program "asnmcmd 9.1.0" is starting. ASN0506E "AsnMcmd" : "ASN" : "Initial" : The command was not processed. The "Monitor" program is presumed down. Note that there is a slight delay of a few seconds between the ASN0600I message and the ASN0506E message. We can check when a monitor last ran using the following query: $ db2 "SELECT SUBSTR(monitor_qual,1,10) AS monqual, last_monitor_time, start_monitor_time, end_monitor_time, lastrun, lastsuccess, status FROM asn.ibmsnap_monservers" MONQUAL LAST_MONITOR_TIME START_MONITOR_TIME MONAC1 2007-03-16-10.18.57.750000 2007-03-16-10.18.57.750000 END_MONITOR_TIME 2007-03-16-10.18.59.765000 LASTRUN LASTSUCCESS STATUS 2007-03-16-10.18.57.750000 2007-03-16-10.18.57.750000 0 Changing or reinitializing a monitor If we change any of the following while the monitor is running such as contact information, alert conditions, or parameter values: $ asnmcmd MONITOR_SERVER=mondb MONITOR_QUAL=monac1 CHGPARMS MONITOR_INTERVAL=10 Then we do not have to stop and start the monitor, we can just reinitialize it as follows: $ asnmcmd MONITOR_SERVER=mondb MONITOR_QUAL=monac1 REINIT Stopping a monitor To stop a monitor called monac1, we would issue the following command: $ asnmcmd MONITOR_SERVER=mondb MONITOR_QUAL=monac1 STOP Suspending or resuming a monitor We cannot suspend a monitor from the Replication Center, we can only use ASNCLP scripts. We can stop checking Q Capture and Q Apply for all defined alert conditions using the ASNMCMD SUSPEND command. When we want to resume monitoring again, then we issue the ASNMCMD RESUME command. Note that using the ANSMCMD command is an all or nothing approach. The SUSPEND option will suspend all monitoring qualifiers. So what happens if we just want to suspend monitoring one monitored sever (DB2A or DB2B)? In this case, we would have to create a monitor suspension. We can suspend a monitor once only or on a repeatable basis. If we want to suspend a monitor on a repeatable basis, then it's best to first create a suspension template and then create a monitor suspension. If we want to suspend a monitor server once only, then we just need to create a monitor suspension. All dates and times for monitor suspensions are based on the clock at the system where the monitor is running (MONDB). The time format is HH:MM:SS and the date format is YYYY-MM-DD. The ASNCLP command to create a monitor suspension template is: CREATE MONITOR SUSPENSION TEMPLATE <template_name> START TIME <starting_time> REPEATS occ-clause Where occ_clause can be: DAILY FOR DURATION <n> [HOURS | MINUTES] Or: WEEKLY DAY OF WEEK <day> FOR DURATION <n> [HOURS/MINUTES/DAYS] Where <day> can be Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, or Saturday. The ASNCLP command to create a monitor suspension is: CREATE MONITOR SUSPENSION <name> [FOR SERVER <server_name> | ALIAS <server_alias>] STARTING DATE <date> [USING TEMPLATE <template_name> | STARTING TIME <starting_time>] ENDING DATE <date> ENDING TIME <ending_time> So when should we use templates? We should use them: If we want to suspend more than one monitor at the same date or time. If we want to suspend a monitor on anything but a daily basis. Note that, we can specify a day of week when we create a monitor suspension template, which is not possible in the monitor suspension definition. There are eight ASNCLP commands which deal with monitor suspensions: ASNCLP command:Description:LIST MONITOR SUSPENSIONGenerates a list of suspensions on a monitor control server.ALTER MONITOR SUSPENSIONAllows us to change the following properties of a monitor suspension: • The template that is used • The start or end date for using a template • The start or end date for suspending the monitor program one timeDROP MONITOR SUSPENSIONDeletes a monitor suspension from the monitor control tables.LIST MONITOR SUSPENSION TEMPLATEGenerates a list of monitor suspension templates on a monitor control server.ALTER MONITOR SUSPENSION TEMPLATEAllows us to change the frequency and length of monitor suspensions as defined in a suspension template.DROP MONITOR SUSPENSION TEMPLATEDeletes a monitor suspension template from the monitor control tables.CREATE MONITOR SUSPENSION TEMPLATECreates a monitor suspension template.CREATE MONITOR SUSPENSIONCreates a monitor suspension. Even though we want to monitor Q replication, we need to specify SQL replication in the ASNCLP SESSION line. So let's look at the ASNCLP command to create a monitor suspension template called LUNCH which starts daily at 12:00 and lasts for one hour: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; CREATE MONITOR SUSPENSION TEMPLATE lunch START TIME 12:00:00 REPEATS DAILY FOR DURATION 1 HOURS; In the preceding command, we have not specified a server on which to apply the suspension—we have only defined the monitor server where to store the metadata. We have also not specified a start and end date—only a start and end time. Once we have created a monitor suspension template, we can define a monitor suspension for a specific server (that is, source or target) and a specific date range, which uses the template we defined previously. ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; SET SERVER TARGET TO DB db2b; CREATE MONITOR SUSPENSION NAME s1 FOR SERVER db2b STARTING DATE 2007-03-20 USING TEMPLATE lunch ENDING DATE 2007-12-31; In the above monitor suspension code, we do not have to specify a time value, because the time value is specified in the template definition. Note that now we have to specify a server for the template to work against. We can also define a monitor suspension without making reference to a template by specifying all the information that we need (this would be the once only processing model): CREATE MONITOR SUSPENSION NAME s2 FOR SERVER db2a STARTING DATE 2007-03-20 STARTING TIME 12:00:00 ENDING DATE 2007-12-31 ENDING TIME 13:00:00 In the above monitor suspension definition we have not specified a template, so we have included the start and end dates and times. We can list our monitor suspension templates and monitor suspensions using the following ASNCLP command: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; LIST MONITOR SUSPENSION TEMPLATE; LIST MONITOR SUSPENSION; This produces the following output: ==== CMD: LIST MONITOR SUSPENSION TEMPLATE; ==== TEMPLATE NAME START TIME FREQUENCY DURATION UNITS ------------------ ---------- --------- -------- ------- LUNCH 12:00:00 SUNDAY 1.0 HOURS 1 Template(s) found. ==== CMD: LIST MONITOR SUSPENSION; ==== SUSPENSION NAME SERVER NAME TEMPLATE NAME FREQUENCY DURATION ------------------ ------------------ ------------------ --------- S1 TARGET LUNCH SUNDAY 1.0 SUSPENDUNITS FIRST SUSPENSION STOP -------- ------- ------------------- ------------------- HOURS 2007-03-20-12:00:00 2007-12-31-00:00:00 1 Suspension(s) found. We can see the monitor suspension template called LUNCH, which we created and the monitor suspension S1. We can alter a monitor suspension template by using the ASNCLP command ALTER MONITOR SUSPENSION. Suppose we want to change the suspension day from Sunday to Monday, then the ASNCLP command would be: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; ALTER MONITOR SUSPENSION TEMPLATE lunch; LIST MONITOR SUSPENSION TEMPLATE; To drop a monitor suspension, we would use the DROP MONITOR SUSPENSION ASNCLP command: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; SET SERVER TARGET TO DB db2b; DROP MONITOR SUSPENSION s1; Dropping a monitor suspension involves running the following SQL: DELETE FROM ASN.IBMSNAP_SUSPENDS WHERE SUSPENSION_NAME = 'S1' To drop a monitor suspension template, we would use the DROP MONITOR SUSPENSION TEMPLATE ASNCLP command: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; DROP MONITOR SUSPENSION TEMPLATE lunch; Dropping a monitor suspension template involves running the following SQL: DELETE FROM ASN.IBMSNAP_TEMPLATES WHERE TEMPLATE_NAME = 'LUNCH' So let's look at an example in a bidirectional scenario as shown in the following diagram: In this example, we have created four monitors to monitor Q Capture and Q Apply on each of the two servers. The monitor_qual are MONAC1, MONAA1, MONBA1, and MONBC1. The monitor_server is MONDB. If we want to perform maintenance on the DB2B database every Sunday afternoon at 16:00 for one hour, we would create a monitor suspension template as follows: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; CREATE MONITOR SUSPENSION TEMPLATE tmaintbaft START TIME 16:00:00 REPEATS WEEKLY DAY OF WEEK SUNDAY FOR DURATION 1 HOURS; And then, we would create a monitor suspension as follows: ASNCLP SESSION SET TO SQL REPLICATION; SET RUN SCRIPT NOW STOP ON SQL ERROR ON; SET SERVER MONITOR TO DB mondb; SET SERVER TARGET TO DB db2b; CREATE MONITOR SUSPENSION NAME maintbaft FOR SERVER DB2B STARTING DATE 2007-03-20 USING TEMPLATE tmaintbaft ENDING DATE 2007-12-31; The ibmsnap_alerts table The IBMSNAP_ALERTS table contains a record of all the alerts issued by the Replication Alert Monitor. The table records what alert condition occurred, at which server, and when they were detected. Some common errors in the ALERT_CODE column are: ASN5153W MONITOR "<monitor_qualifier>". The latency exceeds the threshold value for program "<program_name>". The server is "<server_name>". The schema is "<schema>". The latency is "<latency>" seconds. The threshold is "<threshold>" seconds. ASN5157W MONITOR "<monitor_qualifier>". The Q subscription "<subscription_name>" is inactive. The server is "<server_name>". The schema is "<schema>". State information: "<stateinfo>". Summary In this article we described the Replication Alert Monitor and how to monitor the Q replication setup. Further resources on this subject: Lotus Notes Domino 8: Upgrader's Guide [Book] Q Replication Components in IBM Replication Server [Article] IBM WebSphere MQ commands [Article] WebSphere MQ Sample Programs [Article] Q Subscription Maintenance in IBM Infosphere [Article]
Read more
  • 0
  • 0
  • 1533

article-image-user-extensions-and-add-ons-selenium-10-testing-tools
Packt
26 Nov 2010
6 min read
Save for later

User Extensions and Add-ons in Selenium 1.0 Testing Tools

Packt
26 Nov 2010
6 min read
Important preliminary points If you are creating an extension that can be used by all, make sure that it is stored in a central place, like a version control system. This will prevent any potential issues in the future when others in your team start to use it. User extensions Imagine that you wanted to use a snippet of code that is used in a number of different tests. You could use: type | locator | javascript{ .... } However, if you had a bug in the JavaScript you would need to go through all the tests that reused this snippet of code. This, as we know from software development, is not good practice and is normally corrected with a refactoring of the code. In Selenium, we can create our own function that can then be used throughout the tests. User extensions are stored in a separate file that we will tell Selenium IDE or Selenium RC to use. Inside there the new function will be written in JavaScript. Because Selenium's core is developed in JavaScript, creating an extension follows the standard rules for prototypal languages. To create an extension, we create a function in the following design pattern. Selenium.prototype.doFunctionName = function(){ . . . } The "do" in front of the function name tells Selenium that this function can be called as a command for a step instead of an internal or private function. Now that we understand this, let's see this in action. Time for action – installing a user extension Now that you have a need for a user extension, let's have a look at installing an extension into Selenium IDE. This will make sure that we can use these functions in future Time for action sections throughout this article. Open your favorite text editor. Create an empty method with the following text: Selenium.prototype.doNothing = function(){ . . . } Start Selenium IDE. Click on the Options menu and then click on Options. Place the path of the user-extension.js file in the textbox labeled Selenium IDE extensions. Click on OK. Restart Selenium IDE. Start typing in the Command textbox and your new command will be available, as seen in the next screenshot: What just happened? We have just seen how to create our first basic extension command and how to get this going in Selenium IDE. You will notice that you had to restart Selenium IDE for the changes to take effect. Selenium has a process that finds all the command functions available to it when it starts up, and does a few things to it to make sure that Selenium can use them without any issues. Now that we understand how to create and install an extension command let's see what else we can do with it. In the next Time for action, we are going to have a look at creating a randomizer command that will store the result in a variable that we can use later in the test. Time for action – using Selenium variables in extensions Imagine that you are testing something that requires some form of random number entered into a textbox. You have a number of tests that require you to create a random number for the test so you can decide that you are going to create a user extension and then store the result in a variable. To do this we will need to pass in arguments to our function that we saw earlier. The value in the target box will be passed in as the first argument and the value textbox will be the second argument. We will use this in a number of different examples throughout this article. Let's now create this extension. Open your favorite text editor and open the user-extension.js file you created earlier. We are going to create a function called storeRandom. The function will look like the following: Selenium.prototype.doStoreRandom = function(variableName){ random = Math.floor(Math.random()*10000000); storedVars[variableName] = random; } Save the file. Restart Selenium IDE. Create a new step with storeRandom and the variable that will hold the value will be called random. Create a step to echo the value in the random variable. What just happened? In the previous example, we saw how we can create an extension function that allows us to use variables that can be used throughout the rest of the test. It uses the storedVars dictionary that we saw in the previous chapter. As everything that comes from the Selenium IDE is interpreted as a string, we just needed to put the variable as the key in storedVars. It is then translated and will look like storedVars['random'] so that we can use it later. As with normal Selenium commands, if you run the command a number of times, it will overwrite the value that is stored within that variable, as we can see in the previous screenshot. Now that we know how to create an extension command that computes something and then stores the results in a variable, let's have a look at using that information with a locator. Time for action – using locators in extensions Imagine that you need to calculate today's date and then type that into a textbox. To do that you can use the type | locator | javascript{...} format, but sometimes it's just neater to have a command that looks like typeTodaysDate | locator. We do this by creating an extension and then calling the relevant Selenium command in the same way that we are creating our functions. To tell it to type in a locator, use: this.doType(locator,text); The this in front of the command text is to make sure that it used the doType function inside of the Selenium object and not one that may be in scope from the user extensions. Let's see this in action: Use your favorite text editor to edit the user extensions that you were using in the previous examples. Create a new function called doTypeTodaysDate with the following snippet: Selenium.prototype.doTypeTodaysDate = function(locator){ var dates = new Date(); var day = dates.getDate(); if (day < 10){ day = '0' + day; } month = dates.getMonth() + 1; if (month < 10){ month = '0' + month; } var year = dates.getFullYear(); var prettyDay = day + '/' + month + '/' + year; this.doType(locator, prettyDay); } Save the file and restart Selenium IDE. Create a step in a test to type this in a textbox. Run your script. It should look similar to the next screenshot: What just happened? We have just seen that we can create extension commands that use locators. This means that we can create commands to simplify tests as in the previous example where we created our own Type command to always type today's date in the dd/mm/yyyy format. We also saw that we can call other commands from within our new command by calling its original function in Selenium. The original function has do in front of it. Now that we have seen how we can use basic locators and variables, let's have a look at how we can access the page using browserbot from within an extension method.
Read more
  • 0
  • 0
  • 4210