Tuesday, July 17, 2012

Number to Persian letters text


Hey

In every financial systems we should show the letter version of numbers particularly in prints. It is also very useful for large numbers to understand it. For example if you see this number in digits "900025235" you can't catch how much it is exactly in short view but not in letters "Nine hundred million twenty five thousand two hundred and thirty five".

I think Persian digits letters are one of the most difficult ones in the different languages. Today we are trying to handle this. First take a look here:






1.Usage
Our mission is to change the input hint whenever its value is changed. To register that to your input you should pass these steps:
  1. include jquery framework to your page
  2. include number.to.persian.letters.js to your page
  3. register your input to the engine on this way:
        <script type="text/javascript"> 
            $(function() { 
                $(".toLetter").numberToLetter(); 
            }); 
        </script> 
    
According above scripts, all inputs with the class of "toLetter" register the "number to Persian letters" engine.

2.How it works
First take a look to the file number.to.persian.letters.js:
1    /** 
2     * number.to.persian.letters.js 
3     * @author: Mostafa Rastgar 
4     * @version: 1.0.0 
5     * 
6     * Created by Mostafa Rastgar on 2012-07-11. Please report any bug at http://prgassist.blogspot.com/ 
7     * 
8     * Copyright (c) 2012 Mostafa Rastgar  http://prgassist.blogspot.com/ 
9     * 
10    * 
11    */ 
12   $(function($) { 
13       var names = {"0": {value:"\u0635\u0641\u0631", scale:false}, 
14           "1": {value:"\u064a\u06a9", scale:false}, 
15           "2": {value:"\u062f\u0648", scale:false}, 
16           "3": {value:"\u0633\u0647", scale:false}, 
17           "4": {value:"\u0686\u0647\u0627\u0631", scale:false}, 
18           "5": {value:"\u067e\u0646\u062c", scale:false}, 
19           "6": {value:"\u0634\u0634", scale:false}, 
20           "7": {value:"\u0647\u0641\u062a", scale:false}, 
21           "8": {value:"\u0647\u0634\u062a", scale:false}, 
22           "9": {value:"\u0646\u0647", scale:false}, 
23           "10": {value:"\u062f\u0647", scale:false}, 
24           "11": {value:"\u064a\u0627\u0632\u062f\u0647", scale:false}, 
25           "12": {value:"\u062f\u0648\u0627\u0632\u062f\u0647", scale:false}, 
26           "13": {value:"\u0633\u064a\u0632\u062f\u0647", scale:false}, 
27           "14": {value:"\u0686\u0647\u0627\u0631\u062f\u0647", scale:false}, 
28           "15": {value:"\u067e\u0627\u0646\u0632\u062f\u0647", scale:false}, 
29           "16": {value:"\u0634\u0627\u0646\u0632\u062f\u0647", scale:false}, 
30           "17": {value:"\u0647\u0641\u062f\u0647", scale:false}, 
31           "18": {value:"\u0647\u062c\u062f\u0647", scale:false}, 
32           "19": {value:"\u0646\u0648\u0632\u062f\u0647", scale:false}, 
33           "20": {value:"\u0628\u064a\u0633\u062a", scale:false}, 
34           "30": {value:"\u0633\u064a", scale:false}, 
35           "40": {value:"\u0686\u0647\u0644", scale:false}, 
36           "50": {value:"\u067e\u0646\u062c\u0627\u0647", scale:false}, 
37           "60": {value:"\u0634\u0635\u062a", scale:false}, 
38           "70": {value:"\u0647\u0641\u062a\u0627\u062f", scale:false}, 
39           "80": {value:"\u0647\u0634\u062a\u0627\u062f", scale:false}, 
40           "90": {value:"\u0646\u0648\u062f", scale:false}, 
41           "100": {value:"\u0635\u062f", scale:false}, 
42           "200": {value:"\u062f\u0648\u064a\u0633\u062a", scale:false}, 
43           "300": {value:"\u0633\u064a\u0635\u062f", scale:false}, 
44           "400": {value:"\u0686\u0647\u0627\u0631\u0635\u062f", scale:false}, 
45           "500": {value:"\u067e\u0627\u0646\u0635\u062f", scale:false}, 
46           "600": {value:"\u0634\u0634\u0635\u062f", scale:false}, 
47           "700": {value:"\u0647\u0641\u062a\u0635\u062f", scale:false}, 
48           "800": {value:"\u0647\u0634\u062a\u0635\u062f", scale:false}, 
49           "900": {value:"\u0646\u0647\u0635\u062f", scale:false}, 
50           "1000": {value:"\u0647\u0632\u0627\u0631", scale:true}, 
51           "1000000": {value:"\u0645\u064a\u0644\u064a\u0648\u0646", scale:true}, 
52           "1000000000": {value:"\u0645\u064a\u0644\u064a\u0627\u0631\u062f", scale:true}, 
53           "1000000000000": {value:"\u062a\u064a\u0644\u064a\u0627\u0631\u062f", scale:true}, 
54           "1000000000000000": {value:"\u06a9\u0627\u062a\u064a\u0644\u064a\u0627\u0631\u062f", scale:true} 
55       }; 
56       $.fn.numberToLetter = function() { 
57           $(this).change(function() { 
58               numberChange($(this)); 
59           }); 
60           numberChange($(".toLetter")); 
61       } 
62       function numberChange(input){ 
63           var value = input.val(); 
64           input.attr({title:numberToLetter(value)}); 
65       } 
66       function numberToLetter(value) { 
67           value = value.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"); 
68           var parts = value.split(","); 
69           var textParts = new Array(); 
70           for (var i = 0; i < parts.length; i++) { 
71               var setScale = renderNumber(parts[i], textParts); 
72               if (setScale) { 
73                   renderScale((parts.length - i - 1), textParts); 
74               } 
75           } 
76    
77           var text = concatNumberTexts(textParts); 
78           if (text == "" && value != "") { 
79               return names["0"].value; 
80           } else { 
81               return text; 
82           } 
83       } 
84    
85       function concatNumberTexts(renderRepository) { 
86           var text = ""; 
87           for (var i = 0; i < renderRepository.length; i++) { 
88               if (text != "") { 
89                   text += " "; 
90               } 
91               if (text != "" && allowComman(renderRepository[i])) { 
92                   text += "\u0648 "; 
93               } 
94               text += renderRepository[i]; 
95           } 
96           return text; 
97       } 
98    
99       function renderScale(scaleIndex, renderRepository) { 
100          var section = pow(scaleIndex * 3); 
101          if (section > 1) { 
102              renderRepository.push(names[section.toString()].value); 
103          } 
104      } 
105   
106      function renderTwoLengthNumber(num1, num2, renderRepository, setScale) { 
107          if (num1 == "1") { 
108              renderRepository.push(names[num1 + num2].value); 
109              setScale = true; 
110          } else { 
111              if (num1 != "0") { 
112                  renderRepository.push(names[num1 + "0"].value); 
113                  setScale = true; 
114              } 
115              if (num2 != "0") { 
116                  renderRepository.push(names[num2].value); 
117                  setScale = true; 
118              } 
119          } 
120          return setScale; 
121      } 
122   
123      function renderNumber(digit, renderRepository) { 
124          var setScale = false; 
125          if (digit.length > 2) { 
126              if (digit.charAt(0) != "0") { 
127                  renderRepository.push(names[digit.charAt(0) + "00"].value); 
128                  setScale = true; 
129              } 
130              setScale = renderTwoLengthNumber(digit.charAt(1), digit.charAt(2), renderRepository, setScale); 
131          } else if (digit.length > 1) { 
132              setScale = renderTwoLengthNumber(digit.charAt(0), digit.charAt(1), renderRepository, setScale); 
133          } else if (digit.length > 0) { 
134              if (digit.charAt(0) != "0") { 
135                  renderRepository.push(names[digit.charAt(0)].value); 
136                  setScale = true; 
137              } 
138          } 
139          return setScale; 
140      } 
141   
142      function allowComman(txt) { 
143          for(var key in names){ 
144              if(names[key].value == txt){ 
145                  return !names[key].scale; 
146              } 
147          } 
148          return true; 
149      } 
150   
151      function pow(powNum) { 
152          var pow = 1; 
153          for (var j = 0; j < powNum; j++) { 
154              pow = pow * 10; 
155          } 
156          return pow; 
157      } 
158  }); 
159  
we should have a map with digit key and the value is the written version of the key. This map is for translation usage(line 13).
2.1 numberToLetter function (line 66)
This is the major function to handle the job. In line 67 we categorize every three digits near together into a category. This is the scales like thousand, million, billion, ... . parts in line 68 is an array of 3 digits. textParts in line 69 is the translated version of parts. renderNumber in line 71 will fill textParts(refer to 2.2). finally the textParts items should be joined together in line 77(refer to 2.4).

2.2 renderNumber function (line 123)
This function renders the letter and fill it to the 'renderRepository' parameter. Then returns a boolean to say if the scale should be rendered or not. If 3 zeros are near to each other the scale should not be rendered. For example 324000122.

2.3 renderTwoLengthNumber function (line 106)
This function should render two digits (num1, num2). There is a lot of exceptions between 10-20 in Persian. So we should handle this in here(lines 107,108). This function also fill the rendered text into the 'renderRepository'.

2.4 concatNumberTexts function (line 85)
This function concats the 'renderRepository' parameter items(textParts variable in numberToLetter function) together and returns full text. In Persian we shouldn't put 'and' after scales. So we should handle it here(lines 91,92).

You can download the client sample from here.

Tuesday, February 21, 2012

Multi-level jquery right to left menu

Hey ...
Horizontal dropdown main menu is one the most useful components for every sites or web applications. There are several jquery plug-in menus which build menu from UL/LI tags and work so good with various animations and effects. But always they have a problem for right to left web applications such as Arabic or Persian web applications. Most of the menus don’t work from right to left direction as well as from the left to right direction (the default orientation). So here I’m going to build a simple and complete multi-level right to left dropdown menu.
So let’s do it…
There are some rules which should be followed:
  • The menu should be generated from UL/LI tags
  • The first level should be horizontal and the other sub-level should be vertical
  • The menu items should be free and the user can assign it his/her customize CSS class
  • The content of menu items should be handle by user
  • The sub-menus should appear at the bottom of the item for the first level and for the other levels should appear at right side of the selected parent item
  • The sub-menus should vanish after specific time in milliseconds by leaving the parent item
The scenario is like this:
  • The user should make its own UL/LI tags with content
  • The UL/LI should be in the middle of a div tag with specific id and at least “jquerycssmenu” CSS class(it can have more extra classes)
  • There are some parameters which can be changed before generating menu such as: “jquerycssmenu. arrowimages”, “arrowimages. fadesettings”, “arrowimages. isRTL”. For switches of them refer to “jquerycssmenu.js “ to configure them
  • Finally, call “jquerycssmenu.buildmenu(divId);” on page load
The javascript code is available here:
1) var jquerycssmenu = {
2) arrowimages : {down:['downarrowclass', 'arrow-down.gif', 25], right:['rightarrowclass', 'arrow-left.gif']},
3) //duration of fade in/ out animation and the starting fade out delay, in milliseconds
4) fadesettings: {overduration: 350, outduration: 100, startfadeoutdelay:300},
5) // you can set it false and if you set it to false the sub menu positionning will be ok but you should
6) // change arrow incons position to the left side manually and .jquerycssmenu UL LI { float:left} should be set
7) isRTL : true,
8) buildmenu:function(menuid) {
9) jQuery(document).ready(function($) {
10) var mainmenu = $("#" + menuid + ">ul");
11) var headers = mainmenu.find("ul").parent();
12) headers.each(function(i) {
13) var curobj = $(this);
14) var subul = $(this).find('ul:eq(0)');
15) var inMenu = false;
16) this._dimensions = {w:this.offsetWidth, h:this.offsetHeight, subulw:subul.outerWidth(), subulh:subul.outerHeight()};
17) this.istopheader = curobj.parents("ul").length == 1 ? true : false;
18) subul.css({top:this.istopheader ? this._dimensions.h + "px" : 0});
19) /*for isRTL : false set it to paddingRight*/
20) curobj.children("a:eq(0)").css(this.istopheader ? {paddingLeft: jquerycssmenu.arrowimages.down[2]} : {}).append(
21) '<img src="' + (this.istopheader ? jquerycssmenu.arrowimages.down[1] : jquerycssmenu.arrowimages.right[1]) + '" class="' + (this.istopheader ? jquerycssmenu.arrowimages.down[0] : jquerycssmenu.arrowimages.right[0]) + '" style="border:0;" />');
22) curobj.hover(function(e) {
23) inMenu = true;
24) var targetul = $(this).children("ul:eq(0)");
25) this._offsets = {left:$(this).offset().left, top:$(this).offset().top};
26) var menuleft = this.istopheader ? this._dimensions.subulw - this._dimensions.w - 15 : this._dimensions.w;
27)
38) if (jquerycssmenu.isRTL) {
39) menuleft = (this._offsets.left < this._dimensions.subulw) ? (this.istopheader ? -(this._dimensions.subulw - this._dimensions.w) : this._dimensions.subulw ) : -menuleft;
30) } else {
31) menuleft = (this._offsets.left + menuleft + this._dimensions.subulw > $(window).width()) ? (this.istopheader ? -this._dimensions.subulw + this._dimensions.w : -this._dimensions.w) : menuleft;
32) }
33)
34) targetul.css({left:menuleft + "px"}).fadeIn(jquerycssmenu.fadesettings.overduration);
35) }, function(e) {
36) inMenu = false;
37) var li = $(this);
48) setTimeout(function() {
49) if (!inMenu) {
40) li.children("ul:eq(0)").fadeOut(jquerycssmenu.fadesettings.outduration)
41) }
42) }, jquerycssmenu.fadesettings.startfadeoutdelay);
43) });
44) });
45) mainmenu.find("ul").css({display:'none', visibility:'visible'});
46) });
47) }
58) };
As you can see everything happens in “buildmenu” method. So let’s take a look to it.
First of all we should find all LIs containing sub-menu(UL) in the middle of them from level 2 (lines 10, 11). Then we should iterate all of them to position their sub-menus at the right place relatively their parent item and handle mouseove and mouseout events to show or hide the sub-menu. We should find the sub-menu(line 14).
“inMenu” is a flag which determine if the mouse cursor is in the menu area or not (line 15). We set it to true in moseover and false in mouseout. In mouseout we don’t hide the sub-menu immediately. After a specific delay we do it to have more user-friendly result (line 42). You can comment “setTimeout(function() {” and “}, jquerycssmenu.fadesettings.startfadeoutdelay);” then check the result. You will find the menu without these lines so user-enemy on sub-menus navigation.
“_dimensions” is a map containing the selected element (LI) and it’s submenu width and height (line 16)
“istopheader” is a flag to determine if the selected item is top level or not(line 17). We should have a different strategy in sub-menu positioning if the selected item is top level or not.
Then we should set the top position of the sub-menu and place an arrow image into the parent of it (lines 18 to 21)
Then we should handle the mouseover and mouseout events. We do it by hover jquery API. The first argument is mouseover and the second argument is mouseout (line 22)
In mouseover function we should set the sub-menu left postion and make it to be shown (lines 25 to 32)
In mouseout function we should make the sub menu to be hidden (line 40)
At the last step of “buildmenu” API we should hide all of the ULs (line 45)
To have a more user-friendly user interface result we should prepare a CSS to illustrate ULs/LIs and A tags. I do it on this way:
.jquerycssmenu {
PADDING-LEFT: 15px;
FONT: bold 12px Verdana;
}
.jquerycssmenu UL {
PADDING-BOTTOM: 0px;
LIST-STYLE-TYPE: none;
MARGIN: 0px;
PADDING-LEFT: 0px;
PADDING-RIGHT: 0px;
PADDING-TOP: 0px
}
.jquerycssmenu UL LI {
POSITION: relative;
DISPLAY: inline;
/*for isRTL : false set it to left*/
FLOAT: right
}
.jquerycssmenu UL LI A {
BACKGROUND: #eff0ff;
PADDING-BOTTOM: 4px;
PADDING-LEFT: 7px;
PADDING-RIGHT: 7px;
DISPLAY: inline-block;
/*for isRTL : false set it to ltr*/
DIRECTION: rtl;
COLOR: #2d2b2b;
MARGIN-RIGHT: 3px;
TEXT-DECORATION: none;
PADDING-TOP: 5px
}
.jquerycssmenu UL LI A:hover {
BACKGROUND: #eff9ff;
COLOR: black
}
.jquerycssmenu UL LI UL {
Z-INDEX: 1;
POSITION: absolute;
DISPLAY: block;
VISIBILITY: hidden;
BORDER-BOTTOM: black 1px solid;
LEFT: 0px
}
.jquerycssmenu UL LI UL LI {
DISPLAY: list-item;
FLOAT: none;
}
.jquerycssmenu UL LI UL LI UL {
TOP: 0px
}
.jquerycssmenu UL LI UL LI A {
BORDER-BOTTOM: #778 0px solid;
BORDER-LEFT: #778 1px solid;
BORDER-RIGHT: #778 1px solid;
BORDER-TOP: #778 1px solid;
PADDING-BOTTOM: 4px;
MARGIN: 0px;
PADDING-LEFT: 5px;
WIDTH: 160px;
PADDING-RIGHT: 5px;
FONT: 13px Verdana;
/*for isRTL : false set it to ltr*/
DIRECTION: rtl;
BACKGROUND: white;
COLOR: black;
PADDING-TOP: 4px
}
.jquerycssmenu UL LI UL LI A:hover {
BACKGROUND: #eff9ff;
COLOR: black
}
.downarrowclass {
POSITION: absolute;
TOP: 7px;
/*for isRTL : false set it to right: 5px */
LEFT: 5px
}
.rightarrowclass {
POSITION: absolute;
TOP: 5px;
/*for isRTL : false set it to right: 5px */
LEFT: 5px
}
That’s it. Now we have a multi-level right to left dropdown menu.
You can download the js, css and sample html file from here.

all rights reserved by Mostafa Rastgar and Programmer Assistant weblog