Rework sizing and positioning of details pane popup.
[quanweb.git] / js / src / ToolTip.js
1 // =======
2 // ToolTip
3
4 var ToolTip = (function () {
5     // =================
6     // Private variables
7     var my = {},
8         bookId = undefined,
9         milliSecs = 500,     // time to wait before displaying tooltip
10         mousePos = {
11             x: undefined,
12             y: undefined
13         },
14         threshold = 10,        // number of pixels that mouse can move before tip is dismissed
15         timer = undefined;
16
17     // ================
18     // Public variables
19     my.booksModel = undefined;
20
21     // ==============
22     // Public Methods
23     
24     // Set the book ID for the details pane, and then show it
25     my.displayDetails = function (newBookId) {
26         bookId = newBookId;
27         my.showDetails();
28     };
29
30     // Hide the details pane, if it is currently visible
31     my.hideDetails = function () {
32         mousePos.x = undefined;
33         mousePos.y = undefined;
34         
35         var elem = Browser.getElementById('details');
36         elem.innerHTML = '';
37         elem.style.display = 'none';
38     };
39     
40     my.mouseMoved = function (x, y) {
41         // Is there an active tooltip?
42         if (typeof mousePos.x === 'undefined') {
43             // No active tooltip, so nothing further to do
44             return;
45         }
46         
47         var deltaX = Math.abs(x - mousePos.x);
48         var deltaY = Math.abs(y - mousePos.y);
49
50         if (  (deltaX > threshold)
51            || (deltaY > threshold) )
52         {
53             my.stopTooltipTimer();
54             my.hideDetails();
55         }
56     };
57
58     // Show the details pane
59     my.showDetails = function () {
60         var id = bookId;
61         var detailsElem = Browser.getElementById('details');
62         var index = BooksModel.map[id];
63         var book = BooksModel.cache[index];
64         var html = '<div><p><b>' + book.Title + '</b></p>'
65         + '<p><i>' + ce(book.AuthorReading) + '<br/>' + ce(book.Series) + ' ' + ce(book.Volume) + '</i></p></div><div>'
66         + ce(book.Description)
67         + '</div>';
68
69         // Remember the current mouse (x,y).
70         // If we move the mouse too far from this point, that will trigger hiding the tooltip.
71         mousePos.x = g_state.mousePos.x;
72         mousePos.y = g_state.mousePos.y;
73
74         detailsElem.innerHTML = html;
75
76         detailsElem.style.display = 'block';    // show, and calculate size, so that we can query it below
77         
78         var x = mousePos.x;
79         var y = mousePos.y;
80         
81         var detailsBcr = detailsElem.getBoundingClientRect();
82
83         const viewportArea = window.innerWidth * window.innerHeight;
84         const detailsArea = detailsBcr.width * detailsBcr.height;
85
86         const ratio = detailsArea / viewportArea;
87
88         const adjustedWidth = Math.min(
89           window.innerWidth,
90           Math.max(100, Math.ceil(window.innerWidth * ratio * 2))
91         );
92         detailsElem.style.width = adjustedWidth + 'px';
93
94         detailsBcr = detailsElem.getBoundingClientRect();
95
96         const leftOffset = (detailsBcr.width / 2);
97         const topOffset = (detailsBcr.height / 2);
98         
99         x = Math.max(0, x - leftOffset);
100         y = Math.max(0, y - topOffset);
101
102         detailsElem.style.left = x + 'px';
103         detailsElem.style.top = y + 'px';
104     };
105
106     my.startTooltipTimer = function (newBookId) {
107         if (typeof timer !== 'undefined') {
108             clearTimeout(timer);
109         }
110         bookId = newBookId;
111         timer = setTimeout(my.showDetails, milliSecs);
112     };
113
114     my.stopTooltipTimer = function () {
115         if (typeof timer === 'undefined') {
116             return;
117         }
118         
119         clearTimeout(timer);
120         timer = undefined;
121     };
122     
123     // ===============
124     // Private methods
125     
126     // ce(s):  "clear if empty()"
127     //         return s, unless it's undefined, in which case return an empty ("clear") string
128     function ce(s) {
129         if (typeof s !== 'undefined') {
130             return s;
131         }
132         return '';
133     }
134     
135     return my;
136 })();