Tuesday, July 21, 2015

Developing website navigational section

Recently, one of our template was taking too much time to render left navigation section on a webpage, which displays all the pages under certain parent page upto level 3.

For reference, you can consider the following case:

Parent Page
- 1
  - 1.1
     - 1.1.1
     - 1.1.2
     - 1.1.3
  - 1.2
     - 1.2.1
  - 1.3
     - 1.3.1
     - 1.3.2
     - 1.3.3
- 2
  - 2.1
  - 2.2
     - 2.1.1
     - 2.1.2
     - 2.1.3
  - 2.3
- 3
  - 3.1
  - 3.2
     - 3.2.1
     - 3.2.2
     - 3.2.3
  - 3.3
     - 3.3.1
     - 3.3.2
and so on...

where, blue ones are level 1 page assets (placed under parent page), dark purple ones are level 2 page assets (obviously placed under level 1 page asset) and orange ones are level 3 page assets within FatWire/Oracle WebCenter Sites.

Similar configuration is present for other sites too as this project is configured for many countries (meaning many sites within OracleWCS and 2 or more dimensions per site)

I am discussing in detail on this topic so whomsoever reads this doesn't make the same mistake which our previous developer did.

Functionality required:
1. To render a particular page with left menu section listing page assets as shown above
2. The current page should be highlighted in left menu
3. On removing/adding page asset below Parent page asset within WCS at any level should reflect in left menu on next page load
4. Same functionality should be present for other sites (if there are multi-lingual sites)
5. And obviously dependencies and caching should be taken care automatically (no publishing of assets or flushing dependencies/caches)

Let me first discuss on how usually developers code navigational elements. Considering the above example i.e. to render left menu; one can think of a logic like: to call certain Template/SiteEntry (say LeftNav, assettype-Page, usage: Element is used within HTML page, cached) by adding following parameters to cache criteria: c, cid, site, locale and other common parameters (if required); which will be called from Layout template.
Now to build left navigation from top-to-bottom, one needs to know parent page id. So there are 2 ways, either you add parent page id to MAP tab of Template/CSElement for every site or load the current page's siteplan and find the parent page by traversing loaded siteplan (One can argue that same can be achieved if there is basic asset which stores only key-value pairs and parent page's asset id can be stored per site but again there is risk of duplicity of asset name, not to mention it is always prone to human-errors).

First case (getting value from MAP) is generic. For latter case, one can do the following:
<asset:load> - Load current asset from the param - cid
<asset:getsitenode> - Get currently loaded asset's node id in SitePlan Tree
<siteplan:load> - Load siteplan by passing the node id obtained above
<siteplan:notepath> - Get list of ancestors
Last entry in the IList will fetch the parent page asset's asset id and node id

Once you have parent page, start by loading the siteplan from this parent page id or node id and build the left navigation:
<asset:load> - Load current parent page asset
<asset:getsidenode> - Get node id of parent page
<siteplan:load> - Load the site plan
<siteplan:children> - Get first level of children
and so on... repeating same process till required level is reached.

Drawbacks of using above logic:
1. If the Template/SiteEntry for LeftNav is cached, there would be many left menu pagelets generated resulting in non-desirable unnecessary over-caching of a page section. For e.g. if you have 50 page assets under parent page asset, then logically you will have 50 cached enteries for left menu per site. And suppose, there are 10 sites configured (multi-lingual), then total number of cached pagelets = 500. Futhermore, consider this -> Even if there are 2 dimensions configured per site within your WebCenter Sites/FatWire project, then there would be 1000 cached pagelets only for left navigation/menu!! At last, one would think that if there are so many cached enteries, developer's end up leaving LeftNav Template/SiteEntry uncached.

2. If there is only site within project, then retrieving & handling value from MAP or using basic asset should not be a problem, but if there are more than 10 sites present in your project, then it may or may not problem; again depends on project design. If you are using basic asset to store key-value, one needs to always make sure if correct page asset id is added as value or not; which is always prone to human-errors. Hence, one can't escape from loading the current page siteplan and then finding the parent page's asset id or node id. There might be some other simple way too.

3. Last but not the least is the issue with loading siteplan for every page asset as you loop through IList from top-to-bottom. Hence, if considered 50 page assets per site under Parent Page, then 51 siteplan loads!! (including parent page) Furthermore, if 10 sites are present within a project, 510 siteplan loads!!  Also, one doesn't know when to stop loading children pages until it is cleared upto which level page assets are to be searched for. Some page asset may or may not have child page assets but still siteplan:children tag is loaded while looping from top-to-bottom (also have to add extra if-else condition for child pages existence). This unnecessary loads using siteplan:load tag will definitely affect page responsiveness for every new page request. Developers should be really good in developing such elements and the depth needs to be fixed before implementation too. Additionally, one not only needs page id, but also require page attributes or association and generate url to show in left navigation. Also, you will add some condition to highlight the current page in left navigation which may or may not be complex (depends on HTML prototype). Considering all this, if you check the load time using <time:set> and <time:get> tags, that would be high compared to other elements.

In your project, if this is how navigation/menu is built; then this is really a bad implementation on developer's part and requires inspection.

So how to avoid this?
Coding such scenario is not much of challenge until you know the proper way to implement it. If you check FirstSiteII, similar case is present: Loading product categories in left navigation on product page but it was using searchstate tags whereas in this case, siteplan tags are used.
I am listing few suggestions which can be implemented to increase page performance and reduce unnecessary generation of pagelets.

1. Pagelet caching: Considering that every page request goes through layout (or wrapper+Layout) template and each layout template calls various parts of your webpages - header, footer, navigation and body. For our case, layout template will call an extra pagelet element - LeftNav. Create LeftNav template (same configuration as before but make it as uncached). Create another template - LeftNavChildren (or whatever name) which will be called from LeftNav template; having same configuration (cached, for page asset and usage as HTML pagelet, include same params). When a page having left navigation is requested,  LeftNav (uncached) will just calculate the parent page's asset id or node id from the current page cid's value and call LeftNavChildren template by passing this calculated parent page's asset id. As we will always get same parent's page asset id from LeftNav template for a site for every page asset in left navigation, there would be only one cached LeftNavChildren per site per locale. Hence, considering same scenario as 1st point in drawbacks section, we will now have only 1 pagelet * 2 dimensions * 10 sites = 20 cached pagelets!! (Awesome, isn't it??). Hence, in one word, try to cache on parent's page cid not on every page asset in left navigation.

2. Loading parent page: There is not much to say for this case as its very obvious - if more sites are present within project, then its better to use tags to get parent page id or else use MAP tab or some other way.

3. Loading child pages: Use siteplan:listpages tag rather than loading child page assets using siteplan:children tag. This tag has ability to search all the child page assets in one go; given that you provide parent page node id and the level of depth upto which search is required. Hence, no messy code. Following few lines will completely fetch siteplan from top-to-bottom for left navigation.
<asset:load> - Load current parent page asset
<asset:getsidenode> - Get node id of parent page
<siteplan:load> - Load the site plan
<siteplan:listpages> - Get list of child page assets of this parent page upto whatever level of depth required.

Resulting IList contains list of child page assets containing important columns: node level (level 1, 2, 3...), id (page asset id) and hasChildren (page asset has children or not). Hence, if coded efficiently, LeftNav execution time can be reduced drastically. Here is a sample element which can load any siteplan by providing page id and level of depth to search & shows tree like navigation.

4. Highlighting current page: As we are not passing current page id, one may ask how to highlight current page in left navigation? Well, its very simple. By Comparing URL or part of URL generated in left navigation with the current page URL (as URL in left navigation for the current page will be same as the URL in the browser, not to mention that URLs will be unique for a site). Hence, a little knowledge on JavaScript/jQuery can do the trick.

There might be few more other tweaks which I may have missed but these are the essentials to know as every project has requirement to develop navigational section like footer, left navigation, breadcrumb, top navigation or sitemap. I really hope this make things clear on developing navigational part of a website and helps many FatWire/Oracle WCS developers.

Furthermore, this article is a must read for any WCS developer.

Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Friday, July 17, 2015

Dump variables

If you want to know what all variables are present on a particular webpage, you can run the following code to find all the available variables.


If you want to know what are columns and values present in an IList


Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Friday, July 10, 2015

Adding constraint on existing asset list

In one of my project, I faced dire situation where in I had to add search condition on existing attribute (say A1) of type asset on already searched assetlist.
As we were using lucene search, to search against this attribute: A1, default way will be to add one filedcopier filter, so that it can copy id from custom attribute, A1 to some other custom attribute & then should add the condition for it in search element. Basically, I had to show results by previous conditions + search on A1's attribute value (if exists). For e.g. Previously, if I got 500 results on searching, I should now get results by searching against previous conditions + search results from searching against custom attribute, A1; basically adding one more constraint on searching assets.

Sounds straightforward and easy, right?? But no, clients will be clients. Issue with adding filter is that you have to edit and save all the assets so that filter is executed. But by doing so, updateddate field value's changes and which client did not wanted that to happen at all for auditing purposes. Damn!!

So, basically now I had only one option i.e. to add one if condition while iterating through previous results. Meaning i would have to load each asset, get its attribute: A1 value and check if A1 attribute's value exists for that assets or not & matches with desired value or not. Obviously desired result will be generated but it would be worst non-desirable algorithm.

Hence, I started looking for Tags and APIs to avoid the such bad scenario. And to my luck, there are 2 tags which does what was required for my case - <assetset:setsearchedassets> and <assetset:setlistedassets>

<assetset:setlistedassets> tag can build assetset by consuming assetlist (as input list) but should have columns: assetid and assettype. Even if you don't have this columns within your IList, you can build your IList using <listobject> tags with columns: assetid and assettype

Once you have  assetset, it becomes straightforward to add constraint by creating searchstate and passing this constraint to <assetset:setsearchedassets> tag

Overall code would become like following (assetlist is input list with columns: assetid and assettype):

<assetset:setlistedassets name='thisassetset' assets='assetlist' deptype='exists'/>
<searchstate:create name="ss" op="or"/>
<listobject:create name="lo" columns="value" />
<listobject:addrow name="lo">
    <listobject:argument name="value" value="[some value]"/>
</listobject:addrow>
<listobject:addrow name="lo">
    <listobject:argument name="value" value="[some other value]"/>
</listobject:addrow>
<listobject:tolist name="lo" listvarname="matchlist"/>
<searchstate:addstandardconstraint name="ss" attribute="A1" list="matchlist" immediateonly="true" typename="[Attribute type name]" bucket="filterBucket"/>
<assetset:setsearchedassets name="thisassetset" constraint="ss" assettypes="Content_C" site="
    <%= siteId%>"/>
<assetset:getassetlist name="thisassetset" listvarname="assetlist"/>

Voila!! Now, this output list - assetlist returned is desired filtered list with criteria passed.

Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Tuesday, July 7, 2015

Implementing pagination on search results

I have been searching an easy way to implement pagination which requires no jQuery/JavaScript or java class implementation. And to my surprise, I found one element - Search.jsp which was present with product installation itself!! It's located at OpenMarket/Xcelerate/Search/Search.jsp which gave me basic idea on how pagination can be implemented with ease within WCS or FatWire.

Requirements:
1. Your search should be implemented via some search engine like lucene or endeca as they have ability to get results by setting starting index and max results. I have used by default lucene search which is installed out-of-the-box with JSK.
2. Knowledge on implementing forms within WCS. Check details here.
3. Knowledge on implementing lucene search. Check details here.

Procedure:
1. Basic idea is to generate page url with some params which lets your application know what results to show (in other words its just passing different params to your page asset templates). Generate URL same as which you generated for search button - Use <render:gettemplateurl> - Either you get vanity url if configured else you will get url by your assembler or generic long url

2. Append all the input params as query strings - Just append all the input params which were passed to search the current page. You might want to consider using packedargs if many parameters are required to be passed. Use tag: <render:unpackarg> and <render:packargs>

3. Update lucene search logic to search assets by setting start and end index - Simply add the following lines:

int startIndex = (null == pageNum || pageNum.length() == 0 ) ? 0 : (Integer.parseInt( pageNum )-1) * 10;
int endIndex = (null == pageNum || pageNum.length() == 0 ) ? 10 : (Integer.parseInt( pageNum ) * 10);
finalQuery.setStartIndex( startIndex );
finalQuery.setMaxResults( 10 );

where, pageNum = page number like 1,2... if null then just pass 0 as starting index and 10 as endIndex. I have considered that I would be showing 10 results per page. By adding this condition, lucene fetches only those results which are asked for.

4. Atlast, a simple logic to generate pagination

if(numOfArticles>10){%>   
    <ul id="pagination" style="padding-right: 50%;padding-left: 50%;display:inline;">   
    <% int pageCount = 0;   
     for(int p=0; p<numOfArticles; ) {   
        pageCount++;   
        String url = ics.GetVar("actURL")+"?keyword="+keyvalue+"&n="+pageCount;    
       if(Utilities.goodString(pageNum) && Integer.valueOf(pageNum) == pageCount ||       !Utilities.goodString(pageNum) && pageCount==1){   
        %><li style="margin: 0 10px;display:inline;"><a style="font-size: large;"><%= pageCount       %></a></li><%   
        } else {   
           %><li style="margin: 0 10px;display:inline;"><a style="font-size: larger;text-      decoration:underline;" href="<%= url %>"><%= pageCount %></a></li><%   
        }   
        p = p + 10;%>   
     <%} %>   
     </ul>   
<%} 

where, numOfArticles = total number of assets found on search
actURL = search results page url
pageNum = pageNumber like 1,2... which will be passed to URL for all the pagination links
rest of code is simply beautification.

If you have followed instructions mentioned in the posts for Satellite form and lucene, then building pagination should not be tough. 
I have developed and shared one sample JSP element which works with latest 11g JSK + Vanity URL configured + patch2 applied. Make changes in template according to your installation and configuration.

Add the param - "n" to cache-criteria to your previous SearchLayout.jsp and the replace code with the new pagination based search results - SearchLayoutPagination.jsp. Your page should look like following:




Note: There are differences in rendering forms for latest 11g and old versions, do check out how to work with them before trying this example.  

Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Friday, July 3, 2015

A simple ajax call

Often there is a requirement to populate results on basis of jquery/js + ajax call to populate data on a web page.

A simple use case:
Suppose you have following hierarchy for your flex parent

Main Parent
 - Parent1
   -- SubParent 11
       -- SubSubParent111
       -- SubSubParent112
  -- SubParent 12
       -- SubSubParent121
       -- SubSubParent122
 - Parent 2
   -- SubParent 2
       -- SubSubParent1
.. and so on

and on your webpage you would like show parents and subparents on basis of  user selection on dropdown. For eg: There are 3 dropdowns present for parent, subparent and subsubparent, if first parent is selected as "Parent1", you have to populate 2nd dropdown with "SubParent11" and "SubParent12" and similarly on selection of subparent, have to show corresponding subsubparent dropdown without page load. In such scenario, you can make use of ajax call to populate parents dynamically.

A simple procedure can be:

1. Create one Template or SiteEntry/CSElement and add required params in cache criteria, in our case only one argument would be "parent". If template, make its usage as "Can be called externally via browser"
2. A sample code from calling Template/CSElement can be like following:

//within script tag for localhost can be:
var url = "http://localhost:8080/cs/ContentServer?pagename=GetParent";var selectedParam;
//On parent dropdown selection selectedParam = "&parent="+$(".parent").val();
//On subparent dropdown selection selectedParam = "&parent="+$(".subparent").val();
url = url+selectedParam;
$.ajax({
url: url
}).done(function() {
// Get the output from your Template/CSElement  
// and populate corresponding child parents
});


3. Your called element which you created in 1st step, should output values in either JSON format or XML format or simply text or whatever, its upto you/your HTML.

Note: Further optimization can be done like only one call is sufficient to populate your dropdowns, etc, which depends on your knowledge of JavaScript/Jquery.

Thus, you can call SiteEntry or Template to dynamically inject data from WCS/FatWire on your webpage.

Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Sample code for insite editing

1. How to code insite edit for image attribute (BLOB) ?

a. For Flex asset - blob attribute

<insite:edit field="largeThumbnail" assetid='<%=ics.GetVar("imageId")%>' assettype="AVIImage" list="largeThumbnail" column="value" > 
        <img class="photo left" src='<%=ics.GetVar("imageURL")%>' />
</insite:edit>

b. For Basic asset - blob attribute (url field)

<render:getbloburl
        c='<%=ics.GetVar("c") %>'
        cid='<%=ics.GetVar("cid") %>'
        asset="<asset instance name>"
        field="<asset field name>"
        outstr="theURL"/>
 
<insite:edit field="
<asset field name>" variable="theURL" assetid='<%=ics.GetVar("cid") %>' assettype='<%=ics.GetVar("c") %>' editor="upload">
    <img src='<%=ics.GetVar("theURL") %>'/>
</insite:edit>

2. How to code insite edit for recommendation asset?

<commercontext:getrecommendations collectionname="[RECO NAME]" listvarname="recoList" />
<insite:slotlist slotname="RecommendationLink" assetid='<%=ics.GetVar("recoId")%>' assettype="AdvCols" field="Manualrecs">
<ics:listloop listname="assetlist">   
        <ics:listget listname="assetlist" fieldname="assetid" output="Id"/>   
        <ics:listget listname="assetlist" fieldname="assettype" output="Type"/> 
                <insite:calltemplate tname="[Template Name]" c='<%=ics.GetVar("Type")%>'     cid='<%=ics.GetVar("Id")%>'/>
        </ics:listloop>
</ics:slotlist>

Note: Above code is applicable for only static list of recommendation.

3. How to limit characters for insite edit field?

<insite:edit variable="title" field="titleparams="{maxLength:20}"/>

4. How to code insite edit for CKEditor?

<insite:edit field="body"
              value="${asset.body}"
              editor="ckeditor"
              params="{noValueIndicator: 'Enter body ', width: '600px', height: '500px', toolbar: 'SITES', enableEmbeddedLinks:'1'}"/>

You can also create your own toolbar and add your toolbar name in params attribute in above code.

General instructions:
1. Add clegal param to restrict users from adding other asset type
2. Add cssstyle param for using any particular css file
3. Make use of <insite:ifedit> tag when extra slots/additional js or css are required to be loaded
4. Slot data gets lost when using CSDT+eclipse after editing template: Populate slots in CS and then sync with eclipse. If slots are changed, before coding for template in eclipse, first re-sync and then make changes.

Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Sample code for auditing tasks

Sometimes we need to check info on certain attribute, content, templates, etc. for their existence or shared/unshared  or status, etc. For such cases, you can write your own utility to do batch audit operations. I am listing sample code for few cases:

1. Any asset of a particular asset type with a name exists or not?

2. If a particular asset is present in site or not?
3. Check all variables of a particular asset
Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

A simple code compare functionality

One of the most important aspect of any development cycle is deployment and while deployment, it is very important to note the changes don...