Check-in [796dcfe072]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
SHA1 Hash:796dcfe0728bb2bc865db84e50432f0205d1d2fd
Date: 2011-11-04 20:57:52
User: drh
Edited Comment:Merge the json branch into trunk. Json is disabled by default for now. Use the --enable-json option to configure, or set FOSSIL_ENABLE_JSON in the makefile to turn json processing on.
Original Comment:Merge the json branch into trunk. Json is disabled by default for now. Use the --json option to configure, or set FOSSIL_ENABLE_JSON in the makefile to turn json processing on.
Tags And Properties
Changes

Added ajax/README

> 1 This is the README for how to set up the Fossil/JSON test web page > 2 under Apache on Unix systems. This is only intended only for > 3 Fossil/JSON developers/tinkerers: > 4 > 5 First, copy cgi-bin/fossil-json.cgi.example to > 6 cgi-bin/fossil-json.cgi. Edit it and correct the paths to the fossil > 7 binary and the repo you want to serve. Make it executable. > 8 > 9 MAKE SURE that the fossil repo you use is world-writable OR that your > 10 Web/CGI server is set up to run as the user ID of the owner of the > 11 fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be > 12 writable by the CGI process. > 13 > 14 Next, set up an apache vhost entry. Mine looks like: > 15 > 16 <VirtualHost *:80> > 17 ServerAlias fjson > 18 ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/ > 19 DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax > 20 </VirtualHost> > 21 > 22 Now add your preferred vhost name (fjson in the above example) to /etc/hosts: > 23 > 24 127.0.0.1 ...other aliases... fjson > 25 > 26 Restart your Apache. > 27 > 28 Now visit: http://fjson/ > 29 > 30 that will show the test/demo page. If it doesn't, edit index.html and > 31 make sure that: > 32 > 33 WhAjaj.Connector.options.ajax.url = ...; > 34 > 35 points to your CGI script. In theory you can also do this over fossil > 36 standalone server mode, but i haven't yet tested that particular test > 37 page in that mode. > 38

Added ajax/cgi-bin/fossil-json.cgi.example

> 1 #!/path/to/fossil/binary > 2 repository: /path/to/repo.fsl

Added ajax/i-test/rhino-shell.js

> 1 var FShell = { > 2 serverUrl: > 3 'http://localhost:8080' > 4 //'http://fjson/cgi-bin/fossil-json.cgi' > 5 //'http://192.168.1.62:8080' > 6 //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' > 7 , > 8 verbose:false, > 9 prompt:"fossil shell > ", > 10 wiki:{}, > 11 consol:java.lang.System.console(), > 12 v:function(msg){ > 13 if(this.verbose){ > 14 print("VERBOSE: "+msg); > 15 } > 16 } > 17 }; > 18 (function bootstrap() { > 19 var srcdir = '../js/'; > 20 var includes = [srcdir+'json2.js', > 21 srcdir+'whajaj.js', > 22 srcdir+'fossil-ajaj.js' > 23 ]; > 24 for( var i in includes ) { > 25 load(includes[i]); > 26 } > 27 WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; > 28 FShell.fossil = new FossilAjaj({ > 29 asynchronous:false, /* rhino-based impl doesn't support async. */ > 30 timeout:10000, > 31 url:FShell.serverUrl > 32 }); > 33 print("Server: "+FShell.serverUrl); > 34 var cb = FShell.fossil.ajaj.callbacks; > 35 cb.beforeSend = function(req,opt){ > 36 if(!FShell.verbose) return; > 37 print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); > 38 if(req) print("Request envelope="+WhAjaj.stringify(req)); > 39 }; > 40 cb.afterSend = function(req,opt){ > 41 //if(!FShell.verbose) return; > 42 //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); > 43 //if(req) print("Request="+WhAjaj.stringify(req)); > 44 }; > 45 cb.onError = function(req,opt){ > 46 //if(!FShell.verbose) return; > 47 print("ERROR: "+WhAjaj.stringify(opt)); > 48 }; > 49 cb.onResponse = function(resp,req){ > 50 if(!FShell.verbose) return; > 51 if(resp && resp.resultCode){ > 52 print("Response contains error info: "+resp.resultCode+": "+resp.res > 53 } > 54 print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringi > 55 }; > 56 FShell.fossil.HAI({ > 57 onResponse:function(resp,opt){ > 58 assertResponseOK(resp); > 59 } > 60 }); > 61 })(); > 62 > 63 /** > 64 Throws an exception of cond is a falsy value. > 65 */ > 66 function assert(cond, descr){ > 67 descr = descr || "Undescribed condition."; > 68 if(!cond){ > 69 throw new Error("Assertion failed: "+descr); > 70 }else{ > 71 //print("Assertion OK: "+descr); > 72 } > 73 } > 74 > 75 /** > 76 Convenience form of FShell.fossil.sendCommand(command,payload,ajajOpt). > 77 */ > 78 function send(command,payload, ajajOpt){ > 79 FShell.fossil.sendCommand(command,payload,ajajOpt); > 80 } > 81 > 82 /** > 83 Asserts that resp is-a Object, resp.fossil is-a string, and > 84 !resp.resultCode. > 85 */ > 86 function assertResponseOK(resp){ > 87 assert('object' === typeof resp,'Response is-a object.'); > 88 assert( 'string' === typeof resp.fossil, 'Response contains fossil property. > 89 assert( !resp.resultCode, 'resp.resultCode='+resp.resultCode); > 90 } > 91 /** > 92 Asserts that resp is-a Object, resp.fossil is-a string, and > 93 resp.resultCode is a truthy value. If expectCode is set then > 94 it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). > 95 */ > 96 function assertResponseError(resp,expectCode){ > 97 assert('object' === typeof resp,'Response is-a object.'); > 98 assert( 'string' === typeof resp.fossil, 'Response contains fossil property. > 99 assert( resp.resultCode, 'resp.resultCode='+resp.resultCode); > 100 if(expectCode){ > 101 assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code > 102 } > 103 } > 104 > 105 FShell.readline = (typeof readline === 'function') ? (readline) : (function() { > 106 importPackage(java.io); > 107 importPackage(java.lang); > 108 var stdin = new BufferedReader(new InputStreamReader(System['in'])); > 109 var self = this; > 110 return function(prompt) { > 111 if(prompt) print(prompt); > 112 var x = stdin.readLine(); > 113 return null===x ? x : String(x) /*convert to JS string!*/; > 114 }; > 115 }()); > 116 > 117 FShell.dispatchLine = function(line){ > 118 var av = line.split(' '); // FIXME: to shell-like tokenization. Too tired! > 119 var cmd = av[0]; > 120 var key, h; > 121 if('/' == cmd[0]) key = '/'; > 122 else key = this.commandAliases[cmd]; > 123 if(!key) key = cmd; > 124 h = this.commandHandlers[key]; > 125 if(!h){ > 126 print("Command not known: "+cmd +" ("+key+")"); > 127 }else if(!WhAjaj.isFunction(h)){ > 128 print("Not a function: "+key); > 129 } > 130 else{ > 131 print("Sending ["+key+"] command... "); > 132 try{h(av);} > 133 catch(e){ print("EXCEPTION: "+e); } > 134 } > 135 }; > 136 > 137 FShell.onResponseDefault = function(callback){ > 138 return function(resp,req){ > 139 assertResponseOK(resp); > 140 print("Payload: "+(resp.payload ? WhAjaj.stringify(resp.payload) : "none > 141 if(WhAjaj.isFunction(callback)){ > 142 callback(resp,req); > 143 } > 144 }; > 145 }; > 146 FShell.commandHandlers = { > 147 "?":function(args){ > 148 var k; > 149 print("Available commands...\n"); > 150 var o = FShell.commandHandlers; > 151 for(k in o){ > 152 if(! o.hasOwnProperty(k)) continue; > 153 print("\t"+k); > 154 } > 155 }, > 156 "/":function(args){ > 157 FShell.fossil.sendCommand('/json'+args[0],undefined,{ > 158 beforeSend:function(req,opt){ > 159 print("Sending to: "+opt.url); > 160 }, > 161 onResponse:FShell.onResponseDefault() > 162 }); > 163 }, > 164 "eval":function(args){ > 165 eval(args.join(' ')); > 166 }, > 167 "login":function(args){ > 168 FShell.fossil.login(args[1], args[2], { > 169 onResponse:FShell.onResponseDefault() > 170 }); > 171 }, > 172 "whoami":function(args){ > 173 FShell.fossil.whoami({ > 174 onResponse:FShell.onResponseDefault() > 175 }); > 176 }, > 177 "HAI":function(args){ > 178 FShell.fossil.HAI({ > 179 onResponse:FShell.onResponseDefault() > 180 }); > 181 } > 182 > 183 }; > 184 FShell.commandAliases = { > 185 "li":"login", > 186 "lo":"logout", > 187 "who":"whoami", > 188 "hi":"HAI", > 189 "tci":"/timeline/ci?limit=3" > 190 }; > 191 FShell.mainLoop = function(){ > 192 var line; > 193 var check = /\S/; > 194 //var isJavaNull = /java\.lang\.null/; > 195 //print(typeof java.lang['null']); > 196 while( null != (line=this.readline(this.prompt)) ){ > 197 if(null===line) break /*EOF*/; > 198 else if( "" === line ) continue; > 199 //print("Got line: "+line); > 200 else if(!check.test(line)) continue; > 201 print('typeof line = '+typeof line); > 202 this.dispatchLine(line); > 203 print(""); > 204 } > 205 print("Bye!"); > 206 }; > 207 > 208 FShell.mainLoop();

Added ajax/i-test/rhino-test.js

> 1 var TestApp = { > 2 serverUrl: > 3 'http://localhost:8080' > 4 //'http://fjson/cgi-bin/fossil-json.cgi' > 5 //'http://192.168.1.62:8080' > 6 //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' > 7 , > 8 verbose:false, > 9 fossilBinary:'fossil', > 10 wiki:{} > 11 }; > 12 (function bootstrap() { > 13 var srcdir = '../js/'; > 14 var includes = [srcdir+'json2.js', > 15 srcdir+'whajaj.js', > 16 srcdir+'fossil-ajaj.js' > 17 ]; > 18 for( var i in includes ) { > 19 load(includes[i]); > 20 } > 21 WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; > 22 TestApp.fossil = new FossilAjaj({ > 23 asynchronous:false, /* rhino-based impl doesn't support async or timeout > 24 timeout:0, > 25 url:TestApp.serverUrl, > 26 fossilBinary:TestApp.fossilBinary > 27 }); > 28 var cb = TestApp.fossil.ajaj.callbacks; > 29 cb.beforeSend = function(req,opt){ > 30 if(!TestApp.verbose) return; > 31 print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); > 32 if(req) print("Request envelope="+WhAjaj.stringify(req)); > 33 }; > 34 cb.afterSend = function(req,opt){ > 35 //if(!TestApp.verbose) return; > 36 //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); > 37 //if(req) print("Request="+WhAjaj.stringify(req)); > 38 }; > 39 cb.onError = function(req,opt){ > 40 if(!TestApp.verbose) return; > 41 print("ERROR: "+WhAjaj.stringify(opt)); > 42 }; > 43 cb.onResponse = function(resp,req){ > 44 if(!TestApp.verbose) return; > 45 print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringi > 46 }; > 47 > 48 })(); > 49 > 50 /** > 51 Throws an exception of cond is a falsy value. > 52 */ > 53 function assert(cond, descr){ > 54 descr = descr || "Undescribed condition."; > 55 if(!cond){ > 56 print("Assertion FAILED: "+descr); > 57 throw new Error("Assertion failed: "+descr); > 58 // aarrgghh. Exceptions are of course swallowed by > 59 // the AJAX layer, to keep from killing a browser's > 60 // script environment. > 61 }else{ > 62 if(TestApp.verbose) print("Assertion OK: "+descr); > 63 } > 64 } > 65 > 66 /** > 67 Calls func() in a try/catch block and throws an exception if > 68 func() does NOT throw. > 69 */ > 70 function assertThrows(func, descr){ > 71 descr = descr || "Undescribed condition failed."; > 72 var ex; > 73 try{ > 74 func(); > 75 }catch(e){ > 76 ex = e; > 77 } > 78 if(!ex){ > 79 throw new Error("Function did not throw (as expected): "+descr); > 80 }else{ > 81 if(TestApp.verbose) print("Function threw (as expected): "+descr+": "+ex > 82 } > 83 } > 84 > 85 /** > 86 Convenience form of TestApp.fossil.sendCommand(command,payload,ajajOpt). > 87 */ > 88 function send(command,payload, ajajOpt){ > 89 TestApp.fossil.sendCommand(command,payload,ajajOpt); > 90 } > 91 > 92 /** > 93 Asserts that resp is-a Object, resp.fossil is-a string, and > 94 !resp.resultCode. > 95 */ > 96 function assertResponseOK(resp){ > 97 assert('object' === typeof resp,'Response is-a object.'); > 98 assert( 'string' === typeof resp.fossil, 'Response contains fossil property. > 99 assert( undefined === resp.resultCode, 'resp.resultCode is not set'); > 100 } > 101 /** > 102 Asserts that resp is-a Object, resp.fossil is-a string, and > 103 resp.resultCode is a truthy value. If expectCode is set then > 104 it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). > 105 */ > 106 function assertResponseError(resp,expectCode){ > 107 assert('object' === typeof resp,'Response is-a object.'); > 108 assert( 'string' === typeof resp.fossil, 'Response contains fossil property. > 109 assert( !!resp.resultCode, 'resp.resultCode='+resp.resultCode); > 110 if(expectCode){ > 111 assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code > 112 } > 113 } > 114 > 115 function testHAI(){ > 116 var rs; > 117 TestApp.fossil.HAI({ > 118 onResponse:function(resp,req){ > 119 rs = resp; > 120 } > 121 }); > 122 assertResponseOK(rs); > 123 TestApp.serverVersion = rs.fossil; > 124 assert( 'string' === typeof TestApp.serverVersion, 'server version = '+TestA > 125 } > 126 testHAI.description = 'Get server version info.'; > 127 > 128 function testIAmNobody(){ > 129 TestApp.fossil.whoami('/json/whoami'); > 130 assert('nobody' === TestApp.fossil.auth.name, 'User == nobody.' ); > 131 assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' ); > 132 > 133 } > 134 testIAmNobody.description = 'Ensure that current user is "nobody".'; > 135 > 136 > 137 function testAnonymousLogin(){ > 138 TestApp.fossil.login(); > 139 assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+Tes > 140 assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp > 141 TestApp.fossil.userName = null; > 142 TestApp.fossil.whoami('/json/whoami'); > 143 assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp > 144 } > 145 testAnonymousLogin.description = 'Perform anonymous login.'; > 146 > 147 function testAnonWiki(){ > 148 var rs; > 149 TestApp.fossil.sendCommand('/json/wiki/list',undefined,{ > 150 beforeSend:function(req,opt){ > 151 assert( req && (req.authToken==TestApp.fossil.auth.authToken), 'Requ > 152 }, > 153 onResponse:function(resp,req){ > 154 rs = resp; > 155 } > 156 }); > 157 assertResponseOK(rs); > 158 assert( (typeof [] === typeof rs.payload) && rs.payload.length, > 159 "Wiki list seems to be okay."); > 160 TestApp.wiki.list = rs.payload; > 161 > 162 TestApp.fossil.sendCommand('/json/wiki/get',{ > 163 name:TestApp.wiki.list[0] > 164 },{ > 165 onResponse:function(resp,req){ > 166 rs = resp; > 167 } > 168 }); > 169 assertResponseOK(rs); > 170 assert(rs.payload.name == TestApp.wiki.list[0], "Fetched page name matches e > 171 print("Got first wiki page: "+WhAjaj.stringify(rs.payload)); > 172 > 173 } > 174 testAnonWiki.description = 'Fetch wiki list as anonymous user.'; > 175 > 176 function testAnonLogout(){ > 177 var rs; > 178 TestApp.fossil.logout({ > 179 onResponse:function(resp,req){ > 180 rs = resp; > 181 } > 182 }); > 183 assertResponseOK(rs); > 184 print("Ensure that second logout attempt fails..."); > 185 TestApp.fossil.logout({ > 186 onResponse:function(resp,req){ > 187 rs = resp; > 188 } > 189 }); > 190 assertResponseError(rs); > 191 } > 192 testAnonLogout.description = 'Log out anonymous user.'; > 193 > 194 function testExternalProcess(){ > 195 > 196 var req = { command:"HAI", requestId:'testExternalProcess()' }; > 197 var args = [TestApp.fossilBinary, 'json', '--json-input', '-']; > 198 var p = java.lang.Runtime.getRuntime().exec(args); > 199 var outs = p.getOutputStream(); > 200 var osr = new java.io.OutputStreamWriter(outs); > 201 var osb = new java.io.BufferedWriter(osr); > 202 var json = JSON.stringify(req); > 203 osb.write(json,0, json.length); > 204 //osb.flush(); > 205 osb.close(); > 206 var ins = p.getInputStream(); > 207 var isr = new java.io.InputStreamReader(ins); > 208 var br = new java.io.BufferedReader(isr); > 209 var line; > 210 > 211 while( null !== (line=br.readLine())){ > 212 print(line); > 213 } > 214 //outs.close(); > 215 ins.close(); > 216 } > 217 testExternalProcess.description = 'Run fossil as external process.'; > 218 > 219 function testExternalProcessHandler(){ > 220 var aj = TestApp.fossil.ajaj; > 221 var oldImpl = aj.sendImpl; > 222 aj.sendImpl = FossilAjaj.rhinoLocalBinarySendImpl; > 223 var rs; > 224 TestApp.fossil.sendCommand('/json/HAI',undefined,{ > 225 onResponse:function(resp,opt){ > 226 rs = resp; > 227 } > 228 }); > 229 aj.sendImpl = oldImpl; > 230 assertResponseOK(rs); > 231 print("Using local fossil binary via AJAX interface, we fetched: "+ > 232 WhAjaj.stringify(rs)); > 233 } > 234 testExternalProcessHandler.description = 'Try local fossil binary via AJAX inter > 235 > 236 (function runAllTests(){ > 237 var testList = [ > 238 testHAI, > 239 testIAmNobody, > 240 testAnonymousLogin, > 241 testAnonWiki, > 242 testAnonLogout, > 243 //testExternalProcess, > 244 testExternalProcessHandler > 245 ]; > 246 var i, f; > 247 for( i = 0; i < testList.length; ++i ){ > 248 f = testList[i]; > 249 try{ > 250 print("Running test #"+(i+1)+": "+(f.description || "no description. > 251 f(); > 252 }catch(e){ > 253 print("Test #"+(i+1)+" failed: "+e); > 254 throw e; > 255 } > 256 } > 257 > 258 })(); > 259 > 260 print("Done! If you don't see an exception message in the last few lines, you wi

Added ajax/index.html

> 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" > 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> > 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> > 4 > 5 <head> > 6 <title>Fossil/JSON raw request sending</title> > 7 <meta http-equiv="content-type" content="text/html;charset=utf-8" /> > 8 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqu > 9 <script type="text/javascript" src="js/whajaj.js"></script> > 10 <script type="text/javascript" src="js/fossil-ajaj.js"></script> > 11 > 12 <style type='text/css'> > 13 th { > 14 text-align: left; > 15 background-color: #ececec; > 16 } > 17 > 18 .dangerWillRobinson { > 19 background-color: yellow; > 20 } > 21 </style> > 22 > 23 <script type='text/javascript'> > 24 WhAjaj.Connector.options.ajax.url = > 25 /* > 26 Change this to your CGI/server root path: > 27 */ > 28 //'http://fjson/cgi-bin/fossil.cgi' > 29 //'/repos/fossil-sgb/json.cgi' > 30 '/cgi-bin/fossil-json.cgi' > 31 ; > 32 var TheApp = { > 33 response:null, > 34 sessionID:null, > 35 jqe:{}/*jqe==jQuery Elements*/, > 36 ajaxCount:0, > 37 cgi: new FossilAjaj() > 38 }; > 39 > 40 > 41 TheApp.startAjaxNotif = function() > 42 { > 43 ++this.ajaxCount; > 44 TheApp.jqe.responseContainer.removeClass('dangerWillRobinson'); > 45 this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX opera > 46 if( 1 == this.ajaxCount ) this.jqe.ajaxNotification.fadeIn(); > 47 }; > 48 > 49 TheApp.endAjaxNotif = function() > 50 { > 51 --this.ajaxCount; > 52 this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX opera > 53 if( 0 == this.ajaxCount ) this.jqe.ajaxNotification.fadeOut(); > 54 }; > 55 > 56 TheApp.responseContainsError = function(resp) { > 57 if( resp && resp.resultCode ) { > 58 //alert("Error response:\n"+JSON.stringify(resp,0,4)); > 59 TheApp.jqe.taResponse.val( "RESPONSE CONTAINS ERROR INFO:\n"+WhAjaj.stri > 60 //TheApp.jqe.responseContainer.css({backgroundColor:'yellow'}); > 61 //TheApp.jqe.responseContainer.addClass('dangerWillRobinson'); > 62 TheApp.jqe.responseContainer.flash( '255,0,0', 1500 ); > 63 return true; > 64 } > 65 return false; > 66 }; > 67 > 68 > 69 TheApp.sendRequest = function() { > 70 var path = this.jqe.textPath.val(); > 71 var self = this; > 72 var data = this.jqe.taRequest.val(); > 73 var doPost = (data && data.length); > 74 var req; > 75 if( doPost ) try { > 76 req = JSON.parse(data); > 77 } > 78 catch(e) { > 79 TheApp.jqe.taResponse.val("Request is not valid JSON.\n"+e); > 80 return; > 81 } > 82 if( req ) { > 83 req.requestId = this.cgi.generateRequestId(); > 84 } > 85 var self = this; > 86 var opt = { > 87 url: WhAjaj.Connector.options.ajax.url + path, > 88 method: doPost ? 'POST' : 'GET' > 89 }; > 90 this.cgi.sendRequest( req, opt ); > 91 }; > 92 jQuery.fn.animateHighlight = function(highlightColor, duration) { > 93 var highlightBg = highlightColor || "#FFFF9C"; > 94 var animateMs = duration || 1500; > 95 var originalBg = this.css("backgroundColor"); > 96 this.stop().css("background-color", highlightBg).animate({backgroundColor: o > 97 }; > 98 jQuery.fn.flash = function( color, duration ) > 99 { > 100 var current = this.css( 'color' ); > 101 this.animate( { color: 'rgb(' + color + ')' }, duration / 2); > 102 this.animate( { color: current }, duration / 2 ); > 103 }; > 104 > 105 function myJsonPCallback(obj){ > 106 alert("JSONP callback got:\n"+WhAjaj.stringify(obj)); > 107 } > 108 > 109 jQuery(document).ready(function(){ > 110 var ids = [// list of HTML element IDs we use often. > 111 'btnSend', > 112 'ajaxNotification', > 113 'currentAuthToken', > 114 'responseContainer', > 115 'taRequest', > 116 'taRequestOpt', > 117 'taResponse', > 118 'textPath', > 119 'timer' > 120 ]; > 121 var i, k; > 122 for( i = 0; i < ids.length; ++i ) { > 123 k = ids[i]; > 124 TheApp.jqe[k] = jQuery('#'+k); > 125 } > 126 TheApp.jqe.textPath. > 127 keyup(function(event){ > 128 if(event.keyCode == 13){ > 129 TheApp.sendRequest(); > 130 } > 131 }); > 132 TheApp.timer = { > 133 _tstart:0,_tend:0,duration:0, > 134 start:function(){ > 135 this._tstart = (new Date()).getTime(); > 136 }, > 137 end:function(){ > 138 this._tend = (new Date()).getTime(); > 139 return this.duration = this._tend - this._tstart; > 140 } > 141 }; > 142 > 143 var ajcb = TheApp.cgi.ajaj.callbacks; > 144 ajcb.beforeSend = function(req,opt) { > 145 TheApp.timer.start(); > 146 var val = > 147 req ? > 148 (('string'===typeof req) ? req : WhAjaj.stringify(req)) > 149 : ''; > 150 TheApp.jqe.taResponse.val(''); > 151 TheApp.jqe.taRequest.val( val ); > 152 TheApp.jqe.taRequestOpt.val( opt ? WhAjaj.stringify(opt) : '' ); > 153 TheApp.startAjaxNotif(); > 154 }; > 155 ajcb.afterSend = function(req,opt) { > 156 TheApp.timer.end(); > 157 TheApp.endAjaxNotif(); > 158 TheApp.jqe.timer.text( "(Round-trip time (incl. JS overhead): "+TheApp.t > 159 }; > 160 ajcb.onResponse = function(resp,req, opt) { > 161 var val; > 162 if(this.jsonp) return /*was already handled*/; > 163 try { > 164 val = WhAjaj.stringify(resp); > 165 } > 166 catch(e) { > 167 val = WhAjaj.stringify(e) > 168 } > 169 //alert("onResponse this:"+WhAjaj.stringify(this)); > 170 //alert("val="+val); > 171 // FIXME: this.url is hosed for login because of how i overload onRespon > 172 if( opt.url ) TheApp.jqe.textPath.val(opt.url.replace(WhAjaj.Connector.o > 173 TheApp.jqe.taResponse.val( val ); > 174 }; > 175 ajcb.onError = function(req,opt) { > 176 TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(o > 177 }; > 178 > 179 TheApp.cgi.onLogin = function(){ > 180 TheApp.jqe.taResponse.val( "Logged in:\n"+WhAjaj.stringify(this.auth)); > 181 TheApp.jqe.currentAuthToken.html("Logged in: "+WhAjaj.stringify(this.auth) > 182 }; > 183 TheApp.cgi.onLogout = function(){ > 184 TheApp.jqe.taResponse.val( "Logged out!" ); > 185 TheApp.jqe.currentAuthToken.text("Not logged in"); > 186 }; > 187 TheApp.cgi.whoami(); > 188 jQuery('#headerArea').click(function(){ > 189 jQuery(this).slideUp('fast',function(){ > 190 jQuery(this).remove(); > 191 }); > 192 }); > 193 }); > 194 > 195 </script> > 196 > 197 </head> > 198 > 199 <body> > 200 <span id='ajaxNotification'></span> > 201 <div id='headerArea'> > 202 <h1>You know, for sending raw JSON requests to Fossil...</h1> > 203 > 204 If you're actually using this page, then you know what you're doing and don't > 205 need help text, hoverhelp, and a snazzy interface. > 206 > 207 <br><br> > 208 > 209 > 210 JSON API docs: <a href='https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDX > 211 > 212 </div><!-- #headerArea --> > 213 See also: <a href='wiki-editor.html'>prototype wiki editor</a>. > 214 > 215 <h2>Request...</h2> > 216 > 217 Path: <input type='text' size='40' id='textPath' value='/json/HAI'/> > 218 <input type='button' value='Send...' id='btnSend' onclick='TheApp.sendRequest()' > 219 If the POST textarea is not empty then it will be posted with the request. > 220 <hr/> > 221 <strong>Quick-posts:</strong><br/> > 222 <input type='button' value='HAI' onclick='TheApp.cgi.HAI()' /> > 223 <input type='button' value='HAI JSONP' onclick='TheApp.cgi.sendCommand("/json/HA > 224 <input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/vers > 225 <input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat?fu > 226 <input type='button' value='whoami' onclick='TheApp.cgi.whoami()' /> > 227 <input type='button' value='cap' onclick='TheApp.cgi.sendCommand("/json/cap")' / > 228 <input type='button' value='resultCodes' onclick='TheApp.cgi.sendCommand("/json/ > 229 <input type='button' value='g' onclick='TheApp.cgi.sendCommand("/json/g")' /> > 230 > 231 <br/> > 232 > 233 <input type='button' value='branch/list' onclick='TheApp.cgi.sendCommand("/json/ > 234 <input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/ > 235 <input type='button' value='timeline/wiki' onclick='TheApp.cgi.sendCommand("/jso > 236 <input type='button' value='timeline/ticket' onclick='TheApp.cgi.sendCommand("/j > 237 <input type='button' value='timeline/branch' onclick='TheApp.cgi.sendCommand("/j > 238 <input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wi > 239 <input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/j > 240 <input type='button' value='wiki/get/Fossil' onclick='TheApp.cgi.sendCommand("/j > 241 > 242 <br/> > 243 > 244 <input type='button' value='user/list' onclick='TheApp.cgi.sendCommand("/json/us > 245 <input type='button' value='user/get' onclick='TheApp.cgi.sendCommand("/json/use > 246 <input type='button' value='tag/list' onclick='TheApp.cgi.sendCommand("/json/tag > 247 <input type='button' value='tag/list/json' onclick='TheApp.cgi.sendCommand("/jso > 248 <input type='button' value='tag/add' > 249 onclick='TheApp.cgi.sendCommand("/json/tag/add",{name:"json-add-tag-test",ch > 250 <input type='button' value='tag/cancel' > 251 onclick='TheApp.cgi.sendCommand("/json/tag/cancel",{name:"json-add-tag-test" > 252 <input type='button' value='tag/find' > 253 onclick='TheApp.cgi.sendCommand("/json/tag/find",{name:"json",type:"*",raw:f > 254 > 255 <br/> > 256 > 257 <input type='button' value='diff' > 258 onclick='TheApp.cgi.sendCommand("/json/diff",{v1:"b0e9b45baed6f885",v2:"5f22 > 259 <input type='button' value='diff/A/B' > 260 onclick='TheApp.cgi.sendCommand("/json/diff/b0e9b45baed6f885/5f225e261d83628 > 261 > 262 <input type='button' value='query' > 263 onclick='TheApp.cgi.sendCommand("/json/query?format=o","SELECT * from user") > 264 > 265 <input type='button' value='report list' > 266 onclick='TheApp.cgi.sendCommand("/json/report/list")' /> > 267 <input type='button' value='report get' > 268 onclick='TheApp.cgi.sendCommand("/json/report/get",2)' /> > 269 > 270 <input type='button' value='report run' > 271 onclick='TheApp.cgi.sendCommand("/json/report/run",{"report":2,"format":"o"} > 272 > 273 <!-- not yet ready... > 274 <input type='button' value='artifact/XYZ' onclick='TheApp.cgi.sendCommand("/json > 275 --> > 276 > 277 <!-- > 278 <input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' /> > 279 <input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/W > 280 <input type='button' value='get client data' onclick='TheApp.cgi.getPageClientDa > 281 <input type='button' value='save client data' onclick='TheApp.cgi.savePageClient > 282 --> > 283 <hr/> > 284 <b>Login:</b> > 285 <br/> > 286 <input type='button' value='Anon. PW' onclick='TheApp.cgi.sendCommand("/json/ano > 287 <input type='button' value='Anon. PW+Login' onclick='TheApp.cgi.login()' /> > 288 <br/> > 289 name:<input type='text' id='textUser' value='json-demo' size='12'/> > 290 pw:<input type='password' id='textPassword' value='json-demo' size='12'/> > 291 <input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser") > 292 <input type='button' value='logout' onclick='TheApp.cgi.logout()' /> > 293 <br/> > 294 <span id='currentAuthToken' style='font-family:monospaced'></span> > 295 > 296 <br/> > 297 > 298 <hr/> > 299 > 300 <table> > 301 <tr> > 302 <th>POST data</th> > 303 <th>Request AJAJ options</th> > 304 </tr> > 305 <tr> > 306 <td width='50%' valign='top'> > 307 <textarea id='taRequest' rows='10' cols='50'></textarea> > 308 </td> > 309 <td width='50%' valign='top'> > 310 <textarea id='taRequestOpt' rows='10' cols='40' readonly></textarea> > 311 </td> > 312 </tr> > 313 <tr> > 314 <th colspan='2'>Response <span id='timer'></span></th> > 315 </tr> > 316 <tr> > 317 <td colspan='2' id='responseContainer' valign='top'> > 318 <textarea id='taResponse' rows='20' cols='80' readonly></textarea> > 319 </td> > 320 </tr> > 321 </table> > 322 <div></div> > 323 <div></div> > 324 <div></div> > 325 > 326 </body></html>

Added ajax/js/fossil-ajaj.js

> 1 /** > 2 This file contains a WhAjaj extension for use with Fossil/JSON. > 3 > 4 Author: Stephan Beal (sgbeal@googlemail.com) > 5 > 6 License: Public Domain > 7 */ > 8 > 9 /** > 10 Constructor for a new Fossil AJAJ client. ajajOpt may be an optional > 11 object suitable for passing to the WhAjaj.Connector() constructor. > 12 > 13 On returning, this.ajaj is-a WhAjaj.Connector instance which can > 14 be used to send requests to the back-end (though the convenience > 15 functions of this class are the preferred way to do it). Clients > 16 are encouraged to use FossilAjaj.sendCommand() (and friends) instead > 17 of the underlying WhAjaj.Connector API, since this class' API > 18 contains Fossil-specific request-calling handling (e.g. of authentication > 19 info) whereas WhAjaj is more generic. > 20 */ > 21 function FossilAjaj(ajajOpt) > 22 { > 23 this.ajaj = new WhAjaj.Connector(ajajOpt); > 24 return this; > 25 } > 26 > 27 FossilAjaj.prototype.generateRequestId = function() { > 28 return this.ajaj.generateRequestId(); > 29 }; > 30 > 31 /** > 32 Proxy for this.ajaj.sendRequest(). > 33 */ > 34 FossilAjaj.prototype.sendRequest = function(req,opt) { > 35 return this.ajaj.sendRequest(req,opt); > 36 }; > 37 > 38 /** > 39 Sends a command to the fossil back-end. Command should be the > 40 path part of the URL, e.g. /json/stat, payload is a request-specific > 41 value type (may often be null/undefined). ajajOpt is an optional object > 42 holding WhAjaj.sendRequest()-compatible options. > 43 > 44 This function constructs a Fossil/JSON request envelope based > 45 on the given arguments and adds this.auth.authToken and a requestId > 46 to it. > 47 */ > 48 FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) { > 49 var req; > 50 ajajOpt = ajajOpt || {}; > 51 if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) { > 52 req = { > 53 payload:payload, > 54 requestId:('function' === typeof this.generateRequestId) ? this.gene > 55 authToken:(this.auth ? this.auth.authToken : undefined), > 56 jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefine > 57 }; > 58 } > 59 ajajOpt.method = req ? 'POST' : 'GET'; > 60 // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={}; > 61 if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command; > 62 this.ajaj.sendRequest(req,ajajOpt); > 63 }; > 64 > 65 /** > 66 Sends a login request to the back-end. > 67 > 68 ajajOpt is an optional configuration object suitable for passing > 69 to sendCommand(). > 70 > 71 After the response returns, this.auth will be > 72 set to the response payload. > 73 > 74 If name === 'anonymous' (the default if none is passed in) then this > 75 function ignores the pw argument and must make two requests - the first > 76 one gets the captcha code and the second one submits it. > 77 ajajOpt.onResponse() (if set) is only called for the actual login > 78 response (the 2nd one), as opposed to being called for both requests. > 79 However, this.ajaj.callbacks.onResponse() _is_ called for both (because > 80 it happens at a lower level). > 81 > 82 If this object has an onLogin() function it is called (with > 83 no arguments) before the onResponse() handler of the login is called > 84 (that is the 2nd request for anonymous logins). > 85 > 86 */ > 87 FossilAjaj.prototype.login = function(name,pw,ajajOpt) { > 88 name = name || 'anonymous'; > 89 var self = this; > 90 var loginReq = { > 91 name:name, > 92 password:pw > 93 }; > 94 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 95 var oldOnResponse = ajajOpt.onResponse; > 96 ajajOpt.onResponse = function(resp,req) { > 97 var thisOpt = this; > 98 //alert('login response:\n'+WhAjaj.stringify(resp)); > 99 if( resp && resp.payload ) { > 100 //self.userName = resp.payload.name; > 101 //self.capabilities = resp.payload.capabilities; > 102 self.auth = resp.payload; > 103 } > 104 if( WhAjaj.isFunction( self.onLogin ) ){ > 105 try{ self.onLogin(); } > 106 catch(e){} > 107 } > 108 if( WhAjaj.isFunction(oldOnResponse) ) { > 109 oldOnResponse.apply(thisOpt,[resp,req]); > 110 } > 111 }; > 112 function doLogin(){ > 113 //alert("Sending login request..."+WhAjaj.stringify(loginReq)); > 114 self.sendCommand('/json/login', loginReq, ajajOpt); > 115 } > 116 if( 'anonymous' === name ){ > 117 this.sendCommand('/json/anonymousPassword',undefined,{ > 118 onResponse:function(resp,req){ > 119 /* > 120 if( WhAjaj.isFunction(oldOnResponse) ){ > 121 oldOnResponse.apply(this, [resp,req]); > 122 }; > 123 */ > 124 if(resp && !resp.resultCode){ > 125 //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp)); > 126 loginReq.anonymousSeed = resp.payload.seed; > 127 loginReq.password = resp.payload.password; > 128 doLogin(); > 129 } > 130 } > 131 }); > 132 } > 133 else doLogin(); > 134 }; > 135 > 136 /** > 137 Logs out of fossil, invaliding this login token. > 138 > 139 ajajOpt is an optional configuration object suitable for passing > 140 to sendCommand(). > 141 > 142 If this object has an onLogout() function it is called (with > 143 no arguments) before the onResponse() handler is called. > 144 IFF the response succeeds then this.auth is unset. > 145 */ > 146 FossilAjaj.prototype.logout = function(ajajOpt) { > 147 var self = this; > 148 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 149 var oldOnResponse = ajajOpt.onResponse; > 150 ajajOpt.onResponse = function(resp,req) { > 151 var thisOpt = this; > 152 self.auth = undefined; > 153 if( WhAjaj.isFunction( self.onLogout ) ){ > 154 try{ self.onLogout(); } > 155 catch(e){} > 156 } > 157 if( WhAjaj.isFunction(oldOnResponse) ) { > 158 oldOnResponse.apply(thisOpt,[resp,req]); > 159 } > 160 }; > 161 this.sendCommand('/json/logout', undefined, ajajOpt ); > 162 }; > 163 > 164 /** > 165 Sends a HAI request to the server. /json/HAI is an alias /json/version. > 166 > 167 ajajOpt is an optional configuration object suitable for passing > 168 to sendCommand(). > 169 */ > 170 FossilAjaj.prototype.HAI = function(ajajOpt) { > 171 this.sendCommand('/json/HAI', undefined, ajajOpt); > 172 }; > 173 > 174 > 175 /** > 176 Sends a /json/whoami request. Updates this.auth to contain > 177 the login info, removing them if the response does not contain > 178 that data. > 179 */ > 180 FossilAjaj.prototype.whoami = function(ajajOpt) { > 181 var self = this; > 182 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 183 var oldOnResponse = ajajOpt.onResponse; > 184 ajajOpt.onResponse = function(resp,req) { > 185 var thisOpt = this; > 186 if( resp && resp.payload ){ > 187 if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){ > 188 self.auth = resp.payload; > 189 if( WhAjaj.isFunction(self.onLogin) ){ > 190 self.onLogin(); > 191 } > 192 } > 193 } > 194 else { delete self.auth; } > 195 if( WhAjaj.isFunction(oldOnResponse) ) { > 196 oldOnResponse.apply(thisOpt,[resp,req]); > 197 } > 198 }; > 199 self.sendCommand('/json/whoami', undefined, ajajOpt); > 200 }; > 201 > 202 /** > 203 EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which > 204 uses Rhino to connect to a local fossil binary for input and output. Its > 205 signature and semantics are as described for > 206 WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and > 207 additions: > 208 > 209 - It does not support timeouts or asynchronous mode. > 210 > 211 - The args.fossilBinary property must point to the local fossil binary > 212 (it need not be a complete path if fossil is in the $PATH). This > 213 function throws (without calling any request callbacks) if > 214 args.fossilBinary is not set. fossilBinary may be set on > 215 WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as > 216 the ajax options parameter to any of the FossilAjaj.sendCommand() family > 217 of functions, or by setting > 218 aFossilAjajInstance.ajaj.options.fossilBinary on a specific > 219 FossilAjaj instance. > 220 > 221 - It uses the args.url field to create the "command" property of the > 222 request, constructs a request envelope, spawns a fossil process in JSON > 223 mode, feeds it the request envelope, and returns the response envelope > 224 via the same mechanisms defined for the HTTP-based implementations. > 225 > 226 The interface is otherwise compatible with the "normal" > 227 FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and > 228 not back-end agnostic like the WhAjaj.sendImpl() interface intends). > 229 > 230 > 231 */ > 232 FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){ > 233 var self = this; > 234 request = request || {}; > 235 if(!args.fossilBinary){ > 236 throw new Error("fossilBinary is not set on AJAX options!"); > 237 } > 238 var url = args.url.split('?')[0].split(/\/+/); > 239 if(url.length>1){ > 240 // 3x shift(): protocol, host, 'json' part of path > 241 request.command = (url.shift(),url.shift(),url.shift(), url.join('/')); > 242 } > 243 delete args.url; > 244 //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request)); > 245 var json; > 246 try{ > 247 var pargs = [args.fossilBinary, 'json', '--json-input', '-']; > 248 var p = java.lang.Runtime.getRuntime().exec(pargs); > 249 var outs = p.getOutputStream(); > 250 var osr = new java.io.OutputStreamWriter(outs); > 251 var osb = new java.io.BufferedWriter(osr); > 252 > 253 json = JSON.stringify(request); > 254 osb.write(json,0, json.length); > 255 osb.close(); > 256 var ins = p.getInputStream(); > 257 var isr = new java.io.InputStreamReader(ins); > 258 var br = new java.io.BufferedReader(isr); > 259 var line; > 260 json = []; > 261 while( null !== (line=br.readLine())){ > 262 json.push(line); > 263 } > 264 ins.close(); > 265 }catch(e){ > 266 args.errorMessage = e.toString(); > 267 WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); > 268 return undefined; > 269 } > 270 json = json.join(''); > 271 //print("READ IN JSON: "+json); > 272 WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] > 273 }/*rhinoLocalBinary*/

Added ajax/js/json2.js

> 1 /* > 2 http://www.JSON.org/json2.js > 3 2009-06-29 > 4 > 5 Public Domain. > 6 > 7 NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. > 8 > 9 See http://www.JSON.org/js.html > 10 > 11 This file creates a global JSON object containing two methods: stringify > 12 and parse. > 13 > 14 JSON.stringify(value, replacer, space) > 15 value any JavaScript value, usually an object or array. > 16 > 17 replacer an optional parameter that determines how object > 18 values are stringified for objects. It can be a > 19 function or an array of strings. > 20 > 21 space an optional parameter that specifies the indentation > 22 of nested structures. If it is omitted, the text will > 23 be packed without extra whitespace. If it is a number, > 24 it will specify the number of spaces to indent at each > 25 level. If it is a string (such as '\t' or '&nbsp;'), > 26 it contains the characters used to indent at each level. > 27 > 28 This method produces a JSON text from a JavaScript value. > 29 > 30 When an object value is found, if the object contains a toJSON > 31 method, its toJSON method will be called and the result will be > 32 stringified. A toJSON method does not serialize: it returns the > 33 value represented by the name/value pair that should be serialized, > 34 or undefined if nothing should be serialized. The toJSON method > 35 will be passed the key associated with the value, and this will be > 36 bound to the object holding the key. > 37 > 38 For example, this would serialize Dates as ISO strings. > 39 > 40 Date.prototype.toJSON = function (key) { > 41 function f(n) { > 42 // Format integers to have at least two digits. > 43 return n < 10 ? '0' + n : n; > 44 } > 45 > 46 return this.getUTCFullYear() + '-' + > 47 f(this.getUTCMonth() + 1) + '-' + > 48 f(this.getUTCDate()) + 'T' + > 49 f(this.getUTCHours()) + ':' + > 50 f(this.getUTCMinutes()) + ':' + > 51 f(this.getUTCSeconds()) + 'Z'; > 52 }; > 53 > 54 You can provide an optional replacer method. It will be passed the > 55 key and value of each member, with this bound to the containing > 56 object. The value that is returned from your method will be > 57 serialized. If your method returns undefined, then the member will > 58 be excluded from the serialization. > 59 > 60 If the replacer parameter is an array of strings, then it will be > 61 used to select the members to be serialized. It filters the results > 62 such that only members with keys listed in the replacer array are > 63 stringified. > 64 > 65 Values that do not have JSON representations, such as undefined or > 66 functions, will not be serialized. Such values in objects will be > 67 dropped; in arrays they will be replaced with null. You can use > 68 a replacer function to replace those with JSON values. > 69 JSON.stringify(undefined) returns undefined. > 70 > 71 The optional space parameter produces a stringification of the > 72 value that is filled with line breaks and indentation to make it > 73 easier to read. > 74 > 75 If the space parameter is a non-empty string, then that string will > 76 be used for indentation. If the space parameter is a number, then > 77 the indentation will be that many spaces. > 78 > 79 Example: > 80 > 81 text = JSON.stringify(['e', {pluribus: 'unum'}]); > 82 // text is '["e",{"pluribus":"unum"}]' > 83 > 84 > 85 text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); > 86 // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' > 87 > 88 text = JSON.stringify([new Date()], function (key, value) { > 89 return this[key] instanceof Date ? > 90 'Date(' + this[key] + ')' : value; > 91 }); > 92 // text is '["Date(---current time---)"]' > 93 > 94 > 95 JSON.parse(text, reviver) > 96 This method parses a JSON text to produce an object or array. > 97 It can throw a SyntaxError exception. > 98 > 99 The optional reviver parameter is a function that can filter and > 100 transform the results. It receives each of the keys and values, > 101 and its return value is used instead of the original value. > 102 If it returns what it received, then the structure is not modified. > 103 If it returns undefined then the member is deleted. > 104 > 105 Example: > 106 > 107 // Parse the text. Values that look like ISO date strings will > 108 // be converted to Date objects. > 109 > 110 myData = JSON.parse(text, function (key, value) { > 111 var a; > 112 if (typeof value === 'string') { > 113 a = > 114 /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); > 115 if (a) { > 116 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], > 117 +a[5], +a[6])); > 118 } > 119 } > 120 return value; > 121 }); > 122 > 123 myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { > 124 var d; > 125 if (typeof value === 'string' && > 126 value.slice(0, 5) === 'Date(' && > 127 value.slice(-1) === ')') { > 128 d = new Date(value.slice(5, -1)); > 129 if (d) { > 130 return d; > 131 } > 132 } > 133 return value; > 134 }); > 135 > 136 > 137 This is a reference implementation. You are free to copy, modify, or > 138 redistribute. > 139 > 140 This code should be minified before deployment. > 141 See http://javascript.crockford.com/jsmin.html > 142 > 143 USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO > 144 NOT CONTROL. > 145 */ > 146 > 147 /*jslint evil: true */ > 148 > 149 /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, > 150 call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, > 151 getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, > 152 lastIndex, length, parse, prototype, push, replace, slice, stringify, > 153 test, toJSON, toString, valueOf > 154 */ > 155 > 156 // Create a JSON object only if one does not already exist. We create the > 157 // methods in a closure to avoid creating global variables. > 158 > 159 var JSON = JSON || {}; > 160 > 161 (function () { > 162 > 163 function f(n) { > 164 // Format integers to have at least two digits. > 165 return n < 10 ? '0' + n : n; > 166 } > 167 > 168 if (typeof Date.prototype.toJSON !== 'function') { > 169 > 170 Date.prototype.toJSON = function (key) { > 171 > 172 return isFinite(this.valueOf()) ? > 173 this.getUTCFullYear() + '-' + > 174 f(this.getUTCMonth() + 1) + '-' + > 175 f(this.getUTCDate()) + 'T' + > 176 f(this.getUTCHours()) + ':' + > 177 f(this.getUTCMinutes()) + ':' + > 178 f(this.getUTCSeconds()) + 'Z' : null; > 179 }; > 180 > 181 String.prototype.toJSON = > 182 Number.prototype.toJSON = > 183 Boolean.prototype.toJSON = function (key) { > 184 return this.valueOf(); > 185 }; > 186 } > 187 > 188 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u > 189 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b > 190 gap, > 191 indent, > 192 meta = { // table of character substitutions > 193 '\b': '\\b', > 194 '\t': '\\t', > 195 '\n': '\\n', > 196 '\f': '\\f', > 197 '\r': '\\r', > 198 '"' : '\\"', > 199 '\\': '\\\\' > 200 }, > 201 rep; > 202 > 203 > 204 function quote(string) { > 205 > 206 // If the string contains no control characters, no quote characters, and no > 207 // backslash characters, then we can safely slap some quotes around it. > 208 // Otherwise we must also replace the offending characters with safe escape > 209 // sequences. > 210 > 211 escapable.lastIndex = 0; > 212 return escapable.test(string) ? > 213 '"' + string.replace(escapable, function (a) { > 214 var c = meta[a]; > 215 return typeof c === 'string' ? c : > 216 '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); > 217 }) + '"' : > 218 '"' + string + '"'; > 219 } > 220 > 221 > 222 function str(key, holder) { > 223 > 224 // Produce a string from holder[key]. > 225 > 226 var i, // The loop counter. > 227 k, // The member key. > 228 v, // The member value. > 229 length, > 230 mind = gap, > 231 partial, > 232 value = holder[key]; > 233 > 234 // If the value has a toJSON method, call it to obtain a replacement value. > 235 > 236 if (value && typeof value === 'object' && > 237 typeof value.toJSON === 'function') { > 238 value = value.toJSON(key); > 239 } > 240 > 241 // If we were called with a replacer function, then call the replacer to > 242 // obtain a replacement value. > 243 > 244 if (typeof rep === 'function') { > 245 value = rep.call(holder, key, value); > 246 } > 247 > 248 // What happens next depends on the value's type. > 249 > 250 switch (typeof value) { > 251 case 'string': > 252 return quote(value); > 253 > 254 case 'number': > 255 > 256 // JSON numbers must be finite. Encode non-finite numbers as null. > 257 > 258 return isFinite(value) ? String(value) : 'null'; > 259 > 260 case 'boolean': > 261 case 'null': > 262 > 263 // If the value is a boolean or null, convert it to a string. Note: > 264 // typeof null does not produce 'null'. The case is included here in > 265 // the remote chance that this gets fixed someday. > 266 > 267 return String(value); > 268 > 269 // If the type is 'object', we might be dealing with an object or an array or > 270 // null. > 271 > 272 case 'object': > 273 > 274 // Due to a specification blunder in ECMAScript, typeof null is 'object', > 275 // so watch out for that case. > 276 > 277 if (!value) { > 278 return 'null'; > 279 } > 280 > 281 // Make an array to hold the partial results of stringifying this object value. > 282 > 283 gap += indent; > 284 partial = []; > 285 > 286 // Is the value an array? > 287 > 288 if (Object.prototype.toString.apply(value) === '[object Array]') { > 289 > 290 // The value is an array. Stringify every element. Use null as a placeholder > 291 // for non-JSON values. > 292 > 293 length = value.length; > 294 for (i = 0; i < length; i += 1) { > 295 partial[i] = str(i, value) || 'null'; > 296 } > 297 > 298 // Join all of the elements together, separated with commas, and wrap them in > 299 // brackets. > 300 > 301 v = partial.length === 0 ? '[]' : > 302 gap ? '[\n' + gap + > 303 partial.join(',\n' + gap) + '\n' + > 304 mind + ']' : > 305 '[' + partial.join(',') + ']'; > 306 gap = mind; > 307 return v; > 308 } > 309 > 310 // If the replacer is an array, use it to select the members to be stringified. > 311 > 312 if (rep && typeof rep === 'object') { > 313 length = rep.length; > 314 for (i = 0; i < length; i += 1) { > 315 k = rep[i]; > 316 if (typeof k === 'string') { > 317 v = str(k, value); > 318 if (v) { > 319 partial.push(quote(k) + (gap ? ': ' : ':') + v); > 320 } > 321 } > 322 } > 323 } else { > 324 > 325 // Otherwise, iterate through all of the keys in the object. > 326 > 327 for (k in value) { > 328 if (Object.hasOwnProperty.call(value, k)) { > 329 v = str(k, value); > 330 if (v) { > 331 partial.push(quote(k) + (gap ? ': ' : ':') + v); > 332 } > 333 } > 334 } > 335 } > 336 > 337 // Join all of the member texts together, separated with commas, > 338 // and wrap them in braces. > 339 > 340 v = partial.length === 0 ? '{}' : > 341 gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + > 342 mind + '}' : '{' + partial.join(',') + '}'; > 343 gap = mind; > 344 return v; > 345 } > 346 } > 347 > 348 // If the JSON object does not yet have a stringify method, give it one. > 349 > 350 if (typeof JSON.stringify !== 'function') { > 351 JSON.stringify = function (value, replacer, space) { > 352 > 353 // The stringify method takes a value and an optional replacer, and an optional > 354 // space parameter, and returns a JSON text. The replacer can be a function > 355 // that can replace values, or an array of strings that will select the keys. > 356 // A default replacer method can be provided. Use of the space parameter can > 357 // produce text that is more easily readable. > 358 > 359 var i; > 360 gap = ''; > 361 indent = ''; > 362 > 363 // If the space parameter is a number, make an indent string containing that > 364 // many spaces. > 365 > 366 if (typeof space === 'number') { > 367 for (i = 0; i < space; i += 1) { > 368 indent += ' '; > 369 } > 370 > 371 // If the space parameter is a string, it will be used as the indent string. > 372 > 373 } else if (typeof space === 'string') { > 374 indent = space; > 375 } > 376 > 377 // If there is a replacer, it must be a function or an array. > 378 // Otherwise, throw an error. > 379 > 380 rep = replacer; > 381 if (replacer && typeof replacer !== 'function' && > 382 (typeof replacer !== 'object' || > 383 typeof replacer.length !== 'number')) { > 384 throw new Error('JSON.stringify'); > 385 } > 386 > 387 // Make a fake root object containing our value under the key of ''. > 388 // Return the result of stringifying the value. > 389 > 390 return str('', {'': value}); > 391 }; > 392 } > 393 > 394 > 395 // If the JSON object does not yet have a parse method, give it one. > 396 > 397 if (typeof JSON.parse !== 'function') { > 398 JSON.parse = function (text, reviver) { > 399 > 400 // The parse method takes a text and an optional reviver function, and returns > 401 // a JavaScript value if the text is a valid JSON text. > 402 > 403 var j; > 404 > 405 function walk(holder, key) { > 406 > 407 // The walk method is used to recursively walk the resulting structure so > 408 // that modifications can be made. > 409 > 410 var k, v, value = holder[key]; > 411 if (value && typeof value === 'object') { > 412 for (k in value) { > 413 if (Object.hasOwnProperty.call(value, k)) { > 414 v = walk(value, k); > 415 if (v !== undefined) { > 416 value[k] = v; > 417 } else { > 418 delete value[k]; > 419 } > 420 } > 421 } > 422 } > 423 return reviver.call(holder, key, value); > 424 } > 425 > 426 > 427 // Parsing happens in four stages. In the first stage, we replace certain > 428 // Unicode characters with escape sequences. JavaScript handles many characters > 429 // incorrectly, either silently deleting them, or treating them as line endings. > 430 > 431 cx.lastIndex = 0; > 432 if (cx.test(text)) { > 433 text = text.replace(cx, function (a) { > 434 return '\\u' + > 435 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); > 436 }); > 437 } > 438 > 439 // In the second stage, we run the text against regular expressions that look > 440 // for non-JSON patterns. We are especially concerned with '()' and 'new' > 441 // because they can cause invocation, and '=' because it can cause mutation. > 442 // But just to be safe, we want to reject all unexpected forms. > 443 > 444 // We split the second stage into 4 regexp operations in order to work around > 445 // crippling inefficiencies in IE's and Safari's regexp engines. First we > 446 // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we > 447 // replace all simple value tokens with ']' characters. Third, we delete all > 448 // open brackets that follow a colon or comma or that begin the text. Finally, > 449 // we look to see that the remaining characters are only whitespace or ']' or > 450 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. > 451 > 452 if (/^[\],:{}\s]*$/. > 453 test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). > 454 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') > 455 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { > 456 > 457 // In the third stage we use the eval function to compile the text into a > 458 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity > 459 // in JavaScript: it can begin a block or an object literal. We wrap the text > 460 // in parens to eliminate the ambiguity. > 461 > 462 j = eval('(' + text + ')'); > 463 > 464 // In the optional fourth stage, we recursively walk the new structure, passing > 465 // each name/value pair to a reviver function for possible transformation. > 466 > 467 return typeof reviver === 'function' ? > 468 walk({'': j}, '') : j; > 469 } > 470 > 471 // If the text is not JSON parseable, then a SyntaxError is thrown. > 472 > 473 throw new SyntaxError('JSON.parse'); > 474 }; > 475 } > 476 }());

Added ajax/js/whajaj.js

> 1 /** > 2 This file provides a JS interface into the core functionality of > 3 JSON-centric back-ends. It sends GET or JSON POST requests to > 4 a back-end and expects JSON responses. The exact semantics of > 5 the underlying back-end and overlying front-end are not its concern, > 6 and it leaves the interpretation of the data up to the client/server > 7 insofar as possible. > 8 > 9 All functionality is part of a class named WhAjaj, and that class > 10 acts as namespace for this framework. > 11 > 12 Author: Stephan Beal (http://wanderinghorse.net/home/stephan/) > 13 > 14 License: Public Domain > 15 > 16 This framework is directly derived from code originally found in > 17 http://code.google.com/p/jsonmessage, and later in > 18 http://whiki.wanderinghorse.net, where it contained quite a bit > 19 of application-specific logic. It was eventually (the 3rd time i > 20 needed it) split off into its own library to simplify inclusion > 21 into my many mini-projects. > 22 */ > 23 > 24 > 25 /** > 26 The WhAjaj function is primarily a namespace, and not intended > 27 to called or instantiated via the 'new' operator. > 28 */ > 29 function WhAjaj() > 30 { > 31 } > 32 > 33 /** Returns a millisecond Unix Epoch timestamp. */ > 34 WhAjaj.msTimestamp = function() > 35 { > 36 return (new Date()).getTime(); > 37 }; > 38 > 39 /** Returns a Unix Epoch timestamp (in seconds) in integer format. > 40 > 41 Reminder to self: (1.1 %1.2) evaluates to a floating-point value > 42 in JS, and thus this implementation is less than optimal. > 43 */ > 44 WhAjaj.unixTimestamp = function() > 45 { > 46 var ts = (new Date()).getTime(); > 47 return parseInt( ""+((ts / 1000) % ts) ); > 48 }; > 49 > 50 /** > 51 Returns true if v is-a Array instance. > 52 */ > 53 WhAjaj.isArray = function( v ) > 54 { > 55 return (v && > 56 (v instanceof Array) || > 57 (Object.prototype.toString.call(v) === "[object Array]") > 58 ); > 59 /* Reminders to self: > 60 typeof [] == "object" > 61 toString.call([]) == "[object Array]" > 62 ([]).toString() == empty > 63 */ > 64 }; > 65 > 66 /** > 67 Returns true if v is-a Object instance. > 68 */ > 69 WhAjaj.isObject = function( v ) > 70 { > 71 return v && > 72 (v instanceof Object) && > 73 ('[object Object]' === Object.prototype.toString.apply(v) ); > 74 }; > 75 > 76 /** > 77 Returns true if v is-a Function instance. > 78 */ > 79 WhAjaj.isFunction = function(obj) > 80 { > 81 return obj > 82 && ( > 83 (obj instanceof Function) > 84 || ('function' === typeof obj) > 85 || ("[object Function]" === Object.prototype.toString.call(obj)) > 86 ) > 87 ; > 88 }; > 89 > 90 /** > 91 Parses window.location.search-style string into an object > 92 containing key/value pairs of URL arguments (already urldecoded). > 93 > 94 If the str argument is not passed (arguments.length==0) then > 95 window.location.search.substring(1) is used by default. If > 96 neither str is passed in nor window exists then false is returned. > 97 > 98 On success it returns an Object containing the key/value pairs > 99 parsed from the string. > 100 > 101 FIXME: for keys in the form "name[]", build an array of results, > 102 like PHP does. > 103 > 104 */ > 105 WhAjaj.processUrlArgs = function(str) { > 106 if( 0 === arguments.length ) { > 107 if( (undefined === typeof window) || > 108 !window.location || > 109 !window.location.search ) return false; > 110 else str = (''+window.location.search).substring(1); > 111 } > 112 if( ! str ) return false; > 113 var args = {}; > 114 var sp = str.split(/&+/); > 115 var rx = /^([^=]+)(=(.+))?/; > 116 var i, m; > 117 for( i in sp ) { > 118 m = rx.exec( sp[i] ); > 119 if( ! m ) continue; > 120 args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true > 121 } > 122 return args; > 123 }; > 124 > 125 /** > 126 A simple wrapper around JSON.stringify(), using my own personal > 127 preferred values for the 2nd and 3rd parameters. To globally > 128 set its indentation level, assign WhAjaj.stringify.indent to > 129 an integer value (0 for no intendation). > 130 > 131 This function is intended only for human-readable output, not > 132 generic over-the-wire JSON output (where JSON.stringify(val) will > 133 produce smaller results). > 134 */ > 135 WhAjaj.stringify = function(val) { > 136 if( ! arguments.callee.indent ) arguments.callee.indent = 4; > 137 return JSON.stringify(val,0,arguments.callee.indent); > 138 }; > 139 > 140 /** > 141 Each instance of this class holds state information for making > 142 AJAJ requests to a back-end system. While clients may use one > 143 "requester" object per connection attempt, for connections to the > 144 same back-end, using an instance configured for that back-end > 145 can simplify usage. This class is designed so that the actual > 146 connection-related details (i.e. _how_ it connects to the > 147 back-end) may be re-implemented to use a client's preferred > 148 connection mechanism (e.g. jQuery). > 149 > 150 The optional opt paramater may be an object with any (or all) of > 151 the properties documented for WhAjaj.Connector.options.ajax. > 152 Properties set here (or later via modification of the "options" > 153 property of this object) will be used in calls to > 154 WhAjaj.Connector.sendRequest(), and these override (normally) any > 155 options set in WhAjaj.Connector.options.ajax. Note that > 156 WhAjaj.Connector.sendRequest() _also_ takes an options object, > 157 and ones passed there will override, for purposes of that one > 158 request, any options passed in here or defined in > 159 WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax > 160 and WhAjaj.Connector.prototype.sendRequest() for more details > 161 about the precedence of options. > 162 > 163 Sample usage: > 164 > 165 @code > 166 // Set up common connection-level options: > 167 var cgi = new WhAjaj.Connector({ > 168 url: '/cgi-bin/my.cgi', > 169 timeout:10000, > 170 onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); }, > 171 onError(req,opt) { > 172 alert(opt.errorMessage); > 173 } > 174 }); > 175 // Any of those options may optionally be set globally in > 176 // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend() > 177 // are often easiest/most useful to set globally). > 178 > 179 // Get list of pages... > 180 cgi.sendRequest( null, { > 181 onResponse(resp,req){ alert(WhAjaj.stringify(resp)); } > 182 }); > 183 @endcode > 184 > 185 For common request types, clients can add functions to this > 186 object which act as wrappers for backend-specific functionality. As > 187 a simple example: > 188 > 189 @code > 190 cgi.login = function(name,pw,ajajOpt) { > 191 this.sendRequest( {name:name, password:pw}, ajajOpt ); > 192 }; > 193 @endcode > 194 > 195 TODOs: > 196 > 197 - Caching of page-load requests, with a configurable lifetime. > 198 > 199 - Use-cases like the above login() function are a tiny bit > 200 problematic to implement when each request has a different URL > 201 path (i know this from the whiki implementation). This is partly > 202 a side-effect of design descisions made back in the very first > 203 days of this code's life. i need to go through and see where i > 204 can bend those conventions a bit (where it won't break my other > 205 apps unduly). > 206 */ > 207 WhAjaj.Connector = function(opt) > 208 { > 209 if(WhAjaj.isObject(opt)) this.options = opt; > 210 //TODO?: this.$cache = {}; > 211 }; > 212 > 213 /** > 214 The core options used by WhAjaj.Connector instances for performing > 215 network operations. These options can (and some _should_) > 216 be changed by a client application. They can also be changed > 217 on specific instances of WhAjaj.Connector, but for most applications > 218 it is simpler to set them here and not have to bother with configuring > 219 each WhAjaj.Connector instance. Apps which use multiple back-ends at one tim > 220 however, will need to customize each instance for a given back-end. > 221 */ > 222 WhAjaj.Connector.options = { > 223 /** > 224 A (meaningless) prefix to apply to WhAjaj.Connector-generated > 225 request IDs. > 226 */ > 227 requestIdPrefix:'WhAjaj.Connector-', > 228 /** > 229 Default options for WhAjaj.Connector.sendRequest() connection > 230 parameters. This object holds only connection-related > 231 options and callbacks (all optional), and not options > 232 related to the required JSON structure of any given request. > 233 i.e. the page name used in a get-page request are not set > 234 here but are specified as part of the request object. > 235 > 236 These connection options are a "normalized form" of options > 237 often found in various AJAX libraries like jQuery, > 238 Prototype, dojo, etc. This approach allows us to swap out > 239 the real connection-related parts by writing a simple proxy > 240 which transforms our "normalized" form to the > 241 backend-specific form. For examples, see the various > 242 implementations stored in WhAjaj.Connector.sendImpls. > 243 > 244 The following callback options are, in practice, almost > 245 always set globally to some app-wide defaults: > 246 > 247 - onError() to report errors using a common mechanism. > 248 - beforeSend() to start a visual activity notification > 249 - afterSend() to disable the visual activity notification > 250 > 251 However, be aware that if any given WhAjaj.Connector instance is > 252 given its own before/afterSend callback then those will > 253 override these. Mixing shared/global and per-instance > 254 callbacks can potentially lead to confusing results if, e.g., > 255 the beforeSend() and afterSend() functions have side-effects > 256 but are not used with their proper before/after partner. > 257 > 258 TODO: rename this to 'ajaj' (the name is historical). The > 259 problem with renaming it is is that the word 'ajax' is > 260 pretty prevelant in the source tree, so i can't globally > 261 swap it out. > 262 */ > 263 ajax: { > 264 /** > 265 URL of the back-end server/CGI. > 266 */ > 267 url: '/some/path', > 268 > 269 /** > 270 Connection method. Some connection-related functions might > 271 override any client-defined setting. > 272 > 273 Must be one of 'GET' or 'POST'. For custom connection > 274 implementation, it may optionally be some > 275 implementation-specified value. > 276 > 277 Normally the API can derive this value automatically - if the > 278 request uses JSON data it is POSTed, else it is GETted. > 279 */ > 280 method:'GET', > 281 > 282 /** > 283 A hint whether to run the operation asynchronously or > 284 not. Not all concrete WhAjaj.Connector.sendImpl() > 285 implementations can support this. Interestingly, at > 286 least one popular AJAX toolkit does not document > 287 supporting _synchronous_ AJAX operations. All common > 288 browser-side implementations support async operation, but > 289 non-browser implementations migth not. > 290 */ > 291 asynchronous:true, > 292 > 293 /** > 294 A HTTP authentication login name for the AJAX > 295 connection. Not all concrete WhAjaj.Connector.sendImpl() > 296 implementations can support this. > 297 */ > 298 loginName:undefined, > 299 > 300 /** > 301 An HTTP authentication login password for the AJAJ > 302 connection. Not all concrete WhAjaj.Connector.sendImpl() > 303 implementations can support this. > 304 */ > 305 loginPassword:undefined, > 306 > 307 /** > 308 A connection timeout, in milliseconds, for establishing > 309 an AJAJ connection. Not all concrete > 310 WhAjaj.Connector.sendImpl() implementations can support this. > 311 */ > 312 timeout:10000, > 313 > 314 /** > 315 If an AJAJ request receives JSON data from the back-end, that > 316 data is passed as a plain Object as the response parameter > 317 (exception: in jsonp mode it is passed a string). The initiating > 318 request object is passed as the second parameter, but clients > 319 can normally ignore it (only those which need a way to map > 320 specific requests to responses will need it). The 3rd parameter > 321 is the same as the 'this' object for the context of the callback, > 322 but is provided because the instance-level callbacks (set in > 323 (WhAjaj.Connector instance).callbacks, require it in some > 324 cases (because their 'this' is different!). > 325 > 326 Note that the response might contain error information which > 327 comes from the back-end. The difference between this error info > 328 and the info passed to the onError() callback is that this data > 329 indicates an application-level error, whereas onError() is used > 330 to report connection-level problems or when the backend produces > 331 non-JSON data (which, when not in jsonp mode, is unexpected and > 332 is as fatal to the request as a connection error). > 333 */ > 334 onResponse: function(response, request, opt){}, > 335 > 336 /** > 337 If an AJAX request fails to establish a connection or it > 338 receives non-JSON data from the back-end, this function > 339 is called (e.g. timeout error or host name not > 340 resolvable). It is passed the originating request and the > 341 "normalized" connection parameters used for that > 342 request. The connectOpt object "should" (or "might") > 343 have an "errorMessage" property which describes the > 344 nature of the problem. > 345 > 346 Clients will almost always want to replace the default > 347 implementation with something which integrates into > 348 their application. > 349 */ > 350 onError: function(request, connectOpt) > 351 { > 352 alert('AJAJ request failed:\n' > 353 +'Connection information:\n' > 354 +JSON.stringify(connectOpt,0,4) > 355 ); > 356 }, > 357 > 358 /** > 359 Called before each connection attempt is made. Clients > 360 can use this to, e.g., enable a visual "network activity > 361 notification" for the user. It is passed the original > 362 request object and the normalized connection parameters > 363 for the request. If this function changes opt, those > 364 changes _are_ applied to the subsequent request. If this > 365 function throws, neither the onError() nor afterSend() > 366 callbacks are triggered and WhAjaj.Connector.sendImpl() > 367 propagates the exception back to the caller. > 368 */ > 369 beforeSend: function(request,opt){}, > 370 > 371 /** > 372 Called after an AJAJ connection attempt completes, > 373 regardless of success or failure. Passed the same > 374 parameters as beforeSend() (see that function for > 375 details). > 376 > 377 Here's an example of setting up a visual notification on > 378 ajax operations using jQuery (but it's also easy to do > 379 without jQuery as well): > 380 > 381 @code > 382 function startAjaxNotif(req,opt) { > 383 var me = arguments.callee; > 384 var c = ++me.ajaxCount; > 385 me.element.text( c + " pending AJAX operation(s)..." ); > 386 if( 1 == c ) me.element.stop().fadeIn(); > 387 } > 388 startAjaxNotif.ajaxCount = 0. > 389 startAjaxNotif.element = jQuery('#whikiAjaxNotification'); > 390 > 391 function endAjaxNotif() { > 392 var c = --startAjaxNotif.ajaxCount; > 393 startAjaxNotif.element.text( c+" pending AJAX operation(s)..." ) > 394 if( 0 == c ) startAjaxNotif.element.stop().fadeOut(); > 395 } > 396 @endcode > 397 > 398 Set the beforeSend/afterSend properties to those > 399 functions to enable the notifications by default. > 400 */ > 401 afterSend: function(request,opt){}, > 402 > 403 /** > 404 If jsonp is a string then the WhAjaj-internal response > 405 handling code ASSUMES that the response contains a JSONP-style > 406 construct and eval()s it after afterSend() but before onResponse(). > 407 In this case, onResponse() will get a string value for the response > 408 instead of a response object parsed from JSON. > 409 */ > 410 jsonp:undefined, > 411 /** > 412 Don't use yet. Planned future option. > 413 */ > 414 propagateExceptions:false > 415 } > 416 }; > 417 > 418 > 419 /** > 420 WhAjaj.Connector.prototype.callbacks defines callbacks analog > 421 to the onXXX callbacks defined in WhAjaj.Connector.options.ajax, > 422 with two notable differences: > 423 > 424 1) these callbacks, if set, are called in addition to any > 425 request-specific callback. The intention is to allow a framework to set > 426 "framework-level" callbacks which should be called independently of the > 427 request-specific callbacks (without interfering with them, e.g. > 428 requiring special re-forwarding features). > 429 > 430 2) The 'this' object in these callbacks is the Connector instance > 431 associated with the callback, whereas the "other" onXXX form has its > 432 "ajax options" object as its this. > 433 > 434 When this API says that an onXXX callback will be called for a request, > 435 both the request's onXXX (if set) and this one (if set) will be called. > 436 */ > 437 WhAjaj.Connector.prototype.callbacks = {}; > 438 /** > 439 Instance-specific values for AJAJ-level properties (as opposed to > 440 application-level request properties). Options set here "override" those > 441 specified in WhAjaj.Connector.options.ajax and are "overridden" by > 442 options passed to sendRequest(). > 443 */ > 444 WhAjaj.Connector.prototype.options = {}; > 445 > 446 > 447 /** > 448 Tries to find the given key in any of the following, returning > 449 the first match found: opt, this.options, WhAjaj.Connector.options.ajax. > 450 > 451 Returns undefined if key is not found. > 452 */ > 453 WhAjaj.Connector.prototype.derivedOption = function(key,opt) { > 454 var v = opt ? opt[key] : undefined; > 455 if( undefined !== v ) return v; > 456 else v = this.options[key]; > 457 if( undefined !== v ) return v; > 458 else v = WhAjaj.Connector.options.ajax[key]; > 459 return v; > 460 }; > 461 > 462 /** > 463 Returns a unique string on each call containing a generic > 464 reandom request identifier string. This is not used by the core > 465 API but can be used by client code to generate unique IDs for > 466 each request (if needed). > 467 > 468 The exact format is unspecified and may change in the future. > 469 > 470 Request IDs can be used by clients to "match up" responses to > 471 specific requests if needed. In practice, however, they are > 472 seldom, if ever, needed. When passing several concurrent > 473 requests through the same response callback, it might be useful > 474 for some clients to be able to distinguish, possibly re-routing > 475 them through other handlers based on the originating request type. > 476 > 477 If this.options.requestIdPrefix or > 478 WhAjaj.Connector.options.requestIdPrefix is set then that text > 479 is prefixed to the returned string. > 480 */ > 481 WhAjaj.Connector.prototype.generateRequestId = function() > 482 { > 483 if( undefined === arguments.callee.sequence ) > 484 { > 485 arguments.callee.sequence = 0; > 486 } > 487 var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestI > 488 return pref + > 489 WhAjaj.msTimestamp() + > 490 '/'+(Math.round( Math.random() * 100000000) )+ > 491 ':'+(++arguments.callee.sequence); > 492 }; > 493 > 494 /** > 495 Copies (SHALLOWLY) all properties in opt to this.options. > 496 */ > 497 WhAjaj.Connector.prototype.addOptions = function(opt) { > 498 var k, v; > 499 for( k in opt ) { > 500 if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */ > 501 this.options[k] = opt[k]; > 502 } > 503 return this.options; > 504 }; > 505 > 506 /** > 507 An internal helper object which holds several functions intended > 508 to simplify the creation of concrete communication channel > 509 implementations for WhAjaj.Connector.sendImpl(). These operations > 510 take care of some of the more error-prone parts of ensuring that > 511 onResponse(), onError(), etc. callbacks are called consistently > 512 using the same rules. > 513 */ > 514 WhAjaj.Connector.sendHelper = { > 515 /** > 516 opt is assumed to be a normalized set of > 517 WhAjaj.Connector.sendRequest() options. This function > 518 creates a url by concatenating opt.url and some form of > 519 opt.urlParam. > 520 > 521 If opt.urlParam is an object or string then it is appended > 522 to the url. An object is assumed to be a one-dimensional set > 523 of simple (urlencodable) key/value pairs, and not larger > 524 data structures. A string value is assumed to be a > 525 well-formed, urlencoded set of key/value pairs separated by > 526 '&' characters. > 527 > 528 The new/normalized URL is returned (opt is not modified). If > 529 opt.urlParam is not set then opt.url is returned (or an > 530 empty string if opt.url is itself a false value). > 531 > 532 TODO: if opt is-a Object and any key points to an array, > 533 build up a list of keys in the form "keyname[]". We could > 534 arguably encode sub-objects like "keyname[subkey]=...", but > 535 i don't know if that's conventions-compatible with other > 536 frameworks. > 537 */ > 538 normalizeURL: function(opt) { > 539 var u = opt.url || ''; > 540 if( opt.urlParam ) { > 541 var addQ = (u.indexOf('?') >= 0) ? false : true; > 542 var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false); > 543 var tail = ''; > 544 if( WhAjaj.isObject(opt.urlParam) ) { > 545 var li = [], k; > 546 for( k in opt.urlParam) { > 547 li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) ); > 548 } > 549 tail = li.join('&'); > 550 } > 551 else if( 'string' === typeof opt.urlParam ) { > 552 tail = opt.urlParam; > 553 } > 554 u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail; > 555 } > 556 return u; > 557 }, > 558 /** > 559 Should be called by WhAjaj.Connector.sendImpl() > 560 implementations after a response has come back. This > 561 function takes care of most of ensuring that framework-level > 562 conventions involving WhAjaj.Connector.options.ajax > 563 properties are followed. > 564 > 565 The request argument must be the original request passed to > 566 the sendImpl() function. It may legally be null for GET requests. > 567 > 568 The opt object should be the normalized AJAX options used > 569 for the connection. > 570 > 571 The resp argument may be either a plain Object or a string > 572 (in which case it is assumed to be JSON). > 573 > 574 The 'this' object for this call MUST be a WhAjaj.Connector > 575 instance in order for callback processing to work properly. > 576 > 577 This function takes care of the following: > 578 > 579 - Calling opt.afterSend() > 580 > 581 - If resp is a string, de-JSON-izing it to an object. > 582 > 583 - Calling opt.onResponse() > 584 > 585 - Calling opt.onError() in several common (potential) error > 586 cases. > 587 > 588 - If resp is-a String and opt.jsonp then resp is assumed to be > 589 a JSONP-form construct and is eval()d BEFORE opt.onResponse() > 590 is called. It is arguable to eval() it first, but the logic > 591 integrates better with the non-jsonp handler. > 592 > 593 The sendImpl() should return immediately after calling this. > 594 > 595 The sendImpl() must call only one of onSendSuccess() or > 596 onSendError(). It must call one of them or it must implement > 597 its own response/error handling, which is not recommended > 598 because getting the documented semantics of the > 599 onError/onResponse/afterSend handling correct can be tedious. > 600 */ > 601 onSendSuccess:function(request,resp,opt) { > 602 var cb = this.callbacks || {}; > 603 if( WhAjaj.isFunction(cb.afterSend) ) { > 604 try {cb.afterSend( request, opt );} > 605 catch(e){} > 606 } > 607 if( WhAjaj.isFunction(opt.afterSend) ) { > 608 try {opt.afterSend( request, opt );} > 609 catch(e){} > 610 } > 611 function doErr(){ > 612 if( WhAjaj.isFunction(cb.onError) ) { > 613 try {cb.onError( request, opt );} > 614 catch(e){} > 615 } > 616 if( WhAjaj.isFunction(opt.onError) ) { > 617 try {opt.onError( request, opt );} > 618 catch(e){} > 619 } > 620 } > 621 if( ! resp ) { > 622 opt.errorMessage = "Sending of request succeeded but returned no dat > 623 doErr(); > 624 return false; > 625 } > 626 > 627 if( 'string' === typeof resp ) { > 628 try { > 629 resp = opt.jsonp ? eval(resp) : JSON.parse(resp); > 630 } catch(e) { > 631 opt.errorMessage = e.toString(); > 632 doErr(); > 633 return; > 634 } > 635 } > 636 try { > 637 if( WhAjaj.isFunction( cb.onResponse ) ) { > 638 cb.onResponse( resp, request, opt ); > 639 } > 640 if( WhAjaj.isFunction( opt.onResponse ) ) { > 641 opt.onResponse( resp, request, opt ); > 642 } > 643 return true; > 644 } > 645 catch(e) { > 646 opt.errorMessage = "Exception while handling inbound JSON response:\ > 647 + e > 648 +"\nOriginal response data:\n"+JSON.stringify(resp,0,2) > 649 ; > 650 ; > 651 doErr(); > 652 return false; > 653 } > 654 }, > 655 /** > 656 Should be called by sendImpl() implementations after a response > 657 has failed to connect (e.g. could not resolve host or timeout > 658 reached). This function takes care of most of ensuring that > 659 framework-level conventions involving WhAjaj.Connector.options.ajax > 660 properties are followed. > 661 > 662 The request argument must be the original request passed to > 663 the sendImpl() function. It may legally be null for GET > 664 requests. > 665 > 666 The 'this' object for this call MUST be a WhAjaj.Connector > 667 instance in order for callback processing to work properly. > 668 > 669 The opt object should be the normalized AJAX options used > 670 for the connection. By convention, the caller of this > 671 function "should" set opt.errorMessage to contain a > 672 human-readable description of the error. > 673 > 674 The sendImpl() should return immediately after calling this. The > 675 return value from this function is unspecified. > 676 */ > 677 onSendError: function(request,opt) { > 678 var cb = this.callbacks || {}; > 679 if( WhAjaj.isFunction(cb.afterSend) ) { > 680 try {cb.afterSend( request, opt );} > 681 catch(e){} > 682 } > 683 if( WhAjaj.isFunction(opt.afterSend) ) { > 684 try {opt.afterSend( request, opt );} > 685 catch(e){} > 686 } > 687 if( WhAjaj.isFunction( cb.onError ) ) { > 688 try {cb.onError( request, opt );} > 689 catch(e) {/*ignore*/} > 690 } > 691 if( WhAjaj.isFunction( opt.onError ) ) { > 692 try {opt.onError( request, opt );} > 693 catch(e) {/*ignore*/} > 694 } > 695 } > 696 }; > 697 > 698 /** > 699 WhAjaj.Connector.sendImpls holds several concrete > 700 implementations of WhAjaj.Connector.prototype.sendImpl(). To use > 701 a specific implementation by default assign > 702 WhAjaj.Connector.prototype.sendImpl to one of these functions. > 703 > 704 The functions defined here require that the 'this' object be-a > 705 WhAjaj.Connector instance. > 706 > 707 Historical notes: > 708 > 709 a) We once had an implementation based on Prototype, but that > 710 library just pisses me off (they change base-most types' > 711 prototypes, introducing side-effects in client code which > 712 doesn't even use Prototype). The Prototype version at the time > 713 had a serious toJSON() bug which caused empty arrays to > 714 serialize as the string "[]", which broke a bunch of my code. > 715 (That has been fixed in the mean time, but i don't use > 716 Prototype.) > 717 > 718 b) We once had an implementation for the dojo library, > 719 > 720 If/when the time comes to add Prototype/dojo support, we simply > 721 need to port: > 722 > 723 http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSO > 724 > 725 (search that file for "dojo" and "Prototype") to this tree. That > 726 code is this code's generic grandfather and they are still very > 727 similar, so a port is trivial. > 728 > 729 */ > 730 WhAjaj.Connector.sendImpls = { > 731 /** > 732 This is a concrete implementation of > 733 WhAjaj.Connector.prototype.sendImpl() which uses the > 734 environment's native XMLHttpRequest class to send whiki > 735 requests and fetch the responses. > 736 > 737 The only argument must be a connection properties object, as > 738 constructed by WhAjaj.Connector.normalizeAjaxParameters(). > 739 > 740 If window.firebug is set then window.firebug.watchXHR() is > 741 called to enable monitoring of the XMLHttpRequest object. > 742 > 743 This implementation honors the loginName and loginPassword > 744 connection parameters. > 745 > 746 Returns the XMLHttpRequest object. > 747 > 748 This implementation requires that the 'this' object be-a > 749 WhAjaj.Connector. > 750 > 751 This implementation uses setTimeout() to implement the > 752 timeout support, and thus the JS engine must provide that > 753 functionality. > 754 */ > 755 XMLHttpRequest: function(request, args) > 756 { > 757 var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request; > 758 var xhr = new XMLHttpRequest(); > 759 var startTime = (new Date()).getTime(); > 760 var timeout = args.timeout || 10000/*arbitrary!*/; > 761 var hitTimeout = false; > 762 var done = false; > 763 var tmid /* setTimeout() ID */; > 764 var whself = this; > 765 //if( json ) json = json.replace(/ö/g,"\\u00f6") /* ONLY FOR A SPECIFIC > 766 //alert( 'json=\n'+json ); > 767 function handleTimeout() > 768 { > 769 hitTimeout = true; > 770 if( ! done ) > 771 { > 772 var now = (new Date()).getTime(); > 773 try { xhr.abort(); } catch(e) {/*ignore*/} > 774 // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method > 775 args.errorMessage = "Timeout of "+timeout+"ms reached after "+(n > 776 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, > 777 } > 778 return; > 779 } > 780 function onStateChange() > 781 { // reminder to self: apparently 'this' is-not-a XHR :/ > 782 if( hitTimeout ) > 783 { /* we're too late - the error was already triggered. */ > 784 return; > 785 } > 786 > 787 if( 4 == xhr.readyState ) > 788 { > 789 done = true; > 790 if( tmid ) > 791 { > 792 clearTimeout( tmid ); > 793 tmid = null; > 794 } > 795 if( (xhr.status >= 200) && (xhr.status < 300) ) > 796 { > 797 WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [re > 798 return; > 799 } > 800 else > 801 { > 802 if( undefined === args.errorMessage ) > 803 { > 804 args.errorMessage = "Error sending a '"+args.method+"' A > 805 +"["+args.url+"]: " > 806 +"Status text=["+xhr.statusText+"]" > 807 ; > 808 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [ > 809 } > 810 else { /*maybe it was was set by the timeout handler. */ } > 811 return; > 812 } > 813 } > 814 }; > 815 > 816 xhr.onreadystatechange = onStateChange; > 817 if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchX > 818 { /* plug in to firebug lite's XHR monitor... */ > 819 window.firebug.watchXHR( xhr ); > 820 } > 821 try > 822 { > 823 //alert( JSON.stringify( args )); > 824 function xhrOpen() > 825 { > 826 if( ('loginName' in args) && args.loginName ) > 827 { > 828 xhr.open( args.method, args.url, args.asynchronous, args.log > 829 } > 830 else > 831 { > 832 xhr.open( args.method, args.url, args.asynchronous ); > 833 } > 834 } > 835 if( json && ('POST' === args.method.toUpperCase()) ) > 836 { > 837 xhrOpen(); > 838 xhr.setRequestHeader("Content-Type", "application/json; charset= > 839 // Google Chrome warns that it refuses to set these > 840 // "unsafe" headers (his words, not mine): > 841 // xhr.setRequestHeader("Content-length", json.length); > 842 // xhr.setRequestHeader("Connection", "close"); > 843 xhr.send( json ); > 844 } > 845 else /* assume GET */ > 846 { > 847 xhrOpen(); > 848 xhr.send(null); > 849 } > 850 tmid = setTimeout( handleTimeout, timeout ); > 851 return xhr; > 852 } > 853 catch(e) > 854 { > 855 args.errorMessage = e.toString(); > 856 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, arg > 857 return undefined; > 858 } > 859 }/*XMLHttpRequest()*/, > 860 /** > 861 This is a concrete implementation of > 862 WhAjaj.Connector.prototype.sendImpl() which uses the jQuery > 863 AJAX API to send requests and fetch the responses. > 864 > 865 The first argument may be either null/false, an Object > 866 containing toJSON-able data to post to the back-end, or such an > 867 object in JSON string form. > 868 > 869 The second argument must be a connection properties object, as > 870 constructed by WhAjaj.Connector.normalizeAjaxParameters(). > 871 > 872 If window.firebug is set then window.firebug.watchXHR() is > 873 called to enable monitoring of the XMLHttpRequest object. > 874 > 875 This implementation honors the loginName and loginPassword > 876 connection parameters. > 877 > 878 Returns the XMLHttpRequest object. > 879 > 880 This implementation requires that the 'this' object be-a > 881 WhAjaj.Connector. > 882 */ > 883 jQuery:function(request,args) > 884 { > 885 var data = request || undefined; > 886 var whself = this; > 887 if( data ) { > 888 if('string'!==typeof data) { > 889 try { > 890 data = JSON.stringify(data); > 891 } > 892 catch(e) { > 893 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [requ > 894 return; > 895 } > 896 } > 897 } > 898 var ajopt = { > 899 url: args.url, > 900 data: data, > 901 type: args.method, > 902 async: args.asynchronous, > 903 password: (undefined !== args.loginPassword) ? args.loginPassword : > 904 username: (undefined !== args.loginName) ? args.loginName : undefine > 905 contentType: 'application/json; charset=utf-8', > 906 error: function(xhr, textStatus, errorThrown) > 907 { > 908 //this === the options for this ajax request > 909 args.errorMessage = "Error sending a '"+ajopt.type+"' request to > 910 +"Status text=["+textStatus+"]" > 911 +(errorThrown ? ("Error=["+errorThrown+"]") : "") > 912 ; > 913 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, > 914 }, > 915 success: function(data) > 916 { > 917 WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [reques > 918 }, > 919 /* Set dataType=text instead of json to keep jQuery from doing our c > 920 written response handling for us. > 921 */ > 922 dataType: 'text' > 923 }; > 924 if( undefined !== args.timeout ) > 925 { > 926 ajopt.timeout = args.timeout; > 927 } > 928 try > 929 { > 930 return jQuery.ajax(ajopt); > 931 } > 932 catch(e) > 933 { > 934 args.errorMessage = e.toString(); > 935 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, arg > 936 return undefined; > 937 } > 938 }/*jQuery()*/, > 939 /** > 940 This is a concrete implementation of > 941 WhAjaj.Connector.prototype.sendImpl() which uses the rhino > 942 Java API to send requests and fetch the responses. > 943 > 944 Limitations vis-a-vis the interface: > 945 > 946 - timeouts are not supported. > 947 > 948 - asynchronous mode is not supported because implementing it > 949 requires the ability to kill a running thread (which is deprecated > 950 in the Java API). > 951 > 952 TODOs: > 953 > 954 - add socket timeouts. > 955 > 956 - support HTTP proxy. > 957 > 958 The Java APIs support this, it just hasn't been added here yet. > 959 */ > 960 rhino:function(request,args) > 961 { > 962 var self = this; > 963 var data = request || undefined; > 964 if( data ) { > 965 if('string'!==typeof data) { > 966 try { > 967 data = JSON.stringify(data); > 968 } > 969 catch(e) { > 970 WhAjaj.Connector.sendHelper.onSendError.apply( self, [reques > 971 return; > 972 } > 973 } > 974 } > 975 var url; > 976 var con; > 977 var IO = new JavaImporter(java.io); > 978 var wr; > 979 var rd, ln, json = []; > 980 function setIncomingCookies(list){ > 981 if(!list || !list.length) return; > 982 if( !self.cookies ) self.cookies = {}; > 983 var k, v, i; > 984 for( i = 0; i < list.length; ++i ){ > 985 v = list[i].split('=',2); > 986 k = decodeURIComponent(v[0]) > 987 v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null; > 988 //print("RECEIVED COOKIE: "+k+"="+v); > 989 if(!v) { > 990 delete self.cookies[k]; > 991 continue; > 992 }else{ > 993 self.cookies[k] = v; > 994 } > 995 } > 996 }; > 997 function setOutboundCookies(conn){ > 998 if(!self.cookies) return; > 999 var k, v; > 1000 for( k in self.cookies ){ > 1001 if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken > 1002 v = self.cookies[k]; > 1003 conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+enco > 1004 //print("SENDING COOKIE: "+k+"="+v); > 1005 } > 1006 }; > 1007 try{ > 1008 url = new java.net.URL( args.url ) > 1009 con = url.openConnection(/*FIXME: add proxy support!*/); > 1010 con.setRequestProperty("Accept-Charset","utf-8"); > 1011 setOutboundCookies(con); > 1012 if(data){ > 1013 con.setRequestProperty("Content-Type","application/json; charset > 1014 con.setDoOutput( true ); > 1015 wr = new IO.OutputStreamWriter(con.getOutputStream()) > 1016 wr.write(data); > 1017 wr.flush(); > 1018 wr.close(); > 1019 wr = null; > 1020 //print("POSTED: "+data); > 1021 } > 1022 rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStre > 1023 //var skippedHeaders = false; > 1024 while ((line = rd.readLine()) !== null) { > 1025 //print("LINE: "+line); > 1026 //if(!line.length && !skippedHeaders){ > 1027 // skippedHeaders = true; > 1028 // json = []; > 1029 // continue; > 1030 //} > 1031 json.push(line); > 1032 } > 1033 setIncomingCookies(con.getHeaderFields().get("Set-Cookie")); > 1034 }catch(e){ > 1035 args.errorMessage = e.toString(); > 1036 WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] > 1037 return undefined; > 1038 } > 1039 try { if(wr) wr.close(); } catch(e) { /*ignore*/} > 1040 try { if(rd) rd.close(); } catch(e) { /*ignore*/} > 1041 json = json.join(''); > 1042 //print("READ IN JSON: "+json); > 1043 WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, a > 1044 }/*rhino*/ > 1045 }; > 1046 > 1047 /** > 1048 An internal function which takes an object containing properties > 1049 for a WhAjaj.Connector network request. This function creates a new > 1050 object containing a superset of the properties from: > 1051 > 1052 a) opt > 1053 b) this.options > 1054 c) WhAjaj.Connector.options.ajax > 1055 > 1056 in that order, using the first one it finds. > 1057 > 1058 All non-function properties are _deeply_ copied via JSON cloning > 1059 in order to prevent accidental "cross-request pollenation" (been > 1060 there, done that). Functions cannot be cloned and are simply > 1061 copied by reference. > 1062 > 1063 This function throws if JSON-copying one of the options fails > 1064 (e.g. due to cyclic data structures). > 1065 > 1066 Reminder to self: this function does not "normalize" opt.urlParam > 1067 by encoding it into opt.url, mainly for historical reasons, but > 1068 also because that behaviour was specifically undesirable in this > 1069 code's genetic father. > 1070 */ > 1071 WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt) > 1072 { > 1073 var rc = {}; > 1074 function merge(k,v) > 1075 { > 1076 if( rc.hasOwnProperty(k) ) return; > 1077 else if( WhAjaj.isFunction(v) ) {} > 1078 else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) ); > 1079 rc[k]=v; > 1080 } > 1081 function cp(obj) { > 1082 if( ! WhAjaj.isObject(obj) ) return; > 1083 var k; > 1084 for( k in obj ) { > 1085 if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Pro > 1086 merge(k, obj[k]); > 1087 } > 1088 } > 1089 cp( opt ); > 1090 cp( this.options ); > 1091 cp( WhAjaj.Connector.options.ajax ); > 1092 // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc); > 1093 return rc; > 1094 }; > 1095 > 1096 /** > 1097 This is the generic interface for making calls to a back-end > 1098 JSON-producing request handler. It is a simple wrapper around > 1099 WhAjaj.Connector.prototype.sendImpl(), which just normalizes the > 1100 connection options for sendImpl() and makes sure that > 1101 opt.beforeSend() is (possibly) called. > 1102 > 1103 The request parameter must either be false/null/empty or a > 1104 fully-populated JSON-able request object (which will be sent as > 1105 unencoded application/json text), depending on the type of > 1106 request being made. It is never semantically legal (in this API) > 1107 for request to be a string/number/true/array value. As a rule, > 1108 only POST requests use the request data. GET requests should > 1109 encode their data in opt.url or opt.urlParam (see below). > 1110 > 1111 opt must contain the network-related parameters for the request. > 1112 Paramters _not_ set in opt are pulled from this.options or > 1113 WhAjaj.Connector.options.ajax (in that order, using the first > 1114 value it finds). Thus the set of connection-level options used > 1115 for the request are a superset of those various sources. > 1116 > 1117 The "normalized" (or "superimposed") opt object's URL may be > 1118 modified before the request is sent, as follows: > 1119 > 1120 if opt.urlParam is a string then it is assumed to be properly > 1121 URL-encoded parameters and is appended to the opt.url. If it is > 1122 an Object then it is assumed to be a one-dimensional set of > 1123 key/value pairs with simple values (numbers, strings, booleans, > 1124 null, and NOT objects/arrays). The keys/values are URL-encoded > 1125 and appended to the URL. > 1126 > 1127 The beforeSend() callback (see below) can modify the options > 1128 object before the request attempt is made. > 1129 > 1130 The callbacks in the normalized opt object will be triggered as > 1131 follows (if they are set to Function values): > 1132 > 1133 - beforeSend(request,opt) will be called before any network > 1134 processing starts. If beforeSend() throws then no other > 1135 callbacks are triggered and this function propagates the > 1136 exception. This function is passed normalized connection options > 1137 as its second parameter, and changes this function makes to that > 1138 object _will_ be used for the pending connection attempt. > 1139 > 1140 - onError(request,opt) will be called if a connection to the > 1141 back-end cannot be established. It will be passed the original > 1142 request object (which might be null, depending on the request > 1143 type) and the normalized options object. In the error case, the > 1144 opt object passed to onError() "should" have a property called > 1145 "errorMessage" which contains a description of the problem. > 1146 > 1147 - onError(request,opt) will also be called if connection > 1148 succeeds but the response is not JSON data. > 1149 > 1150 - onResponse(response,request) will be called if the response > 1151 returns JSON data. That data might hold an error response code - > 1152 clients need to check for that. It is passed the response object > 1153 (a plain object) and the original request object. > 1154 > 1155 - afterSend(request,opt) will be called directly after the > 1156 AJAX request is finished, before onError() or onResonse() are > 1157 called. Possible TODO: we explicitly do NOT pass the response to > 1158 this function in order to keep the line between the responsibilities > 1159 of the various callback clear (otherwise this could be used the same > 1160 as onResponse()). In practice it would sometimes be useful have the > 1161 response passed to this function, mainly for logging/debugging > 1162 purposes. > 1163 > 1164 The return value from this function is meaningless because > 1165 AJAX operations tend to take place asynchronously. > 1166 > 1167 */ > 1168 WhAjaj.Connector.prototype.sendRequest = function(request,opt) > 1169 { > 1170 if( !WhAjaj.isFunction(this.sendImpl) ) > 1171 { > 1172 throw new Error("This object has no sendImpl() member function! I don't > 1173 } > 1174 var ex = false; > 1175 var av = Array.prototype.slice.apply( arguments, [0] ); > 1176 > 1177 /** > 1178 FIXME: how to handle the error, vis-a-vis- the callbacks, if > 1179 normalizeAjaxParameters() throws? It can throw if > 1180 (de)JSON-izing fails. > 1181 */ > 1182 var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} ); > 1183 norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm); > 1184 if( ! request ) norm.method = 'GET'; > 1185 var cb = this.callbacks || {}; > 1186 if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) { > 1187 this.callbacks.beforeSend( request, norm ); > 1188 } > 1189 if( WhAjaj.isFunction(norm.beforeSend) ){ > 1190 norm.beforeSend( request, norm ); > 1191 } > 1192 //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm)); > 1193 try { this.sendImpl( request, norm ); } > 1194 catch(e) { ex = e; } > 1195 if(ex) throw ex; > 1196 }; > 1197 > 1198 /** > 1199 sendImpl() holds a concrete back-end connection implementation. It > 1200 can be replaced with a custom implementation if one follows the rules > 1201 described throughout this API. See WhAjaj.Connector.sendImpls for > 1202 the concrete implementations included with this API. > 1203 */ > 1204 //WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpReques > 1205 //WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; > 1206 //WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; > 1207 > 1208 if( 'undefined' !== typeof jQuery ){ > 1209 WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; > 1210 } > 1211 else { > 1212 WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequ