Friday, December 25, 2015

Adding unbookmark context menu

Authors are always trained to use bookmark function while creating or updating assets for associating assets to other asset in an attribute or association. Usually authors keep on bookmarking assets in Contributor UI for a site as they tend to bookmark asset frequently while creating or updating assets but often forget to unbookmark when work is completed.

If you double-click bookmarks node from left pane under "My Work" tab in Contributor UI, bookmarks list gets populated in center pane showing bookmarked assets for the current site. And if an author right-click's any asset or group of assets, he/she still sees bookmark option in context-menu but assets are already bookmarked and don't know how to unbookmark them! Should he/she unbookmark each asset one by one by opening them from Top Navigation - Content menu? Sounds boring and time consuming task.

Bookmarks Dashboard Widget

But wait, there is already OOTB dashboard in WebCenter Sites which also shows bookmarked assets and thus an author can unbookmark multiple assets at a time by either CTRL + CLICK for selective asset or Shift + Click for selecting all assets. That's it, multiple assets can be unbookmarked easily.

But in one of my recent project, there was a scenario wherein these dashboards were deleted so that Contributor UI loads faster as authors used to bookmark many assets, also many assets were assigned due to workflow and many assets were checked out. And thus, Contributor UI load time was really slow as many authors worked at same time on the Authoring Instance.

So now problem again was how can an author unbookmark multiple assets?
Well, with simple customization, one can provide unbookmark option in context menu which can help authors/contributors to unbookmark multiple assets again when assets are opened on double-clicking Bookmarks node under My Work tab. This customization is mentioned in guide here.

So basically, you just need to create either global or for a single site; a config element under CustomElements folder and write the following logic which will provide unbookmark option:

config.contextMenus = {
  "default":["bookmark", "unbookmark", "tagasset", "delete"],
  "asset":["edit", "copy", "preview", "share", "delete", "bookmark", "unbookmark", "tagasset"],
  "asset/Page":["edit", "copy", "preview", "delete", "bookmark", "unbookmark", "tagasset"],
  "proxy":["preview", "bookmark", "unbookmark", "tagasset", "delete"]
};

I tested it for avisports site by updating CustomElements/avisports/UI/Config/SiteConfigHtml element and it works well. That's it, problem solved. Now authors/contributors can bookmark and unbookmark as many assets as they desire.  

Unbookmark multiple assets

Note: After unbookmarking asset in list pane, one has to reload list pane so that only bookmarked assets are visible by either double-clicking again on Bookmarks node or by clicking on refresh buttom from right side.

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

Tuesday, December 22, 2015

Resolving missing dimension relationship

In one of my previous project on Oracle WCS 11g 8, I faced several times the problem of missing translation among master and translated assets for many assets after using Realtime Publishing. No matter how tedious or careful I was to publish first only master assets and then publish other translated assets, either due to dependency not all master assets were published or somehow did not published the translation link between master and translated asset. In such scenario, there is no other option than to write a simple utility to create a dimension relationship link between them again.

Steps to resolve this issue is:
  1. Find the assets which are missing this translation link among assets. In order to do that, compare the <AssetType>_DimP tables between source and destination instances after running the following query using Support Tools or using some utility element:
    Select CS_OWNERID, CS_DIMENSIONPARENTID from <AssetType>_DimP where CS_OWNERID!=CS_DIMENSIONPARENTID
  2. Important columns are CS_OWNERID (translated assetid) and CS_DIMENSIONPARENTID (master assetid)
  3. List down the assets which are not present on your destination instance by comparing. I prefer the following final format to update dimension relationship for multiple assets of a particular assettype: <TranslatedId1>,<MasterId1>;<TranslatedId2>,<MasterId2>... 
Once you have the above string, create one template (applies to various assettypes, can be called externally from browser or as layout) and add the following code:

Hit the url: [hostname]:[port]/[sites context]/ContentServer?pagename=[Sitename where template was created]/[YourTemplateName]&assetType=[AssetType] 
for e.g. http://localhost:9080/cs/ContentServer?pagename=FirstSiteII/ResolveMissingTranslations&assetType=Product_C
and wait for the output.

Sometimes, even after running the above code may not update all assets because there can be 2 translated assets supposed to be associated to same master asset and <asset:save> tag will not able to save the asset for both in same request. So, just run the utility again for the failed ones and that should resolve the issue. Compare again by running the same above query and the result should be same. Check in contributor UI to cross-check if dimension relationship was generated or not.

NOTE: You have to run this utility for all the affected assets of different assets types separately.
 
Disclaimer: Any sample code on this blog is not officially recommended, use at your own risk.

Monday, November 30, 2015

Get full asset details

There are times when we need to inspect all the fields of asset or either to perform other tasks. One can find all the details of any asset using the following method:

Using Tag:

Using Asset API


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

Add assets to workflow programatically

Within WCS, Workflows are the way to initiate assets from initial creation step to publish step by going through series of steps & condition wherein each step is governed by different group of users with different permissions.

Sometimes when assets were initially created, they may or may not be in workflow. But sudden requirement like assets should be editable by only certain users, workflow can be best solution for it as WCS can restrict users from editing/deleting by checking against Roles.

But even if you define workflow, how to assign workflow to an asset?
Well, after creating workflow, you have to inspect the asset in Contributor UI -> From Top Navigation, click on View -> Status -> Select workflow process and the participants. These are quite a few steps to do assign workflow for many assets, thus, a requirement to have a utility wherein one could assign workflow to many assets at once.

Procedure:
1. Find the condition by which you can filter which assets are required to be in workflow. For e.g. only page assets are required to be in workflow, only content assets of particular type, etc. Once decided, just write a query + condition using Asset API
2. Loop through each assets and while in loop assign asset to workflow as shown:

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

Thursday, September 10, 2015

Sample url assembler for avisports

Before introduction of Vanity URL in Oracle WCS 11g 11.1.1.8.0 version, writing custom assemblers was only the official way to change the generic long url to pretty url.

With installation of FatWire/Oracle WCS from scratch, there are basically 2 URL assemblers present in the product itself: Query Assembler (default url assembler) and QueryAsPathInfo Assembler (appends url to query string, after encoding it, to the servlet name; which can be indexed by search engines). Furthermore, if you have installed FirstSiteII as sample site, then FSIIAssembler is already present in /web-apps/WEB-INF/lib folder with name as firstsite-uri.jar. Also, source code of this assembler can be found in unpacked WCS installation package here under FirstSiteII\PrettyUrl folder.

Even after introduction of Vanity URLs, few clients prefer to have assembler than Vanity urls. For e.g. Think of the case wherein client wants to have certain string in the url which is not present in any of WCS assets. Even if the desired string were present in some other asset, for e.g. if corresponding metadata title from metadata flex family is to be added in url for every product asset, one has to write filter to achieve it. Furthermore, introduction of filters meaning updation of mass assets which may not desirable for few. Also, if a project was upgraded from old version to 11g 8.0 version, many clients don't like to configure vanity urls as it is arduous task to add vanity urls manually (considering it is not possible with automatic vanity url pattern configuration).

Although FirstSiteII url assembler is present, I thought to create my own url assembler as an exercise as nearly every project has a requirement to do so. Before that, let me put some very simple pointers on how URL assemblers work, taking FirstSiteII assembler as an example.

The FirstSiteII assembler creates URLs for
page asset as: <servlet>/<site>/<siteprefix>/<pageid>
all other asset types as: <servlet>/<site>/<siteprefix>/<assettype>/<assetid>/<pageid>

where,
<servlet> can be ContentServer or Satellite
<site> is extracted from 1st part of "/" from childpagename as childpagename=FirstSiteII/FSIILayout
<siteprefix> is extracted from the pagename as pagename=FSIIWrapper. For eg: here <siteprefix> is FSII
<assettype> == c parameter
<assetid> == cid parameter
<pageid> == p parameter

If any parameter is missing, next assembler is called which is usually configured to QueryAssembler. Assembler rankings are configured in ServletRequest.properties file. If you want to run FSIIAssembler, then proceed with updating FSIILink template for all asset types and add assembler="fsii" param to <render:gettemplateurl> tag. Verify once,  your products urls should look like the following:
http://<hostname>:<port>/cs/Satellite/FirstSiteII/FSII/Product_C/<ProductCId>/<ParentPageid>

Furthermore, FSIIAssembler can be configured to show names rather than cid and p which is nicely explained in this post.

Main method of assembling & disassembling the url is valueOf([AssemblerName] assembler, Definition def) where all params are extracted from definition object and assembled into one url, and disassembled when pretty url is requested on browser, respectively. If you keep most of the code as is  from FSIIAssembler and update only these 2 methods and few params here and there, it would be sufficient to code your own url assembler. Also, if you want certain strings to be passed or asset related info, it is better to create separate utility class to retrieve it.

After customising FSIIAssembler, I created my own assembler: AVIAssembler for avisports site and deployed on 11.1.1.6.1 JSK, which creates the following url pattern -
for page assets: <servlet>/<sitename>/Page/<Page name>/<Template>
for article assets: <servlet>/<sitename>/AVIArticle/<Article Parent>/<Article name>/<Template>

and a utility class - AVIAssemblerUtility to get name from cid and vice-versa using Michele's helper class and to get Parent name of Article asset. I've shared this assembler related stuff here.

To make this assembler work with JSK, do the following:
1. Deploy avisports-uri.jar file to /web-apps/cs/WEB-INF/lib folder
2. Make backup of ServletRequest.properties. Register url assembler by updating in ServletRequest.properties and make avisports as 1st rank. Note: If you are ranking AVISports as 1st, then keep FSIIAssembler as 2nd and Query Assembler as 3rd rank. Update each FSIILink template to include assembler="fsii" to run FSII urls, but still will fail to run product, product parent and content urls. Hence, later you can restore ServletRequest.properties from backup to run FirstSiteII and avisports as it was running earlier without AVIAssembler.

Add the following 2 lines and update rankings of other assembler accordingly:
uri.assembler.1.shortform=avi
uri.assembler.1.classname=com.sites.avisports.uri.AVIAssembler

and restart your JSK
3. Update the link CSElements (GetLink) which generate urls for page and article assets by adding assembler="avi"
4. Update the element - /Head to point to correct relative path to static folder - avisports within JSK.
5. Test your URLs

Page urls should be like http:localhost:8080/cs/Satellite/avisports/Page/Home/HomeLayout1

and article urls should be like the following: http://localhost:8080/cs/Satellite/avisports/AVIArticle/Surfing/How+Dial+A+Spot/ArticleLayout



Further improvements can be done like:
1. Generate path and pass it as a argument to <render:gettemplateurl> tag and then, include that path in the assembler rather than asset type name and site name. For e.g. As Running page asset is child page asset under Home page, url should be http://localhost:8080/cs/Satellite/Home/Running/SectionLayout rather than http://localhost:8080/cs/Satellite/avisports/Page/Running/SectionLayout

if article clicked from any page, it should be
http://localhost:8080/cs/Satellite/Home/<SurfingPageName>/<ArticleParent>/How+Dial+A+Spot/ArticleLayout
rather than as it is configured with this assembler http://localhost:8080/cs/Satellite/avisports/AVIArticle/Surfing/How+Dial+A+Spot/ArticleLayout

2. One can also remove /cs/Satellite from the URL by rewriting url using mod_rewrite.

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

Monday, September 7, 2015

Content targeting based on clickstream

Every online shopping store has a feature where consumers are shown related items or recommended items as any registered or anonymous users keep on browsing certain items on a website. Such use case are very common and can be developed within WCS or FatWire using Engage.

I am describing a very simple use case using Oracle WCS 11g 11.1.1.6.1 JSK for avisports site. My aim is to record number of times any article (article can be of any type: Running, Football, Skiiing, etc.) was clicked on a particular section and show recommended articles in "you may also like" section on basis of number of clicks + article type.

 Solution can be broken into following steps:
  • Enable visitor attribute, history attribute, history definition, segment and recommendation asset type for AviSport site
  • Add all the required start menus
  • Create visitor attribute - aviarticleuser (string, length-20)
  • Create history attributes - AviArticleType (enumeration - with all the values of ArticleCategory attribute: Category) and AviArticleClickCount (integer)
  • Create one history definition - AviArticleHistory (include AviArticleType and AviArticleClickCount)
  • Create segments for each article category or just create for 2 article category with condition aviarticleuser=<articlecategoryname> (For eg: for baseball segment, add the visitor attribute condtion as aviarticleuser=Baseball and for history definition condition, include count >10 and select "Baseball" as AviArticleType)
  • Create one recommendation asset (mode: recommendation, for asset type: article and select all segments and include corresponding articles and also select some articles for default case)
  • Record anonymous or visitor's clicks at particular section only.
  • Update ArticleLayout to calculate segments and show related articles based on recommendations.

First 7 steps, should be very straightforward. Full details on creating and configuring them is already given in developer's guide.

For recording visitor's click on particular section, I have added one param to Summary/Link (engage = yes) as I would be targeting those visitors who click on Related links section's articles only on any article detail page. Then, add engage parameter to avisports/AVIArticle/GetLink. On clicking article link of related links section, will add one param: engage to the output url, so add that param to ArticleLayout and ArticleLayout2 templates in cache criteria.

Now, update both article layout template to call the element: avisports/getVisitorArticles. This element finds the category of the current article (using query),  then finds the history count (vdm:gethistorycount), records the current article (vdm:recordhistory) to history definition, sets scalar values (vdm:setscalar) which helps while calculating segments, calculates segments (commercecontext:calculatesegments) and then loads list of articles from via recommendation asset (commercecontext:getrecommendations). This whole process of how segments are calculated and recommendations work is well explained in detail in developer's guide.

Futher improvements can be done by calculating segments for the visitor and then calling the template by including the segment id in cache criteria and then loading list of assets from recommendations.

As history definition table is created separately and keeps on adding data exponentially as visitors browse your site, it is better to clean data from this table on timely basis which is explained on my other post: Flushing visitor's data

All the updated elements are bundled here for downloading. I hope this post helps many developers.

Further improvements can be done by using the tag: <commercecontext:setsegments> if you know what segment does a visitor belongs to and then, can add the segment id to cache criteria to leverage the use of OOTB caching of Sites. Discussion on setting segment prior to calculating recommendation is succinctly explained here.

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

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.

Important list of tags

Listing few important tags required in any FatWire/Oracle WebCenter Sites project:

1. <ics:geterrno/> or ics.GetErrno - Get error number, if error number is not equal to Zero, something error occured. One can check againt errno description here.
<asset:load name="thisAsset" field="name" value="SomeAssetName"/>
if(ics.GetErrno == 0) success
else print error

2. <ics:LogMsg> or ics.LogMsg - To print any info in logs. Use with <ics:debug> tag.

3. <time:set> and <time:get> - Finding time response for any particular Template/CSElement in milliseconds. Enable logging.cs.time=DEBUG
<time:set name="timeVar"/>
//Call template
<time:get name="timeVar" output="elapsedTime"/>

4. <string:stream> - This tag is important when you want text to be shown as exactly as its saved in database. Useful in case of translated asset as it take cares of all other characters which contains diacrtic for other languages like spanish. Furthermore, this tag can retreive value from both ics variable and IList.

5. <render:stream> - All body or summary attributes which may contain hyperlinks or embedded asset can be rendered properly with use of this tag. Similarly, like <string:stream> tag, it can also take both ics variable and IList tag.

6. <asset:children> - It can find associated assets to an asset without loading the asset.

7. <asset:filterassetsbydate> - Very important tag when you want to show versions of an assets. It acts on basis of end date and start date. Read full details here.

8. <asset:setdimparents>, <asset:getdimparents> and <asset:removedimparents> are bunch of tags to set translation for asset, gets dimensionable asset parents and removes dimensional asset parents respectively.

9. <asset:getassettypeproperties> - Get all the properties related to assets as described here.

10. <asset:getlocale> and <asset:locales> - Get asset locale/s.

11. <asset:getsubtype> - Get subtype (definition) of an asset. Usually used in layout template for dispatching request to correct template.

12.  <asset:load>, <asset:get> and <asset:scatter> - Tags related to Basic asset details

13. <assetset:*> - Tags related to Flex asset details

14. <searchstate:*> - Tags related to searching flex assets

15. <asset:revision> and <asset:rollback>: Useful when something goes wrong with batch update/edit and saving assets.

16. <asset:edit>, <asset:checkout>, <asset:save> and <asset:undocheckout> - useful during mass editing and saving assets.

17. <asset:search> and <asset:list> - Tags related to searching basic assets

18. <asset:delete>, <asset:deleterevision> and <asset:void> - Tags related to deleting assets

19. <render:logdep> and <render:unknowndeps> - Tags for generating dependencies.

20. <ics:sql>, <ics:callsql>, <ics:sqlexp> and <ics:selectto> - Tags to get data drom database.

21. <insite:*> - In-context editing tags

22. <ics:getproperty> and <property:get> - Get property from properties files

23. <render:*> and <satellite:*> - Mostly all tags are used to render content. Note: use ics tags if asset is not rendered. For e.g. if your CSElement is just performing some calculation and NOT rendering any data, then use ics:callelement rather than render:callelement.

24. <ics:setssvar>, <ics:getssvar> and <ics:removessvar> - Session related tags

25. <dimensionset:filter> and <dimensionset:filtersingleasset> - Gets translation of asset/s by applying dimensionset filter

26. <listobject:*>, <ics:listget> and <ics:listloop> - List tags

27. <ics:if>, <ics:then> and <ics:else> - Conditional tag. Note: Don't use <ics:if> alone, you always with <ics:then>

28. <render:packargs> and <render:unpackarg> - Send args in form of one string and retreive them later by unpacking them. Usage depends on different situations, check tag library.

29. <satellite:cookie> and <ics:getcookie> - Cookies related tags

30. <vdm:*> and <commercecontext:*> - Engage related tags

31. <siteplan:*> - Tags related to loading siteplan tree

32. <satellite:normalizeurl> - Generates satellite safe url

33. <proxy:*> - Tags related to proxy asset

Page: Placed or Unplaced

There are times when one needs to know if a page asset is under default node or unplaced node of siteplan; be it while generating sitemap, left navigation, top navigation, breadcrumb or finding page asset which contains a particular associated asset.

Mostly anyone can find if a particular page is placed or unplaced using following query:

Select ncode from SitePlanTree where otype='Page' and oid='[Page id]'

But, there seems to be bug for this within Oracle WCS product for the following case. Suppose you have following setup in your siteplan under Default node.

DEFAULT
Page1
- Subpage1
- Subpage2

And if you were to unplace Page1 (along with chilren page assets) from "Default" to "Unplaced Pages" node, ncode for Page1 changes to "Unplaced" but all its children page assets ncode's value remains same i.e. "Placed" as information in SitePlanTree is not updated for children page assets. Hence, running the above query will yield false info.

I faced similar situation when in certain project, there was a requirement to find placed page asset whose association is some asset. As Contributors added that asset to only one placed page asset, it should be unique according to them. But it seemed that somebody had moved that page asset from Default to Unplaced page node which happened to be same case as above and created new set of page asset which was valid as there was only one placed page asset with such association.

Solution: To counter this, there is tag which can decipher if all the parent page assets of a particular page asset are under DEFAULT or UNPLACED node: siteplan:unplacedancestor

<asset:load name="pageAsset" type="Page" objectid='<%=ics.GetVar("PageId")%>'/>
<asset:getsitenode name="pageAsset" output="PageNodeId"/>
<siteplan:load name="pageNode" nodeid='<%=ics.GetVar("PageNodeId")%>'/>
<siteplan:unplacedancestor name="pageNode" output="hasunplacedparent"/>
Parent pages are placed?: <ics:getvar name="hasunplacedparent" />

If value is true, then the loaded page asset is under UNPLACED node.

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

Engage: Flushing visitor's data

If you are using Engage, then is one caveat which needs to be taken care of:

"Data in visitor's tables can increase exponentially (obviously depends on how many times your site is visited) which can affect performance"

FatWire/Oracle WCS has no way to know which data is to be deleted and on what basis so basically you have to flush data from tables either manually or by programming. 

Hence, following sample code snippet can help to flush data:

But wait, does admin has to run this code again and again? No. WebCenter Sites has a very important table: SystemEvents where one can add SiteEntry+CSElement or Template and set a time to call this element. Simple, isn't it? So just create an entry in SystemEvents table and some logs, if required.

Tag info: http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39371/JSP/vdm-flushinactive.html
http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39371/JSP/vdm-mergeinactive.html

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

Batch edit and save assets

Sometimes its required to achieve some task which requires just mass editing and saving assets (no content update) within FatWire/Oracle WCS.

A simple use case can be like this: Suddenly there is a requirement of adding Flex Filter to certain flex definition to perform certain tasks like extracting file info from BLOB attached. In such cases, you will have to edit and save asset as filters only work after assets are SAVED. Hence, if there are many assets (even if  ~ 20-30 assets), its better to have simple program which can help.

Sample code:

//get list of assets in form of IList (you should know which columns are retrieved, I am assuming I will get values in assetid and assettype as column names)
<ics:listloop name="editSaveList">
<ics:listget listname="editSaveList" name="assetid"/>
<ics:listget listname="editSaveList" name="assettype"/>
<%
String assetType = ics.GetVar("assettype");
String assetid = ics.GetVar("assetid");

// Check if the asset is editable or not
%><asset:canedit type="<%=assetType %>" objectid="<%=assetid %>"/><%
//if ics.GetErrno == 0 proceed else show the reason
if(ics.GetErrno == 0) {

//2 cases to consider
//Revision tracking on or off for the asset type

// if asset type is revision tracked
<asset:checkout objectid="<%=assetid %>" type="<%=assetType %>"/>
//if checkout successful ( i.e. ics.GetErrno == 0) proceed else show error
<asset:load name="assetToEdit" type="<%=assetType %>" objectid='<%=assetid%>' option="editable" />
<asset:save name="assetToEdit" />
//if save successful ( i.e. ics.GetErrno == 0) proceed else show error
<asset:checkin name="assetToEdit" annotation="Edited and saved using EditSaveAsset utility" flush="true"/>
//if checkin successful ( i.e. ics.GetErrno == 0) proceed else show error

// else if asset type is NOT revision tracked
<asset:load name="assetToEdit" type="<%=assetType %>" objectid='<%=assetid%>' option="editable" />
<asset:save name="assetToEdit" />

}  else {
//print or log error
}%>
</ics:listloop>

Thus, implementing above code with proper improvements (check at every step for any errors) can be useful in certain scenarios. It is better to test your utility in Test environment or JSK to avoid unexpected results on management environment.

One question I often get is how to run this utility. There are many ways, I am listing few:
1, Just create one template, add your logic to it and make its usage as "Can be called from External browser". Now call this template via browser in following format:

http://<hostname>:<portnum>/<context>/ContentServer?pagename=<SiteCatalog name of Template>&assetid=<AssetId>&assettype=<AssetType>

E.g. http://localhost:8080/cs/ContentServer?pagename=AviSports/EditSaveAsset&assetid=1234567&assettype=AVIArticle

Additionally, you can code satellite:form within which shows form and you can enter values within this form and submit this form to call the same template and then run this utility. In form, you can pass assetids in list or pass query to get the asset ids and then build your list using <ics:listobject> tag

If you are using form, then simple call like following is enough:
http://localhost:8080/cs/ContentServer?pagename=AviSports/EditSaveAsset

2. Create CSElement and add legal parameters like assettype, assetids, query, etc. and then add you logic to "Element" tab. These params which you added are now available to CSElement and can be retrieved using ics.GetVar(""). So basically you can get single valued assettype, list of assetids OR assetids by performing query, then create your IList and perform batch edit&save. To run this CSElement, save it and then scroll down till you see "Preview", click on it, a form will load up in either new window or new tab. Enter values and submit to run this utility.

Tag info: http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39371/JSP/asset-checkout.html
http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39371/JSP/asset-checkin.html

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

IList: Tags and Asset API

Mostly all output after data retrieval in FatWire and Oracle WCS is in form of IList and one needs to know how to work with IList. Hence, just simple case of how one can retrieve values using IList with tag and api.

Using TAG:

<ics:listloop list="mylist">  <ics:listget listname="mylist" fieldname="assetid" /> <ics:listget listname="mylist" fieldname="assettype" /> <ics:listget listname="mylist" fieldname="#curRow" /> //Current Row <ics:listget listname="mylist" fieldname="#numCols" /> //Number of columns <ics:listget listname="mylist" fieldname="#numRows" /> //Number of rows

//Sometimes it is required to use particular field to perfor some operation
//For eg: If you want to use asset id to pass in template
//Calling in following way will always result in same assetid
<render:calltemplate c="Type" cid='<%= ics.GetList("mylist").getValue("assetid")%>'/>

//instead use "output" attribute to save id in variable as followed:
<ics:listget listname="mylist" fieldname="assetid" output="thisId"/> <% String thisId = ics.GetVar("thisId"); %> <render:calltemplate c="Type" cid='<%= thisId%>'/>
</ics:listloop>

Users can also use this attribute while using ics:listloop tag as required - maxrows, endrow and startrow as required

Tag info: http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39371/JSP/ics-listloop.html

Using Asset API:

IList iList = ics.GetList("ListName");
if(null != iList && iList.hasData()){
int numRows = iList.numRows();
String assetid = "";
String assettype = "";
    for(int currentRow=1; currentRow<=numRows; currentRow++){
             iList.moveTo(currentRow);
            assetid = iList.getValue("assetid");
             assettype = iList.getValue("assettype");
             //Do something
}
ics.RegisterList("ListName",null);
}


API info: http://docs.oracle.com/cd/E29542_01/apirefs.1111/e39356/toc.htm

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...