Added wrt files, prior to converting into Qt/C++.
[ptas] / wrt / zouba / WRTKit / UI / ContentPanel.js
1 /*\r
2 � Copyright 2008 Nokia Corporation. All rights reserved.\r
3 \r
4 IMPORTANT:  The Nokia software ("WRTKit and Example Widget files") is supplied to you by Nokia\r
5 Corporation (�Nokia�) in consideration of your agreement to the following terms. Your use, installation\r
6 and/or redistribution of the WRTKit and Example Widget files constitutes acceptance of these terms. If\r
7 you do not agree with these terms, please do not use, install, or redistribute the WRTKit and Example\r
8 Widget files.\r
9 \r
10 In consideration of your agreement to abide by the following terms, and subject to these terms, Nokia\r
11 grants you a personal, non-exclusive license, under Nokia�s copyrights in the WRTKit and Example\r
12 Widget files, to use, reproduce, and redistribute the WRTKit and Example files, in text form (for HTML,\r
13 CSS, or JavaScript files) or binary form (for associated images), for the sole purpose of creating S60\r
14 Widgets.\r
15 \r
16 If you redistribute the WRTKit and Example files, you must retain this entire notice in all such\r
17 redistributions of the WRTKit and Example files.\r
18 \r
19 You may not use the name, trademarks, service marks or logos of Nokia to endorse or promote products\r
20 that include the WRTKit and Example files without the prior written explicit agreement with Nokia.\r
21 Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by\r
22 Nokia herein, including but not limited to any patent rights that may be infringed by your products that\r
23 incorporate the WRTKit and Example files or by other works in which the WRTKit and Example files\r
24 may be incorporated.\r
25 \r
26 The WRTKit and Example files are provided on an "AS IS" basis.  NOKIA MAKES NO\r
27 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED\r
28 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A\r
29 PARTICULAR PURPOSE, REGARDING THE EXAMPLES OR ITS USE AND OPERATION\r
30 ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\r
31 \r
32 IN NO EVENT SHALL NOKIA BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR\r
33 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
34 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
35 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, AND/OR\r
36 DISTRIBUTION OF THE EXAMPLES, HOWEVER CAUSED AND WHETHER UNDER THEORY\r
37 OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE,\r
38 EVEN IF NOKIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
39 \r
40 */\r
41 \r
42 ///////////////////////////////////////////////////////////////////////////////\r
43 // The ContentPanel class is a control for displaying content. The panel\r
44 // can be expanded and collapsed.\r
45 \r
46 // Constructor.\r
47 function ContentPanel(id, caption, content, foldable, expanded) {\r
48     if (id != UI_NO_INIT_ID) {\r
49         this.init(id, caption, content, foldable, expanded);\r
50     }\r
51 }\r
52 \r
53 // ContentPanel inherits from Control.\r
54 ContentPanel.prototype = new Control(UI_NO_INIT_ID);\r
55 \r
56 // The element hierarchy in a content panel is as follows:\r
57 //\r
58 // rootElement\r
59 //     assemblyElement\r
60 //         captionElement\r
61 //             foldToggleElement\r
62 //                 captionLinkElement\r
63 //                     captionTextElement\r
64 //     contentElement\r
65 //\r
66 // captionTextElement is moved under foldToggleElement if disabled\r
67 // or captionElement if not foldable\r
68 \r
69 // The fold toggle element used for folding content panels.\r
70 ContentPanel.prototype.foldToggleElement = null;\r
71 \r
72 // The caption link element of this control.\r
73 ContentPanel.prototype.captionLinkElement = null;\r
74 \r
75 // The caption text element of this control.\r
76 ContentPanel.prototype.captionTextElement = null;\r
77 \r
78 // The content element of this control.\r
79 ContentPanel.prototype.contentElement = null;\r
80 \r
81 // The foldable state of this control.\r
82 ContentPanel.prototype.foldable = false;\r
83 \r
84 // The expanded state of this control.\r
85 ContentPanel.prototype.expanded = false;\r
86 \r
87 // Enabled status.\r
88 ContentPanel.prototype.enabled = false;\r
89 \r
90 // Initializer - called from constructor.\r
91 ContentPanel.prototype.init = function(id, caption, content, foldable, expanded) {\r
92     uiLogger.debug("ContentPanel.init(" + id + ", " + caption + ", " + content + ", " + foldable + ", " + expanded + ")");\r
93     \r
94     // call superclass initializer\r
95     Control.prototype.init.call(this, id, caption);\r
96     \r
97     // the control defaults to enabled\r
98     this.enabled = true;\r
99     \r
100     // create caption text element\r
101     this.captionTextElement = document.createElement("span");\r
102     \r
103     // disconnect the control element\r
104     this.assemblyElement.removeChild(this.controlElement);\r
105     \r
106     // set the foldable state\r
107     this.foldable = foldable;\r
108     \r
109     // is this a foldable content panel?\r
110     if (foldable) {\r
111         // create fold toggle element\r
112         this.foldToggleElement = document.createElement("div");\r
113         this.captionElement.appendChild(this.foldToggleElement);\r
114         \r
115         // create caption link and add to caption element\r
116         this.captionLinkElement = document.createElement("a");\r
117         this.captionLinkElement.href = "JavaScript:void(0)";\r
118         this.foldToggleElement.appendChild(this.captionLinkElement);\r
119         \r
120         // add the text element to the link element\r
121         this.captionLinkElement.appendChild(this.captionTextElement);\r
122         \r
123         // bind event listeners\r
124         var self = this;\r
125         this.captionLinkElement.addEventListener("focus", function() { self.focusStateChanged(true); }, false);\r
126         this.captionLinkElement.addEventListener("blur", function() { self.focusStateChanged(false); }, false);\r
127         this.foldToggleElement.addEventListener("mouseover", function() { self.hoverStateChanged(true); }, false);\r
128         this.foldToggleElement.addEventListener("mouseout", function() { self.hoverStateChanged(false); }, false);\r
129         this.foldToggleElement.addEventListener("mousedown", function(event) {\r
130                                                                  self.captionClicked();\r
131                                                                  event.stopPropagation();\r
132                                                                  event.preventDefault();\r
133                                                              }, true);\r
134         this.foldToggleElement.addEventListener("keydown", function(event) {\r
135                                                                // center and enter trigger the action\r
136                                                                if (event.keyCode == 0 || event.keyCode == 13) {\r
137                                                                    self.captionClicked();\r
138                                                                    event.stopPropagation();\r
139                                                                    event.preventDefault();\r
140                                                                }\r
141                                                            }, true);\r
142         \r
143         this.expanded = expanded;\r
144     } else {\r
145         // since this is not a foldable panel the content should be expanded\r
146         this.expanded = true;\r
147         \r
148         // add the text element directly to the caption element\r
149         this.captionElement.appendChild(this.captionTextElement);\r
150     }\r
151     \r
152     // create content element\r
153     this.contentElement = document.createElement("div");\r
154     this.contentElement.style.display = this.expanded ? "block" : "none";\r
155     this.rootElement.appendChild(this.contentElement);\r
156     \r
157     // set caption, content and expanded state\r
158     this.setCaption(caption);\r
159     this.setContent(content);\r
160     \r
161     // update style\r
162     this.updateStyleFromState();\r
163 }\r
164 \r
165 // Returns the enabled state.\r
166 ContentPanel.prototype.isEnabled = function() {\r
167     return this.enabled;\r
168 }\r
169 \r
170 // Sets the enabled state.\r
171 ContentPanel.prototype.setEnabled = function(enabled) {\r
172     uiLogger.debug("ContentPanel.setEnabled(" + enabled + ")");\r
173     \r
174     // bail out early if there is no change in state\r
175     if (this.enabled == enabled) {\r
176         return;\r
177     }\r
178     \r
179     // set the enabled state\r
180     this.enabled = enabled;\r
181     \r
182     // is this a foldable content?\r
183     if (this.foldable) {\r
184          // the caption link must be disabled\r
185         if (this.enabled) {\r
186             // diabled -> enabled\r
187             this.foldToggleElement.removeChild(this.captionTextElement);\r
188             this.foldToggleElement.appendChild(this.captionLinkElement);\r
189             this.captionLinkElement.appendChild(this.captionTextElement);\r
190         } else {\r
191             // enabled -> diabled\r
192             this.captionLinkElement.removeChild(this.captionTextElement);\r
193             this.foldToggleElement.removeChild(this.captionLinkElement);\r
194             this.foldToggleElement.appendChild(this.captionTextElement);\r
195         }\r
196     }\r
197     \r
198     // update style\r
199     this.updateStyleFromState();    \r
200 }\r
201 \r
202 // Returns the caption; null if none.\r
203 ContentPanel.prototype.getCaption = function() {\r
204     return this.caption;\r
205 }\r
206 \r
207 // Sets the caption; null if none.\r
208 ContentPanel.prototype.setCaption = function(caption) {\r
209     // bail out if the caption text element has not been created\r
210     // this is to prevent the superclass init calling this before\r
211     // we've initialized our custom caption\r
212     if (this.captionTextElement == null)\r
213         return;\r
214     \r
215     uiLogger.debug("ContentPanel.setCaption(" + caption + ")");\r
216     \r
217     // set the display style\r
218     this.captionElement.style.display = (caption == null) ? "none" : "block";\r
219     \r
220     // set the caption\r
221     this.caption = caption;\r
222     this.captionTextElement.innerHTML = (caption == null) ? "" : caption;\r
223     \r
224     // update style\r
225     this.updateStyleFromState();\r
226 }\r
227 \r
228 // Returns the content.\r
229 ContentPanel.prototype.getContent = function() {\r
230     return this.contentElement.innerHTML;\r
231 }\r
232 \r
233 // Sets the content.\r
234 ContentPanel.prototype.setContent = function(content) {\r
235     uiLogger.debug("ContentPanel.setContent(" + content + ")");\r
236     this.contentElement.innerHTML = (content == null) ? "" : content;\r
237 }\r
238 \r
239 // Returns the focusable state for the control.\r
240 ContentPanel.prototype.isFocusable = function() {\r
241     // a content panel is focusable if it's foldable and enabled\r
242     return (this.foldable && this.enabled);\r
243 }\r
244 \r
245 // Sets the focused state for the control.\r
246 // Note: This may not always succeed.\r
247 ContentPanel.prototype.setFocused = function(focused) {\r
248     uiLogger.debug("ContentPanel.setFocused(" + focused + ")");\r
249     if (this.enabled && this.foldable) {\r
250         if (focused) {\r
251             this.captionLinkElement.focus();\r
252         } else {\r
253             this.captionLinkElement.blur();\r
254         }\r
255     }\r
256     // note that this.focused gets set as a result of focusStateChanged() being called\r
257     // rather than setting it explicitly here\r
258 }\r
259 \r
260 // Returns the expanded state.\r
261 ContentPanel.prototype.isExpanded = function() {\r
262     return this.expanded;\r
263 }\r
264 \r
265 // Sets the expanded state.\r
266 ContentPanel.prototype.setExpanded = function(expanded) {\r
267     uiLogger.debug("ContentPanel.setExpanded(" + expanded + ")");\r
268     \r
269     // make sure only foldable content panels are folded\r
270     if (!this.foldable) {\r
271         uiLogger.warn("Cannot fold a non-foldable content panel!");\r
272         return;\r
273     }\r
274     \r
275     this.expanded = expanded;\r
276     if (this.expanded) {\r
277         // expand\r
278         this.contentElement.style.display = "block";\r
279         \r
280         // find out control top and bottom\r
281         var controlTop = this.getAbsoluteTop(this.rootElement);\r
282         var controlHeight = this.rootElement.clientHeight;\r
283         var controlBottom = controlTop + controlHeight;\r
284         \r
285         // find out the viewport top and bottom\r
286         var viewportTop = window.scrollY;\r
287         var viewportHeight = window.innerHeight;\r
288         var viewportBottom = viewportTop + viewportHeight;\r
289         \r
290         // make sure the control is positioned so that it can be seen\r
291         var overflow = controlBottom - viewportBottom;\r
292         if (overflow > 0) {\r
293             // there's overflow so we need to scroll to get the control\r
294             // into the viewport - however not so far that the control\r
295             // goes past the viewport top.\r
296             var distanceToTop = controlTop - viewportTop;\r
297             var scrollAmount = Math.min(overflow, distanceToTop);\r
298             window.scrollBy(0, scrollAmount);\r
299         }\r
300     } else {\r
301         // collapse\r
302         this.contentElement.style.display = "none";\r
303     }\r
304     \r
305     // notify event listeners\r
306     this.fireEvent(this.createEvent("ExpandedStateChanged", this.expanded));\r
307     \r
308     // update the style\r
309     this.updateStyleFromState();\r
310 }\r
311 \r
312 // Returns the absolute position (y) of the given element.\r
313 ContentPanel.prototype.getAbsoluteTop = function(element) {\r
314     // traverse from element to root and add top-offset\r
315     // for each element we find on the way\r
316     var absTop = 0;\r
317     while (element != null) {\r
318         absTop += element.offsetTop;\r
319         element = element.offsetParent;\r
320     }\r
321     return absTop;\r
322 }\r
323 \r
324 // Callback for when the caption is clicked.\r
325 ContentPanel.prototype.captionClicked = function() {\r
326     uiLogger.debug("ContentPanel.captionClicked()");\r
327     \r
328     // if we're enabled then a click results toggling the expanded state\r
329     if (this.enabled) {\r
330         // focus when clicked\r
331         if (!this.focused) {\r
332             this.captionLinkElement.focus();\r
333         }\r
334         \r
335         // toggle the expanded state\r
336         this.setExpanded(!this.expanded);\r
337     }\r
338 }\r
339 \r
340 // Updates the style of the control to reflects the state of the control.\r
341 ContentPanel.prototype.updateStyleFromState = function() {\r
342     uiLogger.debug("ContentPanel.updateStyleFromState()");\r
343 \r
344     // determine the state name\r
345     var stateName = this.getStyleStateName();\r
346     \r
347     // set root element class name\r
348     this.setClassName(this.rootElement, "Control");\r
349 \r
350     // set the control assembly class names\r
351     this.setClassName(this.assemblyElement, "ControlAssembly ControlAssembly" + stateName);\r
352     \r
353     if (this.foldable) {\r
354         // foldable content panel\r
355         this.setClassName(this.captionElement, "ContentPanelCaptionFoldable");\r
356         this.setClassName(this.foldToggleElement, "ContentPanelFoldToggle ContentPanelFoldToggle" + (this.expanded ? "Expanded" : "Collapsed"));\r
357     } else {\r
358         // non-folding\r
359         this.setClassName(this.captionElement, "ContentPanelCaptionNonFoldable");\r
360     }\r
361     \r
362     // set the content caption text class names\r
363     this.setClassName(this.captionTextElement, "ContentPanelCaptionText ContentPanelCaptionText" + stateName);\r
364     \r
365     // set the content element class names\r
366     this.setClassName(this.contentElement, "ContentPanelContent");\r
367 }\r