Saturday, January 30, 2010

Modified DNN with RTL support release

As promised here the release of DNN version 5.02.01 modified to support RTL languages as a proof of concept of my suggestions. Here is a summary of modifications that are explained more in Part 1 & Part 2 for proposal to support RTL in DNN (without messing up the LTR layout). You can download this release from here.

Release Notes

1. API Changes
  1. Added read only property IsRightToLeft in Localization class to determine if the current culture is right to left.
  2. Added an extension method GetLabelModeString to LabelMode enumeration. When LabelMode is left and text direction is rtl the method returns right instead of left and when right the method returns left. If the text direction is left it has no effect when direction. This method should be used instead of ToString for supporting both LTR and RTL directions. This method returns string in lower case.
  3. Changed the PropertyGrid class to use the method described in point 2 above instead of using ToString method.
  4. Changed the behavior of AddStyleSheet in CDefault class to check the existance of RTL files and inculde them along side the normal CSS file in case of RTL culture. For example if AddStyleSheet is called to add file x.css, it will check for existance of x.rtl.css and include it if exists and RTL culture.
  5. Added dnn.direction() method in dnn.js. It returns rtl in case direction is RTL.
2. Changes in WebSite
  1. Added RTL CSS files according to point 4 above.
  2. Fixed calculations for Menu absolute positioning in dnn.controls.dnnmenu.js
  3. Fixed layout of Search controls in Search.js file. (background-image is always aligned left in CSS. In case of RTL direction, it needs to aligned right using background-position.
  4. Numerous fixes in the website to support both RTL and LTR layout. You can find them by searching through the solution for Modified By Michael Fayez in the code.
  5. Added the changes suggested here for adding rtl/ltr plugin to FCK editor (with slight modifications).
  6. I didn’t use CSS flipping as it didn’t work on Opera. I used flipped images from the original ones when needed.
3. Notes
  1. The attached file is only a proof of concept and is not supported by any means.
  2. I don’t claim any copyright for these modifications. (Thanking me would be nice). 
  3. The RTL modification of default skin could be better by flipping images of borders to have the shadows mirrored. Though the current solution passes.
  4. Only default skin and default menu are fixed.
  5. In some places like breadcrumb, the layout looks not mirrored in RTL mode this is because of flow direction of English in an RTL language the layout would be mirrored automatically. So this is not an issue at all.
  6. I didn't make any modifications to the core modules of DNN. They need to be checked one by one to fix them. I know that there are problems in forum module in RTL. The repository module layout engine uses a single template from html file per module which may not be adequate in case of supporting both direction LTR and RTL and so on.
  7. I don’t guarantee nor know if the DNN team would embrace these changes or supporting RTL layout. (Currently they fix RTL issues one by one without resolving to the root cause of the problem).
  8. This release is not thoroughly tested. My changes may cause bugs in some scenarios unknown to me.
  9. I only changed the pages and controls that made the layout wrong in the pages i visited while testing. I didn’t fix ALL DNN user controls with hardcoded alignments.
  10. This code is not upgradable. When I started working on modifying DNN for RTL, latest DNN version was 5.02.01. Version 5.02.02 was released during my work. Copying the changes to the new version was too much to do.
  11. The RTL style is having some problems in IE 7 (and I assume IE 6 too), while working fine on other browsers. I don’t think this is a major issue for a proof of concept.
  12. The default theme for FCK editor has curved dotted end in its toolbars. They look ugly in RTL mode. They should be flipped but I didn’t try to find a solution for this. As a workaround you can use the sliver theme which has a straight dotted vertical line at the end of toolbars.
4. Installation
  1. Set up DNN using the normal method.
  2. Copy the file portal.rtl.css from _default portal file to the folder of your created portals. (I didn’t find a way for the automatic copy of this file every time a new portal is created and didn’t want to spend more time in this point). This is VERY important as without it the page won’t have direction:rtl style and all JavaScript calculations that depends on dnn.direction() will not work.
  3. Add an RTL culture in languages page and compare the layouts of both RTL and LTR layouts by switching cultures using the flags in the top.
5. Things to consider

I suggested that RTL CSS is loaded along the normal css. But this duplicates the number of CSS files loaded and download by browser in RTL layout as almost no CSS file is suitable for both LTR and RTL directions so it duplicates the overhead of downloading. The other solution would be that RTL CSS file is loaded instead of the normal CSS file and duplicate the normal CSS file styles while modifying it for RTL layout. Maybe this is a better solution and easily doable by modifying the attached code.

Thursday, January 21, 2010

Proposal for DNN support of RTL layout Part 2

Please read the first part of this post first to better understand this post.
In my first post, I didn’t speak about the number of RTL script language users. They are about 600 millions (300 million in Arabic states (including economically important Persian gulf states), 170 millions in Pakistan, 75 millions in Iran, 30 millions in Afghanistan, etc.). This makes about 10% of the human population use RTL scripts mostly using the Arabic script which is used for many languages not just Arabic. Hebrew script users are 5 millions. Thaana script users in Maldives are 350,000 users.I have no info about Syriac user community but I think they won't be more than 3 millions but not sure.

I want also to clarify part of my previous post that maybe was not clear enough. The RTL CSS should override any property that affects horizontal direction not only float, text-align but also padding, margin, border any other property that affects the horizontal layout as in RTL layout it should be mirrored.
Now, I will continue explaining how to handle images and JavaScript in RTL layout.

Handling images in RTL mode

Usually images shouldn’t be horizontally flipped in RTL mode except:
  1. Images that specifies horizontal direction. For example in the image below the required image should be flipped to point to the text box not to point to the empty space beside the empty textbox.


  1. Images that are displayed in sequence horizontally as in RTL these images should be flipped to give the same effect. A real life example here is XDNewBlue skin. The skin is having messy layout in RTL mode because of its asymmetric layout. Here I will only show the container in RTL and LTR modes. The container header has 3 consecutive images which when rendered in reverse order were not correct. You will need to reverse second image to have the same layout of LTR flipped horizontally. In this case the image file is specified in a CSS file so the flipped image should be referenced in the RTL CSS file.

Images should be flipped either using:
  1. Client side horizontal flipping: this way is done using flipping images using CSS and remove the need to having a separate mirrored image file. After doing some search, I found a solution here. This solution is very good and would work by adding the CSS class below to the element to be flipped. This class should be empty in normal CSS and only having the text below in the RTL CSS file. Unfortunately this solution while it worked on Internet Explorer, Firefox, Safari and Chrome, it didn’t work on Opera browser. If you find any solution for Opera, please send it to me in comments. Please also note that if you apply this style to an element with text, the text will be mirrored which would not make it readable.
.flip-horizontal {
-moz-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: fliph; /*IE*/ }
  1. Server side flipping: A more trivial and cross browser way but will require a separate copy of every flipped image file. I suggest if the original image called x.jpg, the flipped one should be called x.rtl.jpg. This is just a naming convention and will not imply automatic loading. You will have to handle programmatically substituting images with flipped ones in case you detect the culture is RTL culture.

JavaScript files in RTL mode

Usually JavaScript code don’t have problems with RTL layout. The catch here is that you should not specify any inline style properties in JavaScript files that affect horizontal layout as possible as you can. Use instead classes specified in CSS files, so they would be correct for both LTR and RTL layout or you will have to programmatically check for RTL and then reverse the style.
To determine the direction client side:
According to Part 1 of this post direction propery is put in the portal.rtl.css file. So the only way to get this value in either use GetComputedStyle in Firefox or currentStyle for Internet Explorer. The function below is inspired from here. The code should be put in js/dnn.js file.
dnn.direction = function() {
if (typeof (dnn.__direction) == "undefined") {
var hElement = document.getElementsByTagName('html')[0];
if (window.getComputedStyle)
dnn.__direction = window.getComputedStyle(hElement, null).direction;
else if (oElement.currentStyle)
dnn.__direction = hElement.currentStyle.direction;
}
return dnn.__direction;
}
Explicit handling of RTL layout in JavaScript
The only case I can think of now is handling absolute positioning set programmatically. Usually this is done in menus. Below the image of DNN menu in RTL layout



As the co-ordinates are not mirrored for RTL layout, the code adjusts the absolute position of menu to the left offset of the button. The solution here for RTL layout is if in RTL layout then it should position the menu = offsetLeft of button - menu width + offsetWidth of button and then the menu will look like the image below

Catches:

Real testing RTL layout should ALWAYS be done using an RTL script. In the screenshots above though text is aligned right but text itself is written from left to right which is not the case with RTL scripts (I took screenshots in English for simplicity). The RTL script is written from right to left. This text direction could affect in some cases the layout. For Example the image below is for Save/Cancel buttons are messed up because Latin script is written left to right regardless of alignment.

As you see the save icon is beside cancel and the cancel icon is beside register. But when the text is really in RTL Arabic script, we see in the image below that everything is OK without doing any change.

Recommendations:

  1. Changes to core DNN library and core modules should be done to handle both LTR and RTL layouts automatically.
  2. In snowcovered sold modules, there should be some info about if the displayed module is tested in RTL layout with the default to No.
  3. Every release of DNN or DNN core modules should be developed with RTL layout in mind and should be tested by a developer that knows a language that is written in RTL script ( There would be a lot of them especially Egyptians, Pakistanis and Israelis)
I didnt discuss the RTL localization of FCK editor. There is a post about it here.
Next week, I will publish the DNN code described here as a proof of concept.

Thursday, January 14, 2010

Proposal for DNN support of RTL layout Part 1

DotNetNuke or (DNN in short) is an open source CMS that is built over ASP.Net. It is also extensible using modules providing a powerful programming platform. Unfortunately it has some issues with right to left (RTL) layout. In this blog post I am proposing the steps to be done in DNN to fully support RTL and LTR in the same portal out of the box.

For any CMS to fully support localization it needs:

  1. Static content localization: currently supported by DNN using Resx files and allows admins to modify them using the languages page in admin menu. admins can translate the resx files manually or just use a language pack if they can find a good and complete one.
  2. Dynamic content localization: DNN Corp is currently working on it. They put an infrastructure for dynamic content localization in version 5.2 but still not usable for the end-user. If you cannot wait for it to be complete you can use for example Magic content instead of HTML module, Ealo package for menu, breadcrumb localization and NBStore instead of DNN Store module. All these modules support dynamic content localization
  3. Right to left layout: for scripts that is written in right to left direction. Currently there is no official support for RTL in DNN. You will have to manually add in portal.css html {direction:rtl;} to make layout flipped. Most of the forms of DNN works good but some forms like login and manage profile don’t have a correct layout along with the default DNN menu in the default skin. Though the layout issue is not a blocking bug, it makes the layout looks wrong and amateurish for the end user.

The scripts that uses right to left layout are:

  1. Arabic script: which is used for writing Arabic, Persian, Urdu, Punjabi, Sindhi, etc.
  2. Hebrew script: which is used for writing Hebrew and Jewish languages like Yiddish, Ladino, etc.
  3. Scripts that are used by minority languages that nobody knows or cares about: like Thaana, Syriac, N'Ko, etc.
Only Arabic and Hebrew scripts have economic value especially in the Persian Gulf area and in Israel.

Root causes of messing up the RTL layout in web applications:

  1. In table cell alignment when a cell content is aligned left it remains aligned left event in RTL direction and when aligned right it remains aligned right in RTL direction. It should be mirrored. So when in a perfect world when i say when in RTL mode an html element is having

    <table align="left">


    it should align right and vice versa. unfortunately this is not the case.
  2. When using float left and float right css styles, they behave like point 1
  3. When assuming that images are displayed in specific order horizontally, this order is reversed in RTL mode. So some images will need to be flipped to look as good as in LTR mode (will give an example for this when discussing image RTL localization).
  4. The absolute positioning always starts from top left of the page regardless of the direction of the page (RTL or LTR). Usually this affects absolute positioning when an html element is placed using calculated co-ordinates to align left with another element. In RTL mode you should update the calculation to align right(will give an example for this when discussing JavaScript RTL localization).

These four reasons made some great Javascript frameworks like ExtJs unusable in RTL layout without much customization.

Though this is out of topic, Microsoft did a really good job on supporting RTL layout out of the box in  Windows forms. When a control is positioned using a left anchor in RTL layout it is positioned right though the anchor is still left. All of the standard windows forms controls behaves well in RTL mode. (though not all third party windows forms controls support RTL mode like Devexpress for example but this is not the time to discuss this here)

Straightforward steps to make DNN portal have an RTL layout:

  1. modify portal.css and add

html {direction:rtl;}


  1. Open ~/DesktopModules/AuthenticationServices/DNN/login.ascx and modify all table align="left" to align="right" and vice versa.
Unfortunately the straight forward approach fails due to:
  1. Usually my customers want a multi lingual portal (English/Arabic) using dynamic content localization as described above. Adding the direction:rtl in portal.css will lead to displaying English language in RTL mode which is not correct.
  2. The default menu is still in LTR mode. 
  3. The manage profile tab is messed up because of hardcoded style of text-align: left in Profile.ascx. (though the RTL issue is supposed to be resolved but apparently the developer that fixed it doesn’t know how RTL languages would look like because he left behind text-align: left which messed up the RTL layout)

So now we have a dead end for a trivial quick solution.

The good solution for the RTL problem:

Requirements:
  • The solution should fix the problem of RTL without messing the layout of the LTR languages on the same multi-lingual portal.
Detecting RTL culture server side:

We need to detect the RTL language culture server side to do some actions for handling RTL layout as I will discuss later. The best way to do this is using Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft property which is deeply buried in the properties of the properties of the properties of the current thread object.

I suggest adding a shared/static readonly property to DotNetNuke.Services.Localization.Localization class to return the IsRightToLeft property

Public Shared ReadOnly Property IsRightToLeft() As Boolean
    Get
        Return Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft
    End Get
End Property


Handling the embedded styles in tags:

After digging into the DNN code, I found an enumeration called DotNetNuke.UI.WebControls.LabelMode. this enumeration is used by the FieldEditorControl to display the register and the user profile control. Labels have the LabelMode left and controls have the LabelMode right. And then the FieldEditorControl calls the ToSring().ToLower() for the enumeration to get the text to embed in style left or right (these are the two properties we care about now for RTL layout). So the good news is that if we override the ToString() of the enumeration to render left property if not RTL then "left" else "right" and vice versa, all the forms that uses this control will be adjusted automatically without handling them case by case. The bad news is that there is no support for overriding ToString() method for enumerations :). So we will need an extension method to get label mode text and get the correct string according to text layout and this extension method should be used instead of ToString()

Public Module LabelModeHelper
       <Runtime.CompilerServices.Extension()> _
       Public Function GetLabelModeString(ByVal mode As LabelMode) As String
           If mode <> LabelMode.Left AndAlso mode <> LabelMode.Right Then Return mode.ToString().ToLower()
           If mode = LabelMode.Left Then
               Return If(Localization.IsRightToLeft, "right", "left")
           End If
           If mode = LabelMode.Right Then
               Return If(Localization.IsRightToLeft, "left", "right")
           End If
           Return String.Empty
       End Function
   End Module

There remains another issue; handling LabelMode in RTL is fixed in three places Membership, Profile and User user controls. the same code is copy pasted 3 times in the user control. I write it here for convenience

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
           'Get the base Page
            Dim basePage As PageBase = TryCast(Me.Page, PageBase)
            If basePage IsNot Nothing Then
                'Check if culture is RTL
                If basePage.PageCulture.TextInfo.IsRightToLeft Then
                    UserEditor.LabelMode = LabelMode.Right
                Else
                    UserEditor.LabelMode = LabelMode.Left
                End If
            End If
        End Sub

The code above is not based on a good design as it was written 3 times over and over again!!!! I commented it to make my code work as it would conflict with it. In my code I am trying to imitate the perfect case of Windows forms as I mentioned above. Thus developers would LabelMode.Left without bothering to think how it should be rendered in RTL mode.

For styles embedded in ascx align="left" or text-align:left, and also for right all these makes RTL layout looks odd. So all these should be deleted except if really needed and use the following code <%= LabelMode.Left.GetLabelModeString %> for hard coded part of "left" or move the embded alignments like float, text-align to CSS files which I will discuss now how to localize them.

Handling CSS in RTL mode:

The CSS files would contain values of float:left , float:right, text-align:left, etc. These values should be reversed in RTL mode. The solution I propose here is to immitate ScriptManager control in ASP.Net for loading javascript files. For example when ScriptManager loads x.js file, if ASP.Net is in debug mode and there exists a file called x.debug.js, this file will be loaded instead of x.js which would be loaded in release mode.

I suggest that all css would be optimized for LTR mode for example a file called x.css and if there is style in it that would make RTL looks odd, it would be overridden in another CSS file with the same name for example x.rtl.css  with the same classes but with only the parts that  make a conflict overridden and loaded only when the language is detected to have an RTL layout. So in this case of RTL language both files would be loaded unlike what the ScriptManager does.

To achieve this we need to modify the code in the AddStyleSheet method in CDefault class in the core DNN library to check if:

  1. An RTL language is used for current culture
  2. and there exists a file with the same name of the CSS file being added to the page ending with .rtl.css

if both conditions are fulfilled then the .rtl.css class is loaded immediately after the file being added.

Public Sub AddStyleSheet(ByVal id As String, ByVal href As String, ByVal isFirst As Boolean)
    'Find the placeholder control
    Dim objCSS As Control = Me.FindControl("CSS")

    If Not objCSS Is Nothing Then
        'First see if we have already added the <LINK> control
        Dim objCtrl As Control = Page.Header.FindControl(id)

        If objCtrl Is Nothing Then
            Dim objLink As New HtmlLink()
            objLink.ID = id
            objLink.Attributes("rel") = "stylesheet"
            objLink.Attributes("type") = "text/css"
            objLink.Href = href

            If isFirst Then
                'Find the first HtmlLink
                Dim iLink As Integer
                For iLink = 0 To objCSS.Controls.Count - 1
                    If TypeOf objCSS.Controls(iLink) Is HtmlLink Then
                        Exit For
                    End If
                Next
                objCSS.Controls.AddAt(iLink, objLink)
                If Localization.IsRightToLeft Then
                    AddRTLStyleSheet(id, href, objCSS, iLink + 1)
                End If
            Else
                objCSS.Controls.Add(objLink)
                If Localization.IsRightToLeft Then
                    AddRTLStyleSheet(id, href, objCSS)
                End If
            End If
        End If
    End If
End Sub

Private Sub AddRTLStyleSheet(ByVal id As String, ByVal href As String, ByVal objCSS As Control, Optional ByVal index As Integer = -1)
    Dim rtlhref As String = GetRTLStyleSheetName(href)
    If String.IsNullOrEmpty(rtlhref) Then Return
    Dim objLink As New HtmlLink()
    objLink.ID = id + "rtl_"
    objLink.Attributes("rel") = "stylesheet"
    objLink.Attributes("type") = "text/css"
    objLink.Href = rtlhref
    If index <> -1 Then
        objCSS.Controls.AddAt(index, objLink)
    Else
        objCSS.Controls.Add(objLink)
    End If

End Sub

Private Function GetRTLStyleSheetName(ByVal href As String) As String
    Dim physicalFilePath As String = Server.MapPath(href)
    If Path.GetExtension(physicalFilePath).ToLower <> ".css" Then
        Return Nothing
    End If
    physicalFilePath = physicalFilePath.Substring(0, physicalFilePath.Length - 4) + ".rtl.css"
    If File.Exists(physicalFilePath) Then
        Return href.Substring(0, href.Length - 4) + ".rtl.css"
    End If
    Return Nothing
End Function

I have tested the code above and it loaded portal.rtl.css, skin.rtl.css and index.rtl.css which are files that I made. It may not be fully optimized but it is a start at least.

portal.rtl.css is the file that should contain html {direction:rtl;} not in portal.css.

The rtl css files should be only overriding properties in  css classes in the normal css files that are only related to rtl only. No need to copy/paste the stylesheet and make changes. This way you will double size of css and will have to maintain 2 css files. Below is an example for this.

In portal.css file there is class

.branding-bottom li { list-style: none; margin: 0 10px 0 0; padding: 0; display: block; width: 170px; float: left; }

In portal.rtl.css there should be

.branding-bottom li { float: right; margin: 0 0 0 10px; }

In my next post, I will discuss the 2 remaining aspects of RTL localization; localizing images and JavaScript files. I will publish my modified code as a proof of concept but won’t be suitable for production.

I hope that DNN corp incorporate these changes to make DNN more powerful than it already is.

Acknowledgements: I copied the style of the grey code boxes form here

Wednesday, January 13, 2010

My first post

This my first time to start blogging. I found myself tempted to start a blog to share my ideas with the developer community. For what I see now, I will always blog about programming and software development. Talking about politics could be dangerous :) so I hope I won’t speak about them