summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/charting/BidiSupport.js
blob: cc0dcec43b9f60dcf46e7734201d28bb7b10ff89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//>>built
define("dojox/charting/BidiSupport", ["dojo/_base/lang", "dojo/_base/html", "dojo/_base/array", "dojo/_base/sniff",
	"dojo/dom","dojo/dom-construct",
	"dojox/gfx", "dojox/gfx/_gfxBidiSupport", "./Chart", "./axis2d/common", "dojox/string/BidiEngine", "dojox/lang/functional"], 
	function(lang, html, arr, has, dom, domConstruct, g, gBidi, Chart, da, BidiEngine, df){

	var bidiEngine = new BidiEngine();
	
	lang.extend(Chart, {
		// summary:
		//		Add support for bidi scripts.
		// description:
		//		Bidi stands for support for languages with a bidirectional script. 
		//		There's a special need for displaying BIDI text in rtl direction 
		//		in ltr GUI, sometimes needed auto support.
		//		dojox.charting does not support control over base text direction provided in Dojo.

		// textDir: String
		//		Bi-directional support,	the main variable which is responsible for the direction of the text.
		//		The text direction can be different than the GUI direction by using this parameter.
		// 		Allowed values:
		//			1. "ltr"
		//			2. "rtl"
		//			3. "auto" - contextual the direction of a text defined by first strong letter.
		//		By default is as the page direction.		
		textDir:"",
		
		getTextDir: function(/*String*/text){
			// summary:
			//		Return direction of the text. 
			// description:
			// 		If textDir is ltr or rtl returns the value.
			//		If it's auto, calls to another function that responsible 
			//		for checking the value, and defining the direction.			
			// text:
			//		Used in case textDir is "auto", this case the direction is according to the first
			//		strong (directionally - which direction is strong defined) letter.
			//	tags:
			//		protected.
			var textDir = this.textDir == "auto" ? bidiEngine.checkContextual(text) : this.textDir;
			// providing default value
			if(!textDir){
				textDir = html.style(this.node,"direction");
			}
			return textDir;
		},

		postscript: function(node,args){
			// summary:
			//		Kicks off chart instantiation.
			// description:
			//		Used for setting the textDir of the chart. 
			// tags:
			//		private

			// validate textDir
			var textDir = args ? (args["textDir"] ? validateTextDir(args["textDir"]) : "") : "";
			// if textDir wasn't defined or was defined wrong, apply default value
			textDir = textDir ? textDir : html.style(this.node,"direction");
			this.textDir = textDir;

			this.surface.textDir = textDir;
			
			// two data structures, used for storing data for further enablement to change
			// textDir dynamically
			this.htmlElementsRegistry = [];
			this.truncatedLabelsRegistry = [];
		},

		setTextDir: function(/*String*/ newTextDir, obj){
			// summary:
			//		Setter for the textDir attribute.
			// description:
			//		Allows dynamically set the textDir, goes over all the text-children and  
			//		updates their base text direction.
			// tags:
			//		public
		
			if(newTextDir == this.textDir){
				return this;
			}
			if(validateTextDir(newTextDir) != null){
				this.textDir = newTextDir;
				
				// set automatically all the gfx objects that were created by this surface
				// (groups, text objects)
				this.surface.setTextDir(newTextDir);
			
				// truncated labels that were created with gfx creator need to recalculate dir
				// for case like: "111111A" (A stands for bidi character) and the truncation
				// is "111..." If the textDir is auto, the display should be: "...111" but in gfx
				// case we will get "111...". Because this.surface.setTextDir will calculate the dir of truncated
				// label, which value is "111..." but th real is "111111A".
				// each time we created a gfx truncated label we stored it in the truncatedLabelsRegistry, so update now 
				// the registry.
				if(this.truncatedLabelsRegistry && newTextDir == "auto"){
					arr.forEach(this.truncatedLabelsRegistry, function(elem){
						var tDir = this.getTextDir(elem["label"]);
						if(elem["element"].textDir != tDir){
							elem["element"].setShape({textDir: tDir});
						}
					}, this);
				}
				
				// re-render axes with html labels. for recalculation of the labels
				// positions etc.
				// create array of keys for all the axis in chart 
				var axesKeyArr = df.keys(this.axes);
				if(axesKeyArr.length > 0){
					// iterate over the axes, and for each that have html labels render it.
					arr.forEach(axesKeyArr, function(key, index, arr){
						// get the axis 
						var axis = this.axes[key];
						// if the axis has html labels 
						if(axis.htmlElements[0]){
							axis.dirty = true;
							axis.render(this.dim, this.offsets);
						}
					},this);
					
					// recreate title
					if(this.title){
						var forceHtmlLabels = (g.renderer == "canvas"),
							labelType = forceHtmlLabels || !has("ie") && !has("opera") ? "html" : "gfx",
							tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
						// remove the title
						domConstruct.destroy(this.chartTitle);
						this.chartTitle =null;
						// create the new title
						this.chartTitle = da.createText[labelType](
							this,
							this.surface,
							this.dim.width/2,
							this.titlePos=="top" ? tsize + this.margins.t : this.dim.height - this.margins.b,
							"middle",
							this.title,
							this.titleFont,
							this.titleFontColor
						);
					}				
				}else{
				// case of pies, spiders etc.
					arr.forEach(this.htmlElementsRegistry, function(elem, index, arr){
						var tDir = newTextDir == "auto" ? this.getTextDir(elem[4]) : newTextDir;
						if(elem[0].children[0] && elem[0].children[0].dir != tDir){
							dom.destroy(elem[0].children[0]);
							elem[0].children[0] = da.createText["html"]
									(this, this.surface, elem[1], elem[2], elem[3], elem[4], elem[5], elem[6]).children[0];
						}
					},this);
				}
			}
		},

		truncateBidi: function(elem, label, labelType){
			// summary:
			//		Enables bidi support for truncated labels.
			// description:
			//		Can be two types of labels: html or gfx.
			//		gfx labels: 
			//			Need to be stored in registry to be used when the textDir will be set dynamically.
			//			Additional work on truncated labels is needed for case as 111111A (A stands for "bidi" character rtl directioned).
			//			let say in this case the truncation is "111..." If the textDir is auto, the display should be: "...111" but in gfx
			//			case we will get "111...". Because this.surface.setTextDir will calculate the dir of truncated
			//			label, which value is "111..." but th real is "111111A".
			//			each time we created a gfx truncated label we store it in the truncatedLabelsRegistry.
			//		html labels:
			//			no need for repository (stored in another place). Here we only need to update the current dir according to textDir.
			// tags:
			//		private
		
			if(labelType == "gfx"){
				// store truncated gfx labels in the data structure.
				this.truncatedLabelsRegistry.push({element: elem, label: label});
				if(this.textDir == "auto"){
					elem.setShape({textDir: this.getTextDir(label)});
				}
			}
			if(labelType == "html" && this.textDir == "auto"){
				elem.children[0].dir = this.getTextDir(label);
			}
		}
	});

	var extendMethod = function(obj, method, bundleByPrototype, before, after){
		// Some helper function. Used for extending method of obj.
		// obj: Object
		//		The obj we overriding it's method.
		// method: String
		//		The method that is extended, the original method is called before or after
		//		functions that passed to extendMethod.
		// bundleByPrototype: boolean
		//		There's two methods to extend, using prototype or not.
		// before: function
		//		If defined this function will be executed before the original method.
		// after: function
		//		If defined this function will be executed after the original method.
		if(bundleByPrototype){
			var old = obj.prototype[method];
			obj.prototype[method] = 
				function(){
					var rBefore;
					if (before){
						rBefore = before.apply(this, arguments);
					}
					var r = old.apply(this, rBefore);
					if (after){
						r = after.call(this, r, arguments);
					}
					return r;
				};
		}else{
			var old = lang.clone(obj[method]);
			obj[method] = 
				function(){
					var rBefore;
					if (before){
						rBefore = before.apply(this, arguments);
					}
					var r = old.apply(this, arguments);
					if (after){
						after(r, arguments);
					}
					return r;
				};		
		}
	};

	var labelPreprocess = function(elem, chart, label, truncatedLabel, font, elemType){
		// aditional preprocessing of the labels, needed for rtl base text direction in LTR 
		// GUI, or for ltr base text direction for RTL GUI.

		var isChartDirectionRtl = (html.style(chart.node,"direction") == "rtl");
		var isBaseTextDirRtl = (chart.getTextDir(label) == "rtl");

		if(isBaseTextDirRtl && !isChartDirectionRtl){
			label = "<span dir='rtl'>" + label +"</span>";
		}
		if(!isBaseTextDirRtl && isChartDirectionRtl){
			label = "<span dir='ltr'>" + label +"</span>";
		}

		return arguments;
	};

	// connect labelPreprocess to run before labelTooltip.
	// patch it only is available
	if(dojox.charting.axis2d && dojox.charting.axis2d.Default){
		extendMethod(dojox.charting.axis2d.Default,"labelTooltip",true, labelPreprocess, null);
		//extendMethod(dijit,"showTooltip",false, labelPreprocess, null);
	}

	function htmlCreateText(r, agumentsArr){
		// function to register HTML elements that created by html.createText, this array
		// needed for allowing to change textDir dynamically.
		agumentsArr[0].htmlElementsRegistry.push([r, agumentsArr[2], agumentsArr[3], agumentsArr[4], agumentsArr[5], agumentsArr[6], agumentsArr[7]]);
	}

	extendMethod(da.createText,"html", false, null, htmlCreateText);

	function validateTextDir(textDir){
		return /^(ltr|rtl|auto)$/.test(textDir) ? textDir : null;
	}
		
});