PageSourceSearch

https://2023.techdebtconf.org/javascript/ajax.js?2dbd1cc4f8f2d52ae9c879d7346f4f8a

js techdebtconf.org collected 2026-09-28 12:19:53 UTC 23,784 bytes, 725 lines download raw bytes

1function ajax_post_process(node) {
2  //script tags, such as for datepicker init, are not evaluated when inserted with ajax, this is a workaround that explicitly runs the script contents
3  var inner = node.innerHTML;
4  if (inner.indexOf("<script") >= 0) {
5    var response = $(node);
6    var responseScript = response.filter("script").add(response.find("script"));
7    $.each(responseScript, function(idx, val) {
8      try {
9        eval(val.text);
10      } catch (err) {
11        console.log("Error evaluating javascript as part of ajax response:\n" + err);
12      }
13    });
14  }
15}
16
17function formToJSON(formObj) {
18  var request = "{";
19  for (var i = 0; i < formObj.length; i++)
20    if (formObj.elements[i].name != "") {
21      if (formObj.elements[i].type == "select-multiple") {
22        request = request + "\"" + formObj.elements[i].name + "\" : [ ";
23        for (var j = 0; j < formObj.elements[i].options.length; j++)
24          if (formObj.elements[i].options[j].selected)
25            request = request + formObj.elements[i].options[j].value + ",";
26        request = request.substr(0, request.length - 1) + "]";
27      } else
28        request = request + "\"" + formObj.elements[i].name + "\" : \"" + formObj.elements[i].value + "\"";
29      if (i < formObj.length - 1)
30        request += ",";
31    }
32  request = request + "}";
33  //alert(request);
34  return request;
35};
36
37function encodePost(string) {
38  if (string == undefined)
39    return "";
40  return escape(Utf8.encode(string)).replace(/\+/g, "%2B"); // + is not being encoded, but will be incorrectly interpreted as a space
41  // return escape(encodeURI(string).replace(/%20/g,"+"));  //stupid replace because of difference between urlencode, en post encoding (see http://www.devpro.it/examples/php_js_escaping.php),
42  //using regexp to replaceAll
43}
44
45function formToPost(formObj) {
46  var request = "";
47  var showFileUploadNotSupported = false;
48  for (var i = 0; i < formObj.length; i++)
49    if (formObj.elements != undefined && formObj.elements[i] != undefined && formObj.elements[i].name != "") {
50      if (formObj.elements[i].type == "select-multiple") {
51        for (var j = 0; j < formObj.elements[i].options.length; j++)
52          if (formObj.elements[i].options[j].selected)
53            request += formObj.elements[i].name + "=" + encodePost(formObj.elements[i].options[j].value) + "&";
54      } else if (formObj.elements[i].type == "checkbox") {
55        if (formObj.elements[i].checked == true)
56          request = request + formObj.elements[i].name + "=1&";
57      } else if (formObj.elements[i].type == "radio") {
58        if (formObj.elements[i].checked == true)
59          request = request + formObj.elements[i].name + "=" + formObj.elements[i].value + "&";
60      } else if (formObj.elements[i].nodeName.toLowerCase() == "textarea") {
61        request = request + formObj.elements[i].name + "=" + encodePost(formObj.elements[i].value) + "&";
62      }
63      //exclude submit/button
64      else if ((formObj.elements[i].nodeName.toLowerCase() == "input" && (formObj.elements[i].type == "submit" || formObj.elements[i].type == "button")) ||
65        formObj.elements[i].nodeName.toLowerCase() == "button") {
66      } else if (formObj.elements[i].nodeName.toLowerCase() == "input" && formObj.elements[i].type == "file") {
67        showFileUploadNotSupported = formObj.elements[i].value != "";
68      } else
69        request = request + formObj.elements[i].name + "=" + encodePost(formObj.elements[i].value) + "&";
70    }
71
72  if (showFileUploadNotSupported) {
73    var browserSupportsFormData = window.FormData !== undefined;
74    if( browserSupportsFormData ){
75      doShowError('Something went wrong. The files could not be uploaded through this action.');
76    } else {
77      doShowError('You are using an (older) browser that does not support file uploads in this form');
78    }
79  }
80  //alert(request);
81  return request;
82};
83
84function clickFirstButton(formObj) {
85  for (var i = 0; i < formObj.length; i++) {
86    if (formObj.elements != undefined && formObj.elements[i] != undefined) {
87      if (formObj.elements[i].nodeName.toLowerCase() == "input" && formObj.elements[i].type == "submit") {
88        return true; //just let the normal form submit proceed
89      }
90      if (formObj.elements[i].nodeName.toLowerCase() == "input" && formObj.elements[i].type == "button") {
91        formObj.elements[i].click();
92        return false;
93      }
94    }
95  }
96  return false;
97}
98
99function newRequest() {
100  xmlHttp = null;
101
102  try {
103    xmlHttp = new XMLHttpRequest();
104  } catch (e) {
105    try {
106      xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
107    } catch (e) {
108      xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
109    }
110  }
111  return xmlHttp;
112
113}
114
115function findEnclosingPlaceholder(thisobject) {
116  current = thisobject;
117  result = null;
118  while (current != null && result == null) {
119    if (current.className && current.className.indexOf("webdsl-placeholder") != -1) {
120      result = current;
121    } else {
122      current = current.parentNode;
123    }
124  }
125  return result;
126}
127
128function findOuterEnclosingElement(thisobject, attrname, value) {
129  current = thisobject;
130  result = null;
131  while (current != null && current.tagName != "HTML") {
132    if (current.getAttribute(attrname) == value) {
133      result = current;
134    }
135    current = current.parentNode;
136  }
137  return result;
138}
139
140function findElementById(thisobject, id) {
141  //step one, find the right parent to search within
142  current = thisobject;
143  if (current == null) {
144    current = window.document.body;
145  }
146  result = null;
147  foundscope = false;
148  while (current != null && !foundscope && result == null) {
149    //while going up in the tree, found the id.
150    if (current.id != undefined && current.id == id) {
151      result = current;
152    }
153    //found the scope boundary of this template, search inward
154    else if (current.className && current.className.indexOf("scopediv") != -1) {
155      foundscope = true;
156      if (id == "this")
157        result = current;
158      else
159        result = findTopDown(current, id);
160    }
161    current = current.parentNode;
162  }
163
164  //nothing found, search upwards, to the closest enclosing node with the proper id
165  //useful in recursion
166  if (result == null) {
167    current = thisobject;
168    while (current.parentNode != null) {
169      current = current.parentNode;
170      if (current.id != undefined && current.id == id)
171        return current;
172    }
173  }
174
175  //nothing found, search in the whole document
176  if (result == null)
177    result = document.getElementById(id);
178
179  if (result == null)
180    if (show_webdsl_debug) {
181      alert("Object with id '" + id + "' does not exist in the document!");
182    }
183
184  return result;
185}
186
187function findTopDown(element, id) {
188  if (element.childNodes != undefined) {
189    for (var i = 0; i < element.childNodes.length; i++) {
190      var child = element.childNodes[i];
191      if (child.id != undefined && child.id == id)
192        return child;
193    }
194    for (var i = 0; i < element.childNodes.length; i++)
195      if (element.childNodes[i].nodeType == 1) { //element node
196        result = findTopDown(element.childNodes[i], id);
197        if (result != null)
198          return result;
199      }
200  }
201  return null;
202}
203
204var __skip_next_action = false;
205var __next_action = false;
206
207function serverInvoke(template, action, jsonparams, thisform, thisobject, loadfeedback, placeholderId) {
208  var attachObj, loadingimage;
209  __skip_next_action = false;
210  if (loadfeedback) {
211    attachObj = typeof loadImageElem !== 'undefined' ? loadImageElem : thisobject;
212    loadImageElem = undefined;
213    loadingimage = startLoading(attachObj);
214  }
215  serverInvokeCommon(template, action, jsonparams, thisform, thisobject, placeholderId,
216    function(req) {
217      if (req.readyState == 4) {
218        if (req.status == 200) {
219          clientExecute(req.responseText, thisobject);
220        } else if (req.status == 413) {
221          doShowError('The file is too large to upload. Please try compressing the file or uploading a smaller selection.');
222        } else {
223          showError(thisobject, 'Error while handling this action, the server returned status ' + req.status + '. The action may no longer be available. Copy your unsaved changes before leaving/refreshing this page.');
224        }
225        if (loadfeedback) {
226          stopLoading(attachObj, loadingimage);
227        }
228        __requestcount--;
229        // handle multiple submit actions for inputajax
230        if( __next_action !== false ){
231          if( ! __skip_next_action ){
232            __next_action();
233          }
234          __next_action = false;
235        }
236      }
237    }
238  );
239}
240
241
242function showError(thisobject, genericError) {
243  var msg = window.actionFailureMessage !== undefined ? actionFailureMessage : genericError;
244  var thisobjectSpecificError = $(thisobject).data('action-failed');
245
246  if (thisobjectSpecificError !== undefined)
247    msg = thisobjectSpecificError;
248
249  doShowError(msg);
250}
251
252function doShowError(msg) {
253  notify(msg);
254  var dismissHTML = "<a href=\"javascript:$('.action-failure-msg').fadeOut();void(0)\" style=\"margin-left: 15px;\">Dismiss</a>";
255  var errorElem = $("<span class=\"action-failure-msg\" style=\"display: none;\">" + msg + dismissHTML + "</span>");
256  $('body').append(errorElem);
257  errorElem.fadeIn();
258}
259
260var __requestcount = 0;
261
262function serverInvokeCommon(template, action, jsonparams, thisform, thisobject, placeholderId, callback) {
263  var supportsFormData = window.FormData !== undefined;
264  __requestcount++;
265
266  if (supportsFormData) {
267    $.ajax({
268      type: 'POST',
269      method: 'POST',
270      cache: false,
271      contentType: false,
272      processData: false,
273      url: template,
274      data: createFormData(action, jsonparams, thisform, thisobject, placeholderId),
275      complete: callback
276    });
277  } else {
278    req = newRequest();
279    req.open("POST", template, true); //chosen for always asynchronous (true), even for testing, to have tested system as close to real thing as possible, also synchronous/false doesn't seem to work with WebDriver currently. The downside is that tests with ajax calls need sleeps to wait for the response.
280    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
281    req.setRequestHeader("charset", "UTF-8");
282
283    req.onreadystatechange = function() {
284      callback(this);
285    };
286
287    data = createData(action, jsonparams, thisform, thisobject, placeholderId);
288
289    req.send(data);
290  }
291}
292
293function createFormData(action, jsonparams, thisform, thisobject, placeholderId) {
294  var theForm = (thisform != '') ? findElementById(thisobject, thisform) : null;
295  var formdata = theForm ? new FormData(theForm) : new FormData();
296  $.each(jsonparams, function(key, val) {
297    formdata.append(val.name, val.value)
298  });
299
300  if (action != '') {
301    formdata.append(action, "1");
302    formdata.append("__ajax_runtime_request__", placeholderId);
303  } else {
304    formdata.append("post-request-no-action", 1);
305  }
306  return formdata;
307}
308
309function replaceWithoutAction(serviceURL, jsonData, idOfElemToReplace) {
310  serverInvokeCommon(serviceURL, '', jsonData, '', '', '',
311    function(req) {
312      if (req.readyState == 4) {
313        if (req.status == 200) {
314          if(req.responseText.substring(0, 15) == "<!DOCTYPE html>"){
315            //in case a html document is returned, don't do anything.
316            console.log("Unexpected server response for " + serviceURL + ". Received complete document instead of partial document");
317            return;
318          }
319          var theNode = document.getElementById(idOfElemToReplace);
320          theNode.innerHTML = req.responseText;
321          ajax_post_process(theNode);
322        } else if (req.status != 200) {
323          console.log("Unexpected server response for " + serviceURL + " status code: " + req.status);
324//          showError(thisobject, 'Invalid return of server: ' + req.status);
325        }
326      }
327    }
328  );
329}
330
331var loadImageElem;
332
333function startLoading(attachObj) {
334  var div = document.createElement("div");
335  div.innerHTML = '<div class="lds-ring"><div></div><div></div><div></div><div></div>' + loadingStyle + '</div>';
336  var container = div.firstChild;
337  container.style.display = 'inline';
338  container.style.position = 'absolute';
339  attachObj.appendChild(container);
340  attachObj.disabled = true;
341  attachObj.temp_onclick = attachObj.onclick;
342  attachObj.onclick = "false;";
343  return container;
344}
345
346function stopLoading(thisobject, loadingimage) {
347  thisobject.removeChild(loadingimage);
348  thisobject.disabled = false;
349  thisobject.onclick = thisobject.temp_onclick;
350}
351
352
353
354function createData(action, jsonparams, thisform, thisobject, placeholderId) {
355  if (action != '') {
356    data = action + "=1&";
357    data += "__ajax_runtime_request__="+placeholderId+"&";
358  } else {
359    data = "post-request-no-action=1&";
360  }
361  paramsdata = eval(jsonparams);
362  if (paramsdata == undefined)
363    if (show_webdsl_debug) {
364      alert("invalid JSON page parameters in ajax action call: " + jsonparams);
365    }
366  for (i = 0; i < paramsdata.length; i++) {
367    data += paramsdata[i].name + "=" + encodePost(paramsdata[i].value) + "&";
368  }
369  //send a form if applicable
370  if (thisform != '')
371    data += formToPost(findElementById(thisobject, thisform));
372  //remove trailing &
373  if (data.charAt(data.length - 1) == '&')
374    data = data.substr(0, data.length - 1);
375  return data;
376}
377
378function serverInvokeDownloadCompatible(template, action, jsonparams, thisform, thisobject, placeholderId) {
379  // Create an IFRAME.
380  var iframe = document.createElement("iframe");
381
382  // This makes the IFRAME invisible to the user.
383  iframe.style.display = "none";
384
385  //encode form data
386  data = createData(action, jsonparams, thisform, thisobject, placeholderId);
387
388  // Point the IFRAME to the action invoke
389  iframe.src = template + "?action-call-with-get-request-type=1&" + data;
390
391  // Add the IFRAME to the page. This will trigger a request to the action now.
392  document.body.appendChild(iframe);
393
394}
395
396function requireJSResource( uri, callback ){
397  if( 1 > $('script[src="' + uri + '"]').length ){
398    console.log("Dynamically loading: " + uri);
399    var head = document.getElementsByTagName('head')[0];
400    var script = document.createElement('script');
401    script.type = 'text/javascript';
402    script.src = uri;
403    script.onload = callback;
404    script.onerror = callback;
405    head.appendChild(script);
406  } else {
407    callback();
408  }
409}
410function requireCSSResource( uri ){
411  if( 1 > $('link[href="' + uri + '"]').length ){
412    console.log("Dynamically loading: " + uri);
413    var head = document.getElementsByTagName('head')[0];
414    var link = document.createElement("link");
415    link.type = "text/css";
416    link.rel = "stylesheet";
417    link.href = uri;
418    head.appendChild(link);
419  }
420}
421
422//load JS resources and defer calls to ajax_post_process until resources are loaded
423function requireJSResources( arr ){
424  var resourcesToLoad = arr.length;
425  if(resourcesToLoad < 1){
426    return;
427  }
428
429  var orig_ajax_post_process = ajax_post_process;
430  var post_process_calls_queue = function(){};
431  ajax_post_process = function( node ){
432    var queue_head = post_process_calls_queue;
433    post_process_calls_queue = function(){
434      queue_head();
435      orig_ajax_post_process(node);
436    }
437  }; 
438   
439  var nextIdx = 0;
440  function tryLoadNextResource(){
441    if(nextIdx >= resourcesToLoad){
442      ajax_post_process = orig_ajax_post_process;
443      post_process_calls_queue();
444    } else {
445      var resource = arr[nextIdx];
446      nextIdx++;
447      requireJSResource( resource, tryLoadNextResource );
448    }
449  }
450  tryLoadNextResource();
451}
452
453function clientExecute(jsoncode, thisobject) {
454  
455  data = eval(jsoncode);
456  if (data == undefined)
457    if (show_webdsl_debug) {
458      alert("received no valid response from the server! " + jsoncode);
459    }
460
461  for (var i = 0; i < data.length; i++) {
462    command = data[i];
463    try {
464      if (command.action == "replace")
465        replace(command, thisobject);
466      else if (command.action == "append")
467        append(command, thisobject);
468      else if (command.action == "clear")
469        clear(command, thisobject);
470      else if (command.action == "visibility")
471        changevisibility(command, thisobject);
472      else if (command.action == "relocate")
473        relocate(command);
474      else if (command.action == "restyle")
475        restyle(command, thisobject);
476      else if (command.action == "refresh")
477        location.reload(true);
478      //used for displaying validation messages
479      else if (command.action == "replaceall") {
480        replaceall(command);
481      } else if (command.action == "runscript") {
482        eval(command.value);
483      } else if (command.action == "logsql") {
484        appendLogSql(command);
485      } else if (command.action == "require_js") {
486        requireJSResources( command.value );
487      } else if (command.action == "require_css") {
488        var arr = command.value;
489        for (var j = 0; j < arr.length; j++) {
490          requireCSSResource( arr[j] );
491        }
492      } else if (command.action == "skip_next_action") {
493        __skip_next_action = true;
494      } else if (command.action != undefined) //last command might equal {}
495        if (show_webdsl_debug) {
496          alert("unknown client command: " + command.action);
497        }
498    } catch (error) {
499      console.log(error);
500    }
501  }
502}
503
504function notify(string) {
505  if (show_webdsl_debug) {
506    alert(string);
507  }
508  console.log(string);
509}
510
511function replaceall(command) {
512  var theNode = window.document.body;
513  theNode.innerHTML = command.value;
514  ajax_post_process(theNode);
515}
516
517function replace(command, thisobject) {
518  var theNode;
519
520  if (command.id.type == "enclosing-placeholder") {
521    theNode = findEnclosingPlaceholder(thisobject);
522  } else if (command.id.submitid) {
523    theNode = findOuterEnclosingElement(thisobject, "submitid", command.id.submitid);
524    command.id = "this";
525  } else {
526    theNode = findElementById(thisobject, command.id);
527  }
528
529  if (command.id != "this") {
530    theNode.innerHTML = command.value;
531  } else {
532    var newElem = document.createElement("tmp");
533    newElem.innerHTML = command.value;
534    var parent = theNode.parentNode;
535    var index = Array.prototype.indexOf.call(parent.children, theNode);
536    parent.replaceChild(newElem.childNodes[0], theNode);
537    theNode = parent.childNodes[index];
538  }
539
540  ajax_post_process(theNode);
541}
542
543function append(command, thisobject) {
544  if (command.value && command.value != "") {
545    var theNode = findElementById(thisobject, command.id);
546    if (command.id != "this") {
547      var newElem = document.createElement("span");
548      newElem.setAttribute("class", "appended");
549      newElem.innerHTML = command.value;
550      theNode.appendChild(newElem);
551      theNode = newElem;
552    } else //this has other semantics
553    {
554      var newElem = document.createElement("tmp");
555      newElem.innerHTML = command.value;
556      theNode.parentNode.appendChild(newElem.childNodes[0], theNode); //wrapper node "this" always available
557      //note that this might break with no template based replacements
558      theNode = newElem.childNodes[0];
559    }
560    ajax_post_process(theNode);
561  }
562}
563
564function clear(command, thisobject) {
565  var theNode = findElementById(thisobject, command.id);
566  while (theNode.hasChildNodes()) {
567    theNode.removeChild(theNode.firstChild);
568  }
569}
570
571var cachedDisplays = new Object(); //stores the default display stiles
572
573function changevisibility(command, thisobject) {
574  var theNode = findElementById(thisobject, command.id);
575
576  if (command.value == "toggle") {
577    if (theNode.style.display == "none")
578      command.value = "show";
579    else
580      command.value = "hide";
581  }
582
583  if (command.value == "hide" && theNode.style.display != "none") {
584    cachedDisplays[command.id] = theNode.style.display; //cache the style visibility
585    theNode.style.display = "none";
586  } else if (command.value == "show" && theNode.style.display == "none") {
587    if (cachedDisplays[command.id] != undefined)
588      theNode.style.display = cachedDisplays[command.id];
589    else
590      theNode.style.display = "block"; //default if cache not found (e.g. the object started as being invisible)
591  }
592}
593
594function restyle(command, thisobject) {
595  var theNode = findElementById(thisobject, command.id);
596  theNode.className = command.value;
597}
598
599function relocate(command) {
600  window.location = command.value;
601}
602
603function loadCSS(url) {
604  e = document.createElement("link");
605  e.setAttribute("rel", "stylesheet")
606  e.setAttribute("type", "text/css")
607  e.setAttribute("href", url)
608  document.getElementsByTagName("head")[0].appendChild(e);
609}
610
611//UTF encoding: http://www.webtoolkit.info/javascript-utf8.html
612var Utf8 = {
613  // public method for url encoding
614  encode: function(string) {
615    if (string == null)
616      return "";
617    string = string.replace(/\r\n/g, "\n");
618    var utftext = "";
619    for (var n = 0; n < string.length; n++) {
620      var c = string.charCodeAt(n);
621      if (c < 128) {
622        utftext += String.fromCharCode(c);
623      } else if ((c > 127) && (c < 2048)) {
624        utftext += String.fromCharCode((c >> 6) | 192);
625        utftext += String.fromCharCode((c & 63) | 128);
626      } else {
627        utftext += String.fromCharCode((c >> 12) | 224);
628        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
629        utftext += String.fromCharCode((c & 63) | 128);
630      }
631    }
632    return utftext;
633  },
634  // public method for url decoding
635  decode: function(utftext) {
636    var string = "";
637    var i = 0;
638    var c = c1 = c2 = 0;
639    while (i < utftext.length) {
640      c = utftext.charCodeAt(i);
641      if (c < 128) {
642        string += String.fromCharCode(c);
643        i++;
644      } else if ((c > 191) && (c < 224)) {
645        c2 = utftext.charCodeAt(i + 1);
646        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
647        i += 2;
648      } else {
649        c2 = utftext.charCodeAt(i + 1);
650        c3 = utftext.charCodeAt(i + 2);
651        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
652        i += 3;
653      }
654    }
655    return string;
656  }
657};
658
659//avoid too many request while typing in a field with onkeyup trigger
660var onkeyupdelay = function() {
661  var timer = 0; //scoped inside this function block, triggering onkeyup again before timeout resets the timer for that particular action
662  return function(callback) {
663    clearTimeout(timer);
664    timer = setTimeout(callback, (typeof onkeyupdelayoverride === 'undefined') ? 250 : onkeyupdelayoverride);
665  }
666}();
667
668function appendLogSql(command) {
669  var hr = document.createElement('hr');
670  document.body.appendChild(hr);
671
672  var div = document.createElement('div');
673  div.className = 'logsql';
674  div.innerHTML = command.value;
675  document.body.appendChild(div);
676}
677
678//report error when character is not in Basic Multilingual Plane (BMP), and would encode as 4 bytes UTF-8
679function checkForUnsupportedCharacters( textValue ){
680  for (var i=0; i<textValue.length; i++) {
681    if (textValue.codePointAt(i) > 65533) {
682      doShowError("You entered text that contains a unicode character '" + String.fromCodePoint( textValue.codePointAt(i) ) + "' which is not supported.");
683      return;
684    }
685  }
686}
687
688var loadingStyle = '\
689<style>\
690.lds-ring {\
691  display: inline-block;\
692  position: relative;\
693  width: 32px;\
694  height: 32px;\
695}\
696.lds-ring div {\
697  box-sizing: border-box;\
698  display: block;\
699  position: absolute;\
700  width: 24px;\
701  height: 24px;\
702  margin: 3px;\
703  border: 3px solid #ddd;\
704  border-radius: 50%;\
705  animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\
706  border-color: #ddd transparent transparent transparent;\
707}\
708.lds-ring div:nth-child(1) {\
709  animation-delay: -0.45s;\
710}\
711.lds-ring div:nth-child(2) {\
712  animation-delay: -0.3s;\
713}\
714.lds-ring div:nth-child(3) {\
715  animation-delay: -0.15s;\
716}\
717@keyframes lds-ring {\
718  0% {\
719    transform: rotate(0deg);\
720  }\
721  100% {\
722    transform: rotate(360deg);\
723  }\
724}\
725</style>';

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.