Tuesday, November 17, 2009

Deconstructing Publisher (6.4) Rich Text Editor and Fixing the Adaptive Tag Reformatting Bug

Stating the Problem

Have you ever noticed that the Publisher Rich Text Editor (RTE) only supports a couple of Adaptive Tags: Current Time, Page Name, Community Name, and User Profiles…

I am not saying that only because no other tags show in the RTE “Adaptive Tag” button…(which is already kind of weird, but would not be so bad compared to what’s coming), but because if you want to use any other tag in the RTE source view, switching to the design view will completely mess up your tag…

Here is the example: I want to create a link to a portal page within my RTE content (no, not a common task at all)…and because I like the Adaptive Tags, I sure should use the pt:standard.openerlink tag, as follow:

<pt:standard.openerlink xmlns:pt='http://www.plumtree.com/xmlschemas/ptui/' pt:objectid='426' pt:classid='512' pt:mode='2'>A link To Portal Page</pt:standard.openerlink>


Great, I enter that in my Publisher RTE field, in the HTML source view…and going back to the RTE edit view will tranform automatically my nicely formatted (and working) tag into something useless:



<standard.openerlink pt:mode="2" pt:classid="512" pt:objectid="426" xmlns:pt="http://www.plumtree.com/xmlschemas/ptui/">A link To Portal Page</standard.openerlink>


As you can see, the RTE design view “ate” the “pt:” and messed up the closing tag as well…



Ultimately, the portal will not understand that tag anymore…and your hope to create a nice link to page using best practice Adaptive Tags is just not realistic so far.



This bug is crazy to me…I would have better understood if the above problem would be happening only with the custom tags you wrote yourself – yes, it obviously happens also with any custom tags you wrote –, but it is not even working with the default “Out of Box” ALUI Tags…I just don’t know how this could have passed QA…oh well…



Apparently that bug is fixed with Publisher 6.5 (check out the release notes), which I believe uses a completely new Rich Text Editor…But I have not personally verified if Custom Tags (the ones you or I wrote) are also working fine with the RTE in Publisher 6.5…,to be continued on this. (Let me know if you verified this)



Either way, if you are not ready to move to Publisher 6.5 quite yet, well, you have 2 options:




  • Try to use a better RTE instead of the one Publisher provides (this might be a good idea, although it has caveats too, such as uploading files in publisher, or create links to other publisher items)


  • Continue to read this post, and implement my fix.



Understanding the Why



After a couple of hours of reverse engineering (thanks to Visual Studio 2008 awesome JavaScript debugging capabilities), I finally found a fix for this issue…



But first, let me explain briefly how it all works…




  1. The publisher Servlet in charge of drawing the content item entry screen (java code) output a huge XML piece within the HTML source of the page…This XML will be the base info that will be read and transformed into Javascript objects, which will be used by the RTE buttons and various other behaviors.


  2. Part of the XML, we can see a pretty interesting one (very related to our problem): “replacementToken”



    <replacementToken index="1" class="PTRichTextToken">
    <pattern>(&amp;lt;PT:PAGENAME[^&amp;gt;]*/&amp;gt;)|(&amp;lt;PT:PAGENAME[^&amp;gt;]*&amp;gt;(.*?)&amp;lt;/PT:PAGENAME\s*&amp;gt;)</pattern>
    <replacementHTML>Page Name</replacementHTML>
    <style>background-color:#FFFF33;border:1px dashed #B9B933;</style>
    <tooltip>Name of the current portal page</tooltip>
    </replacementToken>



  3. As you can see above, the replacementToken contains a regex pattern, a replacement name, a style, and tooltip…This “token” (or more rightly, its existence in the page source) is exactly the cause that makes the only 4 ALUI Adaptive tags (Current Time, Page Name, Community Name, and User Profiles) work…

    Basically, what happens when you toggle between “Design View” and “Source View” is the following:


    1. In source view, you enter your Adaptive tag the way you should, nicely formatted the way the documentation says…


    2. When you toggle to design view, each replacement token regex pattern is executed in order to find if there are indeed any pttag in the source…


    3. Each positive find is first saved into a Javascript hashmap, and then is transformed into a proper valid HTML tag: <span>


    4. So really, in design view, the tag you entered or inserted is just a <SPAN></SPAN> tag, with a particular ID and style as defined by the token…

      Hence, for example, the pagename tag would really be in the design view something like:

      <span id=”PTRichTextToken1” style=”background-color:#FFFF33;border:1px dashed #B9B933;”>Page Name</span>

      This is why you see the dashed yellow line when you add the pagename tag using the RTE button…


    5. And if you toggle back to the source view, the replacement happens again in order to re-output the original tag you entered (using the javascript hashmap filled in step 3)





Ok, so this is overall the way RTE Publisher 6.4 is working with Adaptive Tags… Now we understand this, we need to implement our fix, right?



Delivering the How



Really, the fix is easy…and if I had the publisher java code, it would simply be a 5 minute job…All we would need to do is add all the new tokens you need (with proper regex pattern  for each one, etc…) in the Java class in charge of outputting the XML…



Well, I don’t have the code…



So the 2nd option is hacking the Javascript in order to add the new replacement tokens you want to the already built array of Replacement Tokens…simple, right?



The file that contains all these replacement mechanisms is PTControls.js (found in \imageserver\plumtree\common\private\js\jscontrols\)…and that’s the one we will modify a bit…



First, simply add the following method in there…I added it just before the method "PTRichTextControl.prototype.init" (approx line 5178) because we will need to modify that method too…



//customization: fabien sanglier
PTRichTextControl.prototype.AddCustomReplacementToken = function()
{
//make sure customtokens array have same size as tokenDecorators array...they work hand in hand.
var customtokens = new Array(
new PTRichTextToken('(<pt:customtaglib.openerlink[^>]*/>)|(<pt:customtaglib.openerlink[^>]*>(.*?)</pt:customtaglib.openerlink\s*>)','$3 (OpenerLink)'),
new PTRichTextToken('(<pt:standard.openerlink[^>]*/>)|(<pt:standard.openerlink[^>]*>(.*?)</pt:standard.openerlink\s*>)','$3 (Standard OpenerLink)')
);

//make sure tokenDecorators array have same size as customtokens array...they work hand in hand.
var tokenDecorators = new Array(
new Array('background-color:#FFFF33;border:1px dashed #B9B933;','Opener link to any objects in the portal'),
new Array('background-color:#FFFF33;border:1px dashed #B9B933;','Standard Opener link to any objects in the portal')
);

if(this.replacementTokens && this.replacementTokens.length > 0){
if(customtokens && customtokens.length > 0){
var startindex = this.replacementTokens.length;
var addDecorators = (tokenDecorators && customtokens.length == tokenDecorators.length);
for(var i = 0; i < customtokens.length; i++){
this.replacementTokens[startindex + i] = customtokens[i];
if(addDecorators){
this.replacementTokens[startindex + i].style = tokenDecorators[i][0];
this.replacementTokens[startindex + i].tooltip = tokenDecorators[i][1];
}
}
}
}
}


If you read carefully the above method, you’ll see 2 arrays: customtokens and tokenDecorators…by adding new items to these 2 arrays (which work together), you can add support for as many tags (out of the box or custom) as you want.



Next, add 1 line to the PTRichTextControl.prototype.init, which simply calls our new method above:



PTRichTextControl.prototype.init = function()
{
...

//begin customization: fabien sanglier: add the extra array for the custom tags
this.AddCustomReplacementToken();
//end customization

PTControls.makeGlobalObject(this,this.objName,true);
}


Not too bad to fix such a bad bug, right?



Note1: Make sure you clear your browser cache when you test! You might still get the old un-customized javascript…



Note2: this is non supported…make backup of original file and do it at your own risk bla bla bla usual stuff bla bla bla



Final words



Why haven’t they done this earlier? go figure!



Is it annoying to have to do this so that out of the box features works within out of the box product? Definitely!!



Was it interesting to understand this whole mess? Absolutely!



So long…