OILS / web / ajax.js View on Github | oilshell.org

149 lines, 128 significant
1// Minimal AJAX library.
2//
3// The motivation is that we want to generate PNG, JSON, CSV, etc. from R.
4// And maybe some HTML fragments. But we don't want to generate a different
5// skeleton for every page. It's nice just to hit F5 and see the changes
6// reloaded. It's like "PHP in the browser'.
7
8'use strict';
9
10// Append a message to an element. Used for errors.
11function appendMessage(elem, msg) {
12 elem.innerHTML += msg + '<br />';
13}
14
15// jQuery-like AJAX helper, but simpler.
16
17// Requires an element with id "status" to show errors.
18//
19// Args:
20// errElem: optional element to append error messages to. If null, then
21// alert() on error.
22// success: callback that is passed the xhr object.
23function ajaxGet(url, errElem, success) {
24 var xhr = new XMLHttpRequest();
25 xhr.open('GET', url, true /*async*/);
26 xhr.onreadystatechange = function() {
27 if (xhr.readyState != 4 /*DONE*/) {
28 return;
29 }
30
31 if (xhr.status != 200) {
32 var msg = 'ERROR requesting ' + url + ': ' + xhr.status + ' ' +
33 xhr.statusText;
34 if (errElem) {
35 appendMessage(errElem, msg);
36 } else {
37 alert(msg);
38 }
39 return;
40 }
41
42 success(xhr);
43 };
44 xhr.send();
45}
46
47function jsonGet(url, errElem, success) {
48 ajaxGet(url, errElem, function(xhr) {
49 try {
50 var j = JSON.parse(xhr.responseText);
51 } catch (e) {
52 appendMessage(errElem, `Parsing JSON in ${url} failed`);
53 }
54 success(j);
55 });
56}
57
58function htmlEscape(unsafe) {
59 return unsafe
60 .replace(/&/g, "&amp;")
61 .replace(/</g, "&lt;")
62 .replace(/>/g, "&gt;")
63 .replace(/"/g, "&quot;")
64 .replace(/'/g, "&#039;");
65}
66
67//
68// UrlHash
69//
70
71// helper
72function _decode(s) {
73 var obj = {};
74 var parts = s.split('&');
75 for (var i = 0; i < parts.length; ++i) {
76 if (parts[i].length === 0) {
77 continue; // quirk: ''.split('&') is [''] ? Should be a 0-length array.
78 }
79 var pair = parts[i].split('=');
80 obj[pair[0]] = pair[1]; // for now, assuming no =
81 }
82 return obj;
83}
84
85function _encode(d) {
86 var parts = [];
87 for (var name in d) {
88 var s = name;
89 s += '=';
90 var value = d[name];
91 s += encodeURIComponent(value);
92 parts.push(s);
93 }
94 return parts.join('&');
95}
96
97
98// UrlHash Constructor.
99// Args:
100// hashStr: location.hash
101function UrlHash(hashStr) {
102 this.reset(hashStr);
103}
104
105UrlHash.prototype.reset = function(hashStr) {
106 var h = hashStr.substring(1); // without leading #
107 // Internal storage is string -> string
108 this.dict = _decode(h);
109}
110
111UrlHash.prototype.set = function(name, value) {
112 this.dict[name] = value;
113};
114
115UrlHash.prototype.del = function(name) {
116 delete this.dict[name];
117};
118
119UrlHash.prototype.get = function(name ) {
120 return this.dict[name];
121};
122
123// e.g. Table states have keys which start with 't:'.
124UrlHash.prototype.getKeysWithPrefix = function(prefix) {
125 var keys = [];
126 for (var name in this.dict) {
127 if (name.indexOf(prefix) === 0) {
128 keys.push(name);
129 }
130 }
131 return keys;
132};
133
134// Return a string reflecting internal key-value pairs.
135UrlHash.prototype.encode = function() {
136 return _encode(this.dict);
137};
138
139// Useful for AJAX navigation. If UrlHash is the state of the current page,
140// then we override the state with 'attrs' and then return a serialized query
141// fragment.
142UrlHash.prototype.modifyAndEncode = function(attrs) {
143 var copy = {}
144 // NOTE: Object.assign is ES6-only
145 // https://googlechrome.github.io/samples/object-assign-es6/
146 Object.assign(copy, this.dict, attrs);
147 return _encode(copy);
148};
149