1/** 2 * Magento 3 * 4 * NOTICE OF LICENSE 5 * 6 * This source file is subject to the Academic Free License (AFL 3.0) 7 * that is bundled with this package in the file LICENSE_AFL.txt. 8 * It is also available through the world-wide-web at this URL: 9 * http://opensource.org/licenses/afl-3.0.php 10 * If you did not receive a copy of the license and are unable to 11 * obtain it through the world-wide-web, please send an email 12 * to [email protected] so we can send you a copy immediately. 13 * 14 * DISCLAIMER 15 * 16 * Do not edit or add to this file if you wish to upgrade Magento to newer 17 * versions in the future. If you wish to customize Magento for your 18 * needs please refer to http://www.magento.com for more information. 19 * 20 * @category Varien 21 * @package js 22 * @copyright Copyright (c) 2006-2019 Magento, Inc. (http://www.magento.com) 23 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) 24 */ 25function popWin(url,win,para) { 26 var win = window.open(url,win,para); 27 win.focus(); 28} 29 30function setLocation(url){ 31 window.location.href = encodeURI(url); 32} 33 34function setPLocation(url, setFocus){ 35 if( setFocus ) { 36 window.opener.focus(); 37 } 38 window.opener.location.href = encodeURI(url); 39} 40 41/** 42 * @deprecated 43 */ 44function setLanguageCode(code, fromCode){ 45 //TODO: javascript cookies have different domain and path than php cookies 46 var href = window.location.href; 47 var after = '', dash; 48 if (dash = href.match(/\#(.*)$/)) { 49 href = href.replace(/\#(.*)$/, ''); 50 after = dash[0]; 51 } 52 53 if (href.match(/[?]/)) { 54 var re = /([?&]store=)[a-z0-9_]*/; 55 if (href.match(re)) { 56 href = href.replace(re, '$1'+code); 57 } else { 58 href += '&store='+code; 59 } 60 61 var re = /([?&]from_store=)[a-z0-9_]*/; 62 if (href.match(re)) { 63 href = href.replace(re, ''); 64 } 65 } else { 66 href += '?store='+code; 67 } 68 if (typeof(fromCode) != 'undefined') { 69 href += '&from_store='+fromCode; 70 } 71 href += after; 72 73 setLocation(href); 74} 75 76/** 77 * Add classes to specified elements. 78 * Supported classes are: 'odd', 'even', 'first', 'last' 79 * 80 * @param elements - array of elements to be decorated 81 * [@param decorateParams] - array of classes to be set. If omitted, all available will be used 82 */ 83function decorateGeneric(elements, decorateParams) 84{ 85 var allSupportedParams = ['odd', 'even', 'first', 'last']; 86 var _decorateParams = {}; 87 var total = elements.length; 88 89 if (total) { 90 // determine params called 91 if (typeof(decorateParams) == 'undefined') { 92 decorateParams = allSupportedParams; 93 } 94 if (!decorateParams.length) { 95 return; 96 } 97 for (var k in allSupportedParams) { 98 _decorateParams[allSupportedParams[k]] = false; 99 } 100 for (var k in decorateParams) { 101 _decorateParams[decorateParams[k]] = true; 102 } 103 104 // decorate elements 105 // elements[0].addClassName('first'); // will cause bug in IE (#5587) 106 if (_decorateParams.first) { 107 Element.addClassName(elements[0], 'first'); 108 } 109 if (_decorateParams.last) { 110 Element.addClassName(elements[total-1], 'last'); 111 } 112 for (var i = 0; i < total; i++) { 113 if ((i + 1) % 2 == 0) { 114 if (_decorateParams.even) { 115 Element.addClassName(elements[i], 'even'); 116 } 117 } 118 else { 119 if (_decorateParams.odd) { 120 Element.addClassName(elements[i], 'odd'); 121 } 122 } 123 } 124 } 125} 126 127/** 128 * Decorate table rows and cells, tbody etc 129 * @see decorateGeneric() 130 */ 131function decorateTable(table, options) { 132 var table = $(table); 133 if (table) { 134 // set default options 135 var _options = { 136 'tbody' : false, 137 'tbody tr' : ['odd', 'even', 'first', 'last'], 138 'thead tr' : ['first', 'last'], 139 'tfoot tr' : ['first', 'last'], 140 'tr td' : ['last'] 141 }; 142 // overload options 143 if (typeof(options) != 'undefined') { 144 for (var k in options) { 145 _options[k] = options[k]; 146 } 147 } 148 // decorate 149 if (_options['tbody']) { 150 decorateGeneric(table.select('tbody'), _options['tbody']); 151 } 152 if (_options['tbody tr']) { 153 decorateGeneric(table.select('tbody tr'), _options['tbody tr']); 154 } 155 if (_options['thead tr']) { 156 decorateGeneric(table.select('thead tr'), _options['thead tr']); 157 } 158 if (_options['tfoot tr']) { 159 decorateGeneric(table.select('tfoot tr'), _options['tfoot tr']); 160 } 161 if (_options['tr td']) { 162 var allRows = table.select('tr'); 163 if (allRows.length) { 164 for (var i = 0; i < allRows.length; i++) { 165 decorateGeneric(allRows[i].getElementsByTagName('TD'), _options['tr td']); 166 } 167 } 168 } 169 } 170} 171 172/** 173 * Set "odd", "even" and "last" CSS classes for list items 174 * @see decorateGeneric() 175 */ 176function decorateList(list, nonRecursive) { 177 if ($(list)) { 178 if (typeof(nonRecursive) == 'undefined') { 179 var items = $(list).select('li'); 180 } 181 else { 182 var items = $(list).childElements(); 183 } 184 decorateGeneric(items, ['odd', 'even', 'last']); 185 } 186} 187 188/** 189 * Set "odd", "even" and "last" CSS classes for list items 190 * @see decorateGeneric() 191 */ 192function decorateDataList(list) { 193 list = $(list); 194 if (list) { 195 decorateGeneric(list.select('dt'), ['odd', 'even', 'last']); 196 decorateGeneric(list.select('dd'), ['odd', 'even', 'last']); 197 } 198} 199 200/** 201 * Parse SID and produces the correct URL 202 */ 203function parseSidUrl(baseUrl, urlExt) { 204 var sidPos = baseUrl.indexOf('/?SID='); 205 var sid = ''; 206 urlExt = (urlExt != undefined) ? urlExt : ''; 207 208 if(sidPos > -1) { 209 sid = '?' + baseUrl.substring(sidPos + 2); 210 baseUrl = baseUrl.substring(0, sidPos + 1); 211 } 212 213 return baseUrl+urlExt+sid; 214} 215 216/** 217 * Formats currency using patern 218 * format - JSON (pattern, decimal, decimalsDelimeter, groupsDelimeter) 219 * showPlus - true (always show '+'or '-'), 220 * false (never show '-' even if number is negative) 221 * null (show '-' if number is negative) 222 */ 223 224function formatCurrency(price, format, showPlus){ 225 var precision = isNaN(format.precision = Math.abs(format.precision)) ? 2 : format.precision; 226 var requiredPrecision = isNaN(format.requiredPrecision = Math.abs(format.requiredPrecision)) ? 2 : format.requiredPrecision; 227 228 //precision = (precision > requiredPrecision) ? precision : requiredPrecision; 229 //for now we don't need this difference so precision is requiredPrecision 230 precision = requiredPrecision; 231 232 var integerRequired = isNaN(format.integerRequired = Math.abs(format.integerRequired)) ? 1 : format.integerRequired; 233 234 var decimalSymbol = format.decimalSymbol == undefined ? "," : format.decimalSymbol; 235 var groupSymbol = format.groupSymbol == undefined ? "." : format.groupSymbol; 236 var groupLength = format.groupLength == undefined ? 3 : format.groupLength; 237 238 var s = ''; 239 240 if (showPlus == undefined || showPlus == true) { 241 s = price < 0 ? "-" : ( showPlus ? "+" : ""); 242 } else if (showPlus == false) { 243 s = ''; 244 } 245 246 var i = parseInt(price = Math.abs(+price || 0).toFixed(precision)) + ""; 247 var pad = (i.length < integerRequired) ? (integerRequired - i.length) : 0; 248 while (pad) { i = '0' + i; pad--; } 249 j = (j = i.length) > groupLength ? j % groupLength : 0; 250 re = new RegExp("(\\d{" + groupLength + "})(?=\\d)", "g"); 251 252 /** 253 * replace(/-/, 0) is only for fixing Safari bug which appears 254 * when Math.abs(0).toFixed() executed on "0" number. 255 * Result is "0.-0" :( 256 */ 257 var r = (j ? i.substr(0, j) + groupSymbol : "") + i.substr(j).replace(re, "$1" + groupSymbol) + (precision ? decimalSymbol + Math.abs(price - i).toFixed(precision).replace(/-/, 0).slice(2) : ""); 258 var pattern = ''; 259 if (format.pattern.indexOf('{sign}') == -1) { 260 pattern = s + format.pattern; 261 } else { 262 pattern = format.pattern.replace('{sign}', s); 263 } 264 265 return pattern.replace('%s', r).replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 266}; 267 268function expandDetails(el, childClass) {
269 if (Element.hasClassName(el,'show-details')) { 270 $$(childClass).each(function(item){ 271 item.hide(); 272 }); 273 Element.removeClassName(el,'show-details'); 274 } 275 else { 276 $$(childClass).each(function(item){ 277 item.show(); 278 }); 279 Element.addClassName(el,'show-details'); 280 } 281} 282 283// Version 1.0 284var isIE = navigator.appVersion.match(/MSIE/) == "MSIE"; 285 286if (!window.Varien) 287 var Varien = new Object(); 288 289Varien.showLoading = function(){ 290 var loader = $('loading-process'); 291 loader && loader.show(); 292}; 293Varien.hideLoading = function(){ 294 var loader = $('loading-process'); 295 loader && loader.hide(); 296}; 297Varien.GlobalHandlers = { 298 onCreate: function() { 299 Varien.showLoading(); 300 }, 301 302 onComplete: function() { 303 if(Ajax.activeRequestCount == 0) { 304 Varien.hideLoading(); 305 } 306 } 307}; 308 309Ajax.Responders.register(Varien.GlobalHandlers); 310 311/** 312 * Quick Search form client model 313 */ 314Varien.searchForm = Class.create(); 315Varien.searchForm.prototype = { 316 initialize : function(form, field, emptyText){ 317 this.form = $(form); 318 this.field = $(field); 319 this.emptyText = emptyText; 320 321 Event.observe(this.form, 'submit', this.submit.bind(this)); 322 Event.observe(this.field, 'focus', this.focus.bind(this)); 323 Event.observe(this.field, 'blur', this.blur.bind(this)); 324 this.blur(); 325 }, 326 327 submit : function(event){ 328 if (this.field.value == this.emptyText || this.field.value == ''){ 329 Event.stop(event); 330 return false; 331 } 332 return true; 333 }, 334 335 focus : function(event){ 336 if(this.field.value==this.emptyText){ 337 this.field.value=''; 338 } 339 340 }, 341 342 blur : function(event){ 343 if(this.field.value==''){ 344 this.field.value=this.emptyText; 345 } 346 }, 347 348 initAutocomplete : function(url, destinationElement){ 349 new Ajax.Autocompleter( 350 this.field, 351 destinationElement, 352 url, 353 { 354 paramName: this.field.name, 355 method: 'get', 356 minChars: 2, 357 updateElement: this._selectAutocompleteItem.bind(this), 358 onShow : function(element, update) { 359 if(!update.style.position || update.style.position=='absolute') { 360 update.style.position = 'absolute'; 361 Position.clone(element, update, { 362 setHeight: false, 363 offsetTop: element.offsetHeight 364 }); 365 }
366 Effect.Appear(update,{duration:0}); 367 } 368 369 } 370 ); 371 }, 372 373 _selectAutocompleteItem : function(element){ 374 if(element.title){ 375 this.field.value = element.title; 376 } 377 this.form.submit(); 378 } 379}; 380 381Varien.Tabs = Class.create(); 382Varien.Tabs.prototype = { 383 initialize: function(selector) { 384 var self=this; 385 $$(selector+' a').each(this.initTab.bind(this)); 386 }, 387 388 initTab: function(el) { 389 el.href = 'javascript:void(0)'; 390 if ($(el.parentNode).hasClassName('active')) { 391 this.showContent(el); 392 } 393 el.observe('click', this.showContent.bind(this, el)); 394 }, 395 396 showContent: function(a) { 397 var li = $(a.parentNode), ul = $(li.parentNode); 398 ul.getElementsBySelector('li', 'ol').each(function(el){ 399 var contents = $(el.id+'_contents'); 400 if (el==li) { 401 el.addClassName('active'); 402 contents.show(); 403 } else { 404 el.removeClassName('active'); 405 contents.hide(); 406 } 407 }); 408 } 409}; 410 411Varien.DateElement = Class.create(); 412Varien.DateElement.prototype = { 413 initialize: function(type, content, required, format) { 414 if (type == 'id') { 415 // id prefix 416 this.day = $(content + 'day'); 417 this.month = $(content + 'month'); 418 this.year = $(content + 'year'); 419 this.full = $(content + 'full'); 420 this.advice = $(content + 'date-advice'); 421 } else if (type == 'container') { 422 // content must be container with data 423 this.day = content.day; 424 this.month = content.month; 425 this.year = content.year; 426 this.full = content.full; 427 this.advice = content.advice; 428 } else { 429 return; 430 } 431 432 this.required = required; 433 this.format = format; 434 435 this.day.addClassName('validate-custom'); 436 this.day.validate = this.validate.bind(this); 437 this.month.addClassName('validate-custom'); 438 this.month.validate = this.validate.bind(this); 439 this.year.addClassName('validate-custom'); 440 this.year.validate = this.validate.bind(this); 441 442 this.setDateRange(false, false); 443 this.year.setAttribute('autocomplete','off'); 444 445 this.advice.hide(); 446 447 var date = new Date; 448 this.curyear = date.getFullYear(); 449 }, 450 validate: function() { 451 var error = false, 452 day = parseInt(this.day.value, 10) || 0, 453 month = parseInt(this.month.value, 10) || 0, 454 year = parseInt(this.year.value, 10) || 0; 455 if (this.day.value.strip().empty() 456 && this.month.value.strip().empty() 457 && this.year.value.strip().empty() 458 ) { 459 if (this.required) { 460 error = 'This date is a required value.'; 461 } else { 462 this.full.value = ''; 463 } 464 } else if (!day || !month || !year) { 465 error = 'Please enter a valid full date'; 466 } else { 467 var date = new Date, countDaysInMonth = 0, errorType = null; 468 date.setYear(year);date.setMonth(month-1);date.setDate(32); 469 countDaysInMonth = 32 - date.getDate(); 470 if(!countDaysInMonth || countDaysInMonth>31) countDaysInMonth = 31; 471 if(year < 1900) error = this.errorTextModifier(this.validateDataErrorText); 472 473 if (day<1 || day>countDaysInMonth) { 474 errorType = 'day'; 475 error = 'Please enter a valid day (1-%d).'; 476 } else if (month<1 || month>12) { 477 errorType = 'month'; 478 error = 'Please enter a valid month (1-12).'; 479 } else { 480 if(day % 10 == day) this.day.value = '0'+day; 481 if(month % 10 == month) this.month.value = '0'+month; 482 this.full.value = this.format.replace(/%[mb]/i, this.month.value).replace(/%[de]/i, this.day.value).replace(/%y/i, this.year.value); 483 var testFull = this.month.value + '/' + this.day.value + '/'+ this.year.value; 484 var test = new Date(testFull); 485 if (isNaN(test)) { 486 error = 'Please enter a valid date.'; 487 } else { 488 this.setFullDate(test); 489 } 490 } 491 var valueError = false;
492 if (!error && !this.validateData()){//(year<1900 || year>curyear) { 493 errorType = this.validateDataErrorType;//'year'; 494 valueError = this.validateDataErrorText;//'Please enter a valid year (1900-%d).'; 495 error = valueError; 496 } 497 } 498 499 if (error !== false) { 500 try { 501 error = Translator.translate(error); 502 } 503 catch (e) {} 504 if (!valueError) { 505 this.advice.innerHTML = error.replace('%d', countDaysInMonth); 506 } else { 507 this.advice.innerHTML = this.errorTextModifier(error); 508 } 509 this.advice.show(); 510 return false; 511 } 512 513 // fixing elements class 514 this.day.removeClassName('validation-failed'); 515 this.month.removeClassName('validation-failed'); 516 this.year.removeClassName('validation-failed'); 517 518 this.advice.hide(); 519 return true; 520 }, 521 validateData: function() { 522 var year = this.fullDate.getFullYear(); 523 return (year>=1900 && year<=this.curyear); 524 }, 525 validateDataErrorType: 'year', 526 validateDataErrorText: 'Please enter a valid year (1900-%d).', 527 errorTextModifier: function(text) { 528 text = Translator.translate(text); 529 return text.replace('%d', this.curyear); 530 }, 531 setDateRange: function(minDate, maxDate) { 532 this.minDate = minDate; 533 this.maxDate = maxDate; 534 }, 535 setFullDate: function(date) { 536 this.fullDate = date; 537 } 538}; 539 540Varien.DOB = Class.create(); 541Varien.DOB.prototype = { 542 initialize: function(selector, required, format) { 543 var el = $$(selector)[0]; 544 var container = {}; 545 container.day = Element.select(el, '.dob-day input')[0]; 546 container.month = Element.select(el, '.dob-month input')[0]; 547 container.year = Element.select(el, '.dob-year input')[0]; 548 container.full = Element.select(el, '.dob-full input')[0]; 549 container.advice = Element.select(el, '.validation-advice')[0]; 550 551 new Varien.DateElement('container', container, required, format); 552 } 553}; 554 555Varien.dateRangeDate = Class.create(); 556Varien.dateRangeDate.prototype = Object.extend(new Varien.DateElement(), { 557 validateData: function() { 558 var validate = true; 559 if (this.minDate || this.maxValue) { 560 if (this.minDate) { 561 this.minDate = new Date(this.minDate); 562 this.minDate.setHours(0); 563 if (isNaN(this.minDate)) { 564 this.minDate = new Date('1/1/1900'); 565 } 566 validate = validate && (this.fullDate >= this.minDate); 567 } 568 if (this.maxDate) { 569 this.maxDate = new Date(this.maxDate); 570 this.minDate.setHours(0); 571 if (isNaN(this.maxDate)) { 572 this.maxDate = new Date(); 573 } 574 validate = validate && (this.fullDate <= this.maxDate); 575 } 576 if (this.maxDate && this.minDate) { 577 this.validateDataErrorText = 'Please enter a valid date between %s and %s'; 578 } else if (this.maxDate) { 579 this.validateDataErrorText = 'Please enter a valid date less than or equal to %s'; 580 } else if (this.minDate) { 581 this.validateDataErrorText = 'Please enter a valid date equal to or greater than %s'; 582 } else { 583 this.validateDataErrorText = ''; 584 } 585 } 586 return validate; 587 }, 588 validateDataErrorText: 'Date should be between %s and %s', 589 errorTextModifier: function(text) { 590 if (this.minDate) { 591 text = text.sub('%s', this.dateFormat(this.minDate)); 592 } 593 if (this.maxDate) { 594 text = text.sub('%s', this.dateFormat(this.maxDate)); 595 } 596 return text; 597 }, 598 dateFormat: function(date) { 599 return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear(); 600 } 601}); 602 603Varien.FileElement = Class.create(); 604Varien.FileElement.prototype = { 605 initialize: function (id) { 606 this.fileElement = $(id); 607 this.hiddenElement = $(id + '_value'); 608 609 this.fileElement.observe('change', this.selectFile.bind(this)); 610 }, 611 selectFile: function(event) { 612 this.hiddenElement.value = this.fileElement.getValue(); 613 } 614}; 615 616Validation.addAllThese([ 617 ['validate-custom', ' ', function(v,elm) { 618 return elm.validate(); 619 }] 620]); 621 622function truncateOptions() { 623 $$('.truncated').each(function(element){ 624 Event.observe(element, 'mouseover', function(){ 625 if (element.down('div.truncated_full_value')) { 626 element.down('div.truncated_full_value').addClassName('show'); 627 } 628 }); 629 Event.observe(element, 'mouseout', function(){ 630 if (element.down('div.truncated_full_value')) { 631 element.down('div.truncated_full_value').removeClassName('show'); 632 } 633 }); 634 635 }); 636} 637Event.observe(window, 'load', function(){ 638 truncateOptions(); 639}); 640 641Element.addMethods({ 642 getInnerText: function(element) 643 { 644 element = $(element); 645 if(element.innerText && !Prototype.Browser.Opera) { 646 return element.innerText; 647 } 648 return element.innerHTML.stripScripts().unescapeHTML().replace(/[\n\r\s]+/g, ' ').strip(); 649 } 650}); 651 652/* 653if (!("console" in window) || !("firebug" in console)) 654{ 655 var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", 656 "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; 657 658 window.console = {}; 659 for (var i = 0; i < names.length; ++i) 660 window.console[names[i]] = function() {} 661} 662*/ 663 664/** 665 * Executes event handler on the element. Works with event handlers attached by Prototype, 666 * in a browser-agnostic fashion. 667 * @param element The element object 668 * @param event Event name, like 'change' 669 * 670 * @example fireEvent($('my-input', 'click')); 671 */ 672function fireEvent(element, event) { 673 if (document.createEvent) { 674 // dispatch for all browsers except IE before version 9 675 var evt = document.createEvent("HTMLEvents"); 676 evt.initEvent(event, true, true ); // event type, bubbling, cancelable 677 return element.dispatchEvent(evt); 678 } else { 679 // dispatch for IE before version 9 680 var evt = document.createEventObject(); 681 return element.fireEvent('on' + event, evt); 682 } 683} 684 685/** 686 * Returns more accurate results of floating-point modulo division 687 * E.g.: 688 * 0.6 % 0.2 = 0.19999999999999996 689 * modulo(0.6, 0.2) = 0 690 * 691 * @param dividend 692 * @param divisor 693 */ 694function modulo(dividend, divisor) 695{ 696 var epsilon = divisor / 10000; 697 var remainder = dividend % divisor; 698 699 if (Math.abs(remainder - divisor) < epsilon || Math.abs(remainder) < epsilon) { 700 remainder = 0; 701 } 702 703 return remainder; 704} 705 706/** 707 * createContextualFragment is not supported in IE9. Adding its support. 708 */ 709if ((typeof Range != "undefined") && !Range.prototype.createContextualFragment) 710{ 711 Range.prototype.createContextualFragment = function(html) 712 { 713 var frag = document.createDocumentFragment(), 714 div = document.createElement("div"); 715 frag.appendChild(div); 716 div.outerHTML = html; 717 return frag; 718 }; 719} 720 721/** 722 * Create form element. Set parameters into it and send 723 * 724 * @param url 725 * @param parametersArray 726 * @param method 727 */ 728Varien.formCreator = Class.create(); 729Varien.formCreator.prototype = { 730 initialize : function(url, parametersArray, method) { 731 this.url = url; 732 this.parametersArray = JSON.parse(parametersArray); 733 this.method = method; 734 this.form = ''; 735 736 this.createForm(); 737 this.setFormData(); 738 }, 739 createForm : function() { 740 this.form = new Element('form', { 'method': this.method, action: this.url }); 741 }, 742 setFormData : function () { 743 for (var key in this.parametersArray) { 744 Element.insert( 745 this.form, 746 new Element('input', { name: key, value: this.parametersArray[key], type: 'hidden' }) 747 ); 748 } 749 } 750}; 751 752function customFormSubmit(url, parametersArray, method) { 753 var createdForm = new Varien.formCreator(url, parametersArray, method); 754 Element.insert($$('body')[0], createdForm.form); 755 createdForm.form.submit(); 756} 757 758function customFormSubmitToParent(url, parametersArray, method) { 759 new Ajax.Request(url, { 760 method: method, 761 parameters: JSON.parse(parametersArray), 762 onSuccess: function (response) { 763 var node = document.createElement('div'); 764 node.innerHTML = response.responseText; 765 var responseMessage = node.getElementsByClassName('messages')[0]; 766 var pageTitle = window.document.body.getElementsByClassName('page-title')[0]; 767 pageTitle.insertAdjacentHTML('afterend', responseMessage.outerHTML);
768 window.opener.focus(); 769 window.opener.location.href = response.transport.responseURL; 770 } 771 }); 772}
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.