~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/media/common/util.js

Dispatch now generates an index for each plugin type, allowing plugins to
be written which are aware of other plugins, and other plugin types.

All view plugins now subclass from ivle.webapp.base.plugins.ViewPlugin,
as opposed to subclassing BasePlugin directly. This will allow us to
easily re-write console as an OverlayPlugin, and allow future new
plugins types to be created.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 * Defines some generic JavaScript utility functions.
23
23
 */
24
24
 
 
25
/* Expects the following variables to have been declared by JavaScript in
 
26
 * the HTML generated by the server:
 
27
 * - root_dir
 
28
 * - public_host
 
29
 * - username
 
30
 */
 
31
 
25
32
/** Removes all children of a given DOM element
26
33
 * \param elem A DOM Element. Will be modified.
27
34
 */
34
41
/** Creates a DOM element with simple text inside it.
35
42
 * \param tagname String. Name of the element's tag (eg. "p").
36
43
 * \param text String. Text to be placed inside the element.
 
44
 * \param title String, optional. Tooltip for the text.
 
45
 *  (Note, title creates a span element around the text).
37
46
 * \return DOM Element object.
38
47
 */
39
 
function dom_make_text_elem(tagname, text)
 
48
function dom_make_text_elem(tagname, text, title)
40
49
{
 
50
    if (text == null) text = "";
41
51
    var elem = document.createElement(tagname);
42
 
    elem.appendChild(document.createTextNode(text));
 
52
    var textnode;
 
53
    if (title == null)
 
54
        textnode = document.createTextNode(text);
 
55
    else
 
56
    {
 
57
        textnode = document.createElement("span");
 
58
        textnode.setAttribute("title", title);
 
59
        textnode.appendChild(document.createTextNode(text));
 
60
    }
 
61
    elem.appendChild(textnode);
43
62
    return elem;
44
63
}
45
64
 
46
65
/** Creates a DOM element with hyperlinked text inside it.
47
66
 * \param tagname String. Name of the element's tag (eg. "p").
48
67
 * \param text String. Text to be placed inside the element.
 
68
 * \param title String, optional. Sets a tooltip for the link.
49
69
 * \param href String. URL the text will link to. This is a raw string,
50
70
 *  it will automatically be URL-encoded.
51
71
 * \param onclick Optional string. Will be set as the "onclick" attribute
52
72
 *  of the "a" element.
 
73
 * \param dontencode Optional boolean. If true, will not encode the href.
 
74
 *  if including query strings, you must set this to true and use build_url
 
75
 *  to escape the URI correctly.
53
76
 * \return DOM Element object.
54
77
 */
55
 
function dom_make_link_elem(tagname, text, href, onclick)
 
78
function dom_make_link_elem(tagname, text, title, href, onclick, dontencode)
56
79
{
 
80
    if (text == null) text = "";
 
81
    if (href == null) href = "";
57
82
    var elem = document.createElement(tagname);
58
83
    var link = document.createElement("a");
59
 
    link.setAttribute("href", encodeURI(href));
 
84
    if (dontencode != true)
 
85
        href = urlencode_path(href);
 
86
    link.setAttribute("href", href);
 
87
    if (title != null)
 
88
        link.setAttribute("title", title);
60
89
    if (onclick != null)
61
90
        link.setAttribute("onclick", onclick);
62
91
    link.appendChild(document.createTextNode(text));
64
93
    return elem;
65
94
}
66
95
 
67
 
/** Converts a list of directories into a path name, with a slash at the end.
68
 
 * \param pathlist List of strings.
 
96
/** Creates a DOM img element. All parameters are optional except src.
 
97
 * If alt (compulsory in HTML) is omitted, will be set to "".
 
98
 */
 
99
function dom_make_img(src, width, height, title, alt)
 
100
{
 
101
    var img = document.createElement("img");
 
102
    img.setAttribute("src", urlencode_path(src));
 
103
    if (width != null)
 
104
        img.setAttribute("width", width);
 
105
    if (height != null)
 
106
        img.setAttribute("height", height);
 
107
    if (title != null)
 
108
        img.setAttribute("title", title);
 
109
    if (alt == null) alt = "";
 
110
    img.setAttribute("alt", alt);
 
111
    return img;
 
112
}
 
113
 
 
114
/** Given a number of bytes, returns a string representing the file size in a
 
115
 * human-readable format.
 
116
 * eg. nice_filesize(6) -> "6 bytes"
 
117
 *     nice_filesize(81275) -> "79.4 kB"
 
118
 *     nice_filesize(13498346) -> "12.9 MB"
 
119
 * \param bytes Number of bytes. Must be an integer.
69
120
 * \return String.
70
121
 */
71
 
function pathlist_to_path(pathlist)
 
122
function nice_filesize(bytes)
72
123
{
73
 
    ret = "";
74
 
    for (var i=0; i<pathlist.length; i++)
75
 
    {
76
 
        ret += pathlist[i] + "/";
77
 
    }
78
 
    return ret;
 
124
    if (bytes == null) return "";
 
125
    var size;
 
126
    if (bytes < 1024)
 
127
        return bytes.toString() + " B";
 
128
    size = bytes / 1024;
 
129
    if (size < 1024)
 
130
        return size.toFixed(1) + " kB";
 
131
    size = size / 1024;
 
132
    if (size < 1024)
 
133
        return size.toFixed(1) + " MB";
 
134
    size = size / 1024;
 
135
    return size.toFixed(1) + " GB";
79
136
}
80
137
 
81
138
/** Given a URL, returns an object containing a number of attributes
97
154
 * whose names appear multiple times.
98
155
 * args is never null, though it may be empty.
99
156
 *
100
 
 * The args strings are decoded from URL encoding form. Other strings are left
101
 
 * in raw URL form.
 
157
 * All strings are decoded/unescaped. Reserved characters
 
158
 * (; , / ? : @ & = + * $) are not decoded except in args and path.
102
159
 *
103
160
 * \param url String. A URL. To read from the current browser window, use
104
161
 *  window.location.href.
131
188
    else
132
189
    {
133
190
        serverpart = url.substr(0, index);
134
 
        url = url.substr(index+1);
 
191
        url = url.substr(index);
135
192
    }
136
193
 
137
194
    /* Split server name from port */
167
224
            obj.query_string = url.substr(index+1);
168
225
        }
169
226
    }
 
227
    obj.path = decodeURIComponent(obj.path);
170
228
 
171
229
    /* Split query string into arguments */
172
230
    args = {};
182
240
            /* Ignore malformed args */
183
241
            if (index >= 0)
184
242
            {
185
 
                arg_key = decodeURI(arg_str.substr(0, index));
186
 
                arg_val = decodeURI(arg_str.substr(index+1));
 
243
                arg_key = decodeURIComponent(arg_str.substr(0, index));
 
244
                arg_val = decodeURIComponent(arg_str.substr(index+1));
187
245
                if (arg_key in args)
188
246
                {
189
247
                    /* Collision - make an array */
202
260
    return obj;
203
261
}
204
262
 
 
263
/** Builds a query_string from an args object. Encodes the arguments.
 
264
 * \param args Args object as described in parse_url.
 
265
 * \return Query string portion of a URL.
 
266
 */
 
267
function make_query_string(args)
 
268
{
 
269
    var query_string = "";
 
270
    var arg_val;
 
271
    for (var arg_key in args)
 
272
    {
 
273
        arg_val = args[arg_key];
 
274
        if (arg_val instanceof Array)
 
275
            for (var i=0; i<arg_val.length; i++)
 
276
                query_string += "&" + encodeURIComponent(arg_key) + "=" +
 
277
                    encodeURIComponent(arg_val[i]);
 
278
        else
 
279
            query_string += "&" + encodeURIComponent(arg_key) + "=" +
 
280
                encodeURIComponent(arg_val);
 
281
    }
 
282
    if (query_string != "")
 
283
        /* Drop the first "&" */
 
284
        query_string = query_string.substr(1);
 
285
 
 
286
    return query_string;
 
287
}
 
288
 
205
289
/** Given an object exactly of the form described for the output of parseurl,
206
 
 * returns a URL string built from those parameters.
 
290
 * returns a URL string built from those parameters. The URL is properly
 
291
 * encoded.
207
292
 * parseurl and buildurl are strict inverses of each other.
208
293
 * Note that either query_string or args may be supplied. If both are
209
294
 * supplied, query_string is preferred (because it keeps the argument order).
212
297
 * \param obj Object as returned by parseurl.
213
298
 * \return String, a URL.
214
299
 */
215
 
function buildurl(obj)
 
300
function build_url(obj)
216
301
{
217
302
    var url = "";
218
303
    var query_string = null;
219
304
 
220
 
    if (!("scheme" in obj) || obj.scheme != null)
 
305
    if (("scheme" in obj) && obj.scheme != null)
221
306
        url = obj.scheme.toString() + "://";
222
 
    if (!("server_name" in obj) || obj.server_name != null)
 
307
    if (("server_name" in obj) && obj.server_name != null)
223
308
        url += obj.server_name.toString();
224
 
    if (!("server_port" in obj) || obj.server_port != null)
 
309
    if (("server_port" in obj) && obj.server_port != null)
225
310
        url += ":" + obj.server_port.toString();
226
 
    if (!("path" in obj) || obj.path != null)
 
311
    if (("path" in obj) && obj.path != null)
227
312
    {
228
 
        var path = obj.path.toString();
229
 
        if (path.length > 0 && path[0] != "/")
 
313
        var path = urlencode_path(obj.path.toString());
 
314
        if (url.length > 0 && path.length > 0 && path.charAt(0) != "/")
230
315
            path = "/" + path;
231
316
        url += path;
232
317
    }
233
 
    if (!("query_string" in obj) || obj.query_string != null)
234
 
        query_string = obj.query_string.toString();
235
 
    else if (!("args" in obj) || obj.args != null)
236
 
    {
237
 
        query_string = "";
238
 
        var arg_val;
239
 
        for (var arg_key in obj.args)
240
 
        {
241
 
            arg_val = obj.args[arg_key];
242
 
            if (arg_val instanceof Array)
243
 
                for (var i=0; i<arg_val.length; i++)
244
 
                    query_string += "&" + encodeURI(arg_key) + "=" +
245
 
                        encodeURI(arg_val[i]);
246
 
            else
247
 
                query_string += "&" + encodeURI(arg_key) + "=" +
248
 
                    encodeURI(arg_val);
249
 
   
250
 
        }
251
 
        if (query_string == "")
252
 
            query_string = null;
253
 
        else
254
 
            /* Drop the first "&" */
255
 
            query_string = query_string.substr(1);
256
 
    }
 
318
    if (("query_string" in obj) && obj.query_string != null)
 
319
        query_string = encodeURI(obj.query_string.toString());
 
320
    else if (("args" in obj) && obj.args != null)
 
321
        query_string = make_query_string(obj.args);
257
322
 
258
 
    if (query_string != null)
 
323
    if (query_string != "" && query_string != null)
259
324
        url += "?" + query_string;
260
325
 
261
326
    return url;
262
327
}
263
328
 
 
329
/** URL-encodes a path. This is a special case of URL encoding as all
 
330
 * characters *except* the slash must be encoded.
 
331
 */
 
332
function urlencode_path(path)
 
333
{
 
334
    /* Split up the path, URLEncode each segment with encodeURIComponent,
 
335
     * and rejoin.
 
336
     */
 
337
    var split = path.split('/');
 
338
    for (var i=0; i<split.length; i++)
 
339
        split[i] = encodeURIComponent(split[i]);
 
340
    path = path_join.apply(null, split);
 
341
    if (split[0] == "" && split.length > 1) path = "/" + path;
 
342
    return path;
 
343
}
 
344
 
 
345
/** Writes a JSONable object to the cookie under a particular key
 
346
 * (JSON encoded and URL encoded).
 
347
 */
 
348
function write_cookie(key, value)
 
349
{
 
350
    var sendstr = encodeURIComponent(key) + "="
 
351
        + encodeURIComponent(JSON.stringify(value))
 
352
        + "; path=" + urlencode_path(root_dir);
 
353
    /* This actually just assigns to the key, not replacing the whole cookie
 
354
     * as it appears to. */
 
355
    document.cookie = sendstr;
 
356
}
 
357
/** Reads a cookie which has a JSONable object encoded as its value.
 
358
 * Returns the object, parsed from JSON.
 
359
 */
 
360
function read_cookie(key)
 
361
{
 
362
    var cookies = document.cookie.split(";");
 
363
    var checkstart = encodeURIComponent(key) + "=";
 
364
    var checklen = checkstart.length;
 
365
    for (var i=0; i<cookies.length; i++)
 
366
    {
 
367
        var cookie = cookies[i];
 
368
        while (cookie[0] == ' ')
 
369
            cookie = cookie.substr(1);
 
370
        if (cookie.substr(0, checklen) == checkstart)
 
371
        {
 
372
            var valstr = cookie.substr(checklen);
 
373
            valstr = decodeURIComponent(valstr);
 
374
            return JSON.parse(valstr);
 
375
        }
 
376
    }
 
377
}
 
378
 
264
379
/** Given an argument map, as output in the args parameter of the return of
265
380
 * parseurl, gets the first occurence of an argument in the URL string.
266
381
 * If the argument was not found, returns null.
300
415
    else
301
416
        return [r];
302
417
}
 
418
 
 
419
/** Joins one or more paths together. Accepts 1 or more arguments.
 
420
 */
 
421
function path_join(path1 /*, path2, ... */)
 
422
{
 
423
    var arg;
 
424
    var path = "";
 
425
    for (var i=0; i<arguments.length; i++)
 
426
    {
 
427
        arg = arguments[i];
 
428
        if (arg.length == 0) continue;
 
429
        if (arg.charAt(0) == '/')
 
430
            path = arg;
 
431
        else
 
432
        {
 
433
            if (path.length > 0 && path.charAt(path.length-1) != '/')
 
434
                path += '/';
 
435
            path += arg;
 
436
        }
 
437
    }
 
438
    return path;
 
439
}
 
440
 
 
441
 
 
442
/** Builds a multipart_formdata string from an args object. Similar to
 
443
 * make_query_string, but it returns data of type "multipart/form-data"
 
444
 * instead of "application/x-www-form-urlencoded". This is good for
 
445
 * encoding large strings such as text objects from the editor.
 
446
 * Should be written with a Content-Type of
 
447
 * "multipart/form-data, boundary=<boundary>".
 
448
 * All fields are sent with a Content-Type of text/plain.
 
449
 * \param args Args object as described in parse_url.
 
450
 * \param boundary Random "magic" string which DOES NOT appear in any of
 
451
 *  the argument values. This should match the "boundary=" value written to
 
452
 *  the Content-Type header.
 
453
 * \return String in multipart/form-data format.
 
454
 */
 
455
function make_multipart_formdata(args, boundary)
 
456
{
 
457
    var data = "";
 
458
    var arg_val;
 
459
    /* Mutates data */
 
460
    var extend_data = function(arg_key, arg_val)
 
461
    {
 
462
        /* FIXME: Encoding not supported here (should not matter if we
 
463
         * only use ASCII names */
 
464
        data += "--" + boundary + "\r\n"
 
465
            + "Content-Disposition: form-data; name=\"" + arg_key
 
466
            + "\"\r\n\r\n"
 
467
            + arg_val + "\r\n";
 
468
    }
 
469
 
 
470
    for (var arg_key in args)
 
471
    {
 
472
        arg_val = args[arg_key];
 
473
        if (arg_val instanceof Array)
 
474
            for (var i=0; i<arg_val.length; i++)
 
475
            {
 
476
                extend_data(arg_key, arg_val[i]);
 
477
            }
 
478
        else
 
479
            extend_data(arg_key, arg_val);
 
480
    }
 
481
    /* End boundary */
 
482
    data += "--" + boundary + "--\r\n";
 
483
 
 
484
    return data;
 
485
}
 
486
 
 
487
/** Converts a list of directories into a path name, with a slash at the end.
 
488
 * \param pathlist List of strings.
 
489
 * \return String.
 
490
 */
 
491
function pathlist_to_path(pathlist)
 
492
{
 
493
    ret = path_join.apply(null, pathlist);
 
494
    if (ret.charAt(ret.length-1) != '/')
 
495
        ret += '/';
 
496
    return ret;
 
497
}
 
498
 
 
499
/** Given a path relative to the IVLE root, gives a path relative to
 
500
 * the site root.
 
501
 */
 
502
function make_path(path)
 
503
{
 
504
    return path_join(root_dir, path);
 
505
}
 
506
 
 
507
/** Shorthand for make_path(path_join(app, ...))
 
508
 * Creates an absolute path for a given path within a given app.
 
509
 */
 
510
function app_path(app /*,...*/)
 
511
{
 
512
    return make_path(path_join.apply(null, arguments));
 
513
}
 
514
 
 
515
/** Generates an absolute URL to a public application
 
516
 */
 
517
function public_app_path(app /*,...*/)
 
518
{
 
519
    return location.protocol + "//" + public_host
 
520
        + make_path(path_join.apply(null, arguments));
 
521
}
 
522
 
 
523
/** Given a path, gets the "basename" (the last path segment).
 
524
 */
 
525
function path_basename(path)
 
526
{
 
527
    segments = path.split("/");
 
528
    if (segments[segments.length-1].length == 0)
 
529
        return segments[segments.length-2];
 
530
    else
 
531
        return segments[segments.length-1];
 
532
}
 
533
 
 
534
/** Given a string str, determines whether it ends with substr */
 
535
function endswith(str, substring)
 
536
{
 
537
    if (str.length < substring.length) return false;
 
538
    return str.substr(str.length - substring.length) == substring;
 
539
}
 
540
 
 
541
/** Removes all occurences of a value from an array.
 
542
 */
 
543
Array.prototype.removeall = function(val)
 
544
{
 
545
    var i, j;
 
546
    var arr = this;
 
547
    j = 0;
 
548
    for (i=0; i<arr.length; i++)
 
549
    {
 
550
        arr[j] = arr[i];
 
551
        if (arr[i] != val) j++;
 
552
    }
 
553
    arr.splice(j, i-j);
 
554
}
 
555
 
 
556
/** Shallow-clones an object */
 
557
function shallow_clone_object(obj)
 
558
{
 
559
    o = {};
 
560
    for (k in obj)
 
561
        o[k] = obj[k];
 
562
    return o;
 
563
}
 
564
 
 
565
/** Returns a new XMLHttpRequest object, in a somewhat browser-agnostic
 
566
 * fashion.
 
567
 */
 
568
function new_xmlhttprequest()
 
569
{
 
570
    try
 
571
    {
 
572
        /* Real Browsers */
 
573
        return new XMLHttpRequest();
 
574
    }
 
575
    catch (e)
 
576
    {
 
577
        /* Internet Explorer */
 
578
        try
 
579
        {
 
580
            return new ActiveXObject("Msxml2.XMLHTTP");
 
581
        }
 
582
        catch (e)
 
583
        {
 
584
            try
 
585
            {
 
586
                return new ActiveXObject("Microsoft.XMLHTTP");
 
587
            }
 
588
            catch (e)
 
589
            {
 
590
                throw("Your browser does not support AJAX. "
 
591
                    + "IVLE requires a modern browser.");
 
592
            }
 
593
        }
 
594
    }
 
595
}
 
596
 
 
597
/** Creates a random string of length length,
 
598
 * consisting of alphanumeric characters.
 
599
 */
 
600
var rand_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ"
 
601
               + "abcdefghiklmnopqrstuvwxyz";
 
602
function random_string(length)
 
603
{
 
604
    var str = Array(length);
 
605
    var v;
 
606
    for (var i=0; i<length; i++)
 
607
    {
 
608
        v = Math.floor(Math.random() * rand_chars.length);
 
609
        str[i] = rand_chars.charAt(v);
 
610
    }
 
611
    return str.join('');
 
612
}
 
613
 
 
614
/** Makes an XMLHttpRequest call to the server.
 
615
 * Sends the XMLHttpRequest object containing the completed response to a
 
616
 * specified callback function.
 
617
 *
 
618
 * \param callback A callback function. Will be called when the response is
 
619
 *      complete. Passed 1 parameter, an XMLHttpRequest object containing the
 
620
 *      completed response. If callback is null this is a syncronous request 
 
621
 *      otherwise this is an asynchronous request.
 
622
 * \param app IVLE app to call (such as "fileservice").
 
623
 * \param path URL path to make the request to, within the application.
 
624
 * \param args Argument object, as described in parse_url and friends.
 
625
 * \param method String; "GET", "POST", "PUT", or "PATCH"
 
626
 * \param content_type String, optional.
 
627
 *      Defaults to "application/x-www-form-urlencoded".
 
628
 */
 
629
function ajax_call(callback, app, path, args, method, content_type)
 
630
{
 
631
    if (!content_type)
 
632
        content_type = "application/x-www-form-urlencoded";
 
633
    path = app_path(app, path);
 
634
    var url;
 
635
    /* A random string, for multipart/form-data
 
636
     * (This is not checked against anywhere else, it is solely defined and
 
637
     * used within this function) */
 
638
    var boundary = random_string(20);
 
639
    var xhr = new_xmlhttprequest();
 
640
    var asyncronous = callback != null;
 
641
    if (asyncronous)
 
642
    {
 
643
        xhr.onreadystatechange = function()
 
644
            {
 
645
                if (xhr.readyState == 4)
 
646
                {
 
647
                    callback(xhr);
 
648
                }
 
649
            }
 
650
    }
 
651
    if (method == "GET")
 
652
    {
 
653
        /* GET sends the args in the URL */
 
654
        url = build_url({"path": path, "args": args});
 
655
        /* open's 3rd argument = true -> asynchronous */
 
656
        xhr.open(method, url, asyncronous);
 
657
        xhr.send(null);
 
658
    }
 
659
    else
 
660
    {
 
661
        /* POST & PUT & PATCH sends the args in the request body */
 
662
        url = encodeURI(path);
 
663
        xhr.open(method, url, asyncronous);
 
664
        var message;
 
665
        if (content_type == "multipart/form-data")
 
666
        {
 
667
            xhr.setRequestHeader("Content-Type",
 
668
                "multipart/form-data; boundary=" + boundary);
 
669
            message = make_multipart_formdata(args, boundary);
 
670
        }
 
671
        else if (content_type == "application/x-www-form-urlencoded")
 
672
        {
 
673
            xhr.setRequestHeader("Content-Type", content_type);
 
674
            message = make_query_string(args);
 
675
        }
 
676
        else if (content_type == "application/json")
 
677
        {
 
678
            xhr.setRequestHeader("Content-Type", content_type);
 
679
            message = JSON.stringify(args);
 
680
        }
 
681
        else
 
682
        {
 
683
            xhr.setRequestHeader("Content-Type", content_type);
 
684
            message = args;
 
685
        }
 
686
        xhr.send(message);
 
687
    }
 
688
    /* Only return the XHR for syncronous requests */
 
689
    if (!asyncronous)
 
690
    { 
 
691
        return xhr;
 
692
    }
 
693
}
 
694
 
 
695
/** Attempts to JSON decodes a response object
 
696
 * If a non-200 response or the JSON decode fails then returns null
 
697
 */
 
698
function decode_response(response)
 
699
{
 
700
    if (response.status == 200)
 
701
    {
 
702
        try
 
703
        {
 
704
            var responseText = response.responseText;
 
705
            return JSON.parse(responseText);
 
706
        }
 
707
        catch (e)
 
708
        {
 
709
            // Pass
 
710
        }
 
711
     }
 
712
    
 
713
     return null;
 
714
}