xref: /dokuwiki/lib/scripts/linkwiz.js (revision 1961964f0a9023224b586dd7839f09f4180f00fd)
1/**
2 * The Link Wizard
3 *
4 * @author Andreas Gohr <gohr@cosmocode.de>
5 */
6var linkwiz = {
7    wiz:    null,
8    entry:  null,
9    result: null,
10    timer:  null,
11    sack:   null,
12    textArea: null,
13    selected: -1,
14    selection: null,
15
16    /**
17     * Initialize the linkwizard by creating the needed HTML
18     * and attaching the eventhandlers
19     */
20    init: function(textArea){
21        // prepare AJAX object
22        linkwiz.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
23        linkwiz.sack.AjaxFailedAlert = '';
24        linkwiz.sack.encodeURIString = false;
25
26        // create HTML Structure
27        linkwiz.wiz = document.createElement('div');
28        linkwiz.wiz.id = 'link__wiz';
29        linkwiz.wiz.className     = 'picker';
30        linkwiz.wiz.style.top  = (findPosY(textArea)+20)+'px';
31        linkwiz.wiz.style.left = (findPosX(textArea)+80)+'px';
32        linkwiz.wiz.style.marginLeft = '-10000px';
33
34        linkwiz.wiz.innerHTML =
35             '<div id="link__wiz_header">'+
36             '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+
37             LANG['linkwiz']+'</div>'+
38             '<div>'+LANG['linkto']+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
39             '<div id="link__wiz_result"></div>';
40        textArea.form.parentNode.appendChild(linkwiz.wiz);
41        linkwiz.textArea = textArea;
42        linkwiz.result = $('link__wiz_result');
43        linkwiz.entry = $('link__wiz_entry');
44
45        // attach event handlers
46        var obj;
47        obj = $('link__wiz_close');
48        obj.onclick = linkwiz.hide;
49
50        linkwiz.sack.elementObj = linkwiz.result;
51        addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);
52        addEvent(linkwiz.result,'click',linkwiz.onResultClick);
53        drag.attach(linkwiz.wiz,$('link__wiz_header'));
54    },
55
56    /**
57     * handle all keyup events in the entry field
58     */
59    onEntry: function(e){
60        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
61            return true; //ignore
62        }
63        if(e.keyCode == 27){
64            linkwiz.hide();
65            e.preventDefault();
66            e.stopPropagation();
67            return false;
68        }
69        if(e.keyCode == 38){ //Up
70            linkwiz.select(linkwiz.selected -1);
71            e.preventDefault();
72            e.stopPropagation();
73            return false;
74        }
75        if(e.keyCode == 40){ //Down
76            linkwiz.select(linkwiz.selected +1);
77            e.preventDefault();
78            e.stopPropagation();
79            return false;
80        }
81        if(e.keyCode == 13){ //Enter
82            if(linkwiz.selected > -1){
83                var obj = linkwiz.getResult(linkwiz.selected);
84                if(obj){
85                    var a = obj.getElementsByTagName('A')[0];
86                    linkwiz.resultClick(a);
87                }
88            }else if(linkwiz.entry.value){
89                linkwiz.insertLink(linkwiz.entry.value);
90            }
91
92            e.preventDefault();
93            e.stopPropagation();
94            return false;
95        }
96        linkwiz.autocomplete();
97    },
98
99    /**
100     * Get one of the result by index
101     *
102     * @param int result div to return
103     * @returns DOMObject or null
104     */
105    getResult: function(num){
106        var obj;
107        var childs = linkwiz.result.getElementsByTagName('DIV');
108        obj = childs[num];
109        if(obj){
110            return obj;
111        }else{
112            return null;
113        }
114    },
115
116    /**
117     * Select the given result
118     */
119    select: function(num){
120        if(num < 0){
121            linkwiz.deselect();
122            return;
123        }
124
125        var obj = linkwiz.getResult(num);
126        if(obj){
127            linkwiz.deselect();
128            obj.className += ' selected';
129
130            // make sure the item is viewable in the scroll view
131            // FIXME check IE compatibility
132            if(obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight){
133                linkwiz.result.scrollTop += obj.clientHeight;
134            }else if(obj.offsetTop - linkwiz.result.clientHeight < linkwiz.result.scrollTop){ // this works but isn't quite right, fixes welcome
135                linkwiz.result.scrollTop -= obj.clientHeight;
136            }
137            // now recheck - if still not in view, the user used the mouse to scroll
138            if( (obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight) ||
139                (obj.offsetTop < linkwiz.result.scrollTop) ){
140                obj.scrollIntoView();
141            }
142
143            linkwiz.selected = num;
144        }
145    },
146
147    /**
148     * deselect a result if any is selected
149     */
150    deselect: function(){
151        if(linkwiz.selected > -1){
152            var obj = linkwiz.getResult(linkwiz.selected);
153            if(obj){
154                obj.className = obj.className.replace(/ ?selected/,'');
155            }
156        }
157        linkwiz.selected = -1;
158    },
159
160    /**
161     * Handle clicks in the result set an dispatch them to
162     * resultClick()
163     */
164    onResultClick: function(e){
165        if(e.target.tagName != 'A') return;
166        e.stopPropagation();
167        e.preventDefault();
168        linkwiz.resultClick(e.target);
169        return false;
170    },
171
172    /**
173     * Handles the "click" on a given result anchor
174     */
175    resultClick: function(a){
176        var id = a.title;
177        if(id == '' || id.substr(id.length-1) == ':'){
178            linkwiz.entry.value = id;
179            linkwiz.autocomplete_exec();
180        }else{
181            linkwiz.entry.value = id;
182            if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){
183                linkwiz.insertLink(a.nextSibling.innerHTML);
184            }else{
185                linkwiz.insertLink('');
186            }
187        }
188    },
189
190    /**
191     * Insert the id currently in the entry box to the textarea,
192     * replacing the current selection or at the curso postion.
193     * When no selection is available the given title will be used
194     * as link title instead
195     */
196    insertLink: function(title){
197        if(!linkwiz.entry.value) return;
198
199        var sel = getSelection(linkwiz.textArea);
200        if(sel.start == 0 && sel.end == 0) sel = linkwiz.selection;
201
202        var stxt = sel.getText();
203        if(!stxt && !DOKU_UHC) stxt=title;
204
205        // prepend colon inside namespaces for non namespace pages
206        if(linkwiz.textArea.form['id'].value.indexOf(':') != -1 &&
207           linkwiz.entry.value.indexOf(':') == -1){
208            linkwiz.entry.value = ':'+linkwiz.entry.value;
209        }
210
211        var link = '[['+linkwiz.entry.value+'|';
212        if(stxt) link += stxt;
213        link += ']]';
214
215        var so = linkwiz.entry.value.length+3;
216        var eo = 2;
217
218        pasteText(sel,link,{startofs: so, endofs: eo});
219        linkwiz.hide();
220    },
221
222    /**
223     * Start the page/namespace lookup timer
224     *
225     * Calls autocomplete_exec when the timer runs out
226     */
227    autocomplete: function(){
228        if(linkwiz.timer !== null){
229            window.clearTimeout(linkwiz.timer);
230            linkwiz.timer = null;
231        }
232
233        linkwiz.timer = window.setTimeout(linkwiz.autocomplete_exec,350);
234    },
235
236    /**
237     * Executes the AJAX call for the page/namespace lookup
238     */
239    autocomplete_exec: function(){
240        linkwiz.deselect();
241        linkwiz.result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />';
242        linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));
243    },
244
245    /**
246     * Clears the result area
247     */
248    clear: function(){
249        linkwiz.result.innerHTML = 'Search for a matching page name above, or browse through the pages on the right';
250        linkwiz.entry.value = '';
251    },
252
253    /**
254     * Show the linkwizard
255     */
256    show: function(){
257        linkwiz.selection  = getSelection(linkwiz.textArea);
258        linkwiz.wiz.style.marginLeft = '0px';
259        linkwiz.entry.focus();
260        linkwiz.autocomplete();
261    },
262
263    /**
264     * Hide the link wizard
265     */
266    hide: function(){
267        linkwiz.wiz.style.marginLeft = '-10000px';
268        linkwiz.textArea.focus();
269    },
270
271    /**
272     * Toggle the link wizard
273     */
274    toggle: function(){
275        if(linkwiz.wiz.style.marginLeft == '-10000px'){
276            linkwiz.show();
277        }else{
278            linkwiz.hide();
279        }
280    }
281};
282
283