1/* ========================================================= 2 * bootstrap-datepicker.js 3 * http://www.eyecon.ro/bootstrap-datepicker 4 * ========================================================= 5 * Copyright 2012 Stefan Petre 6 * Improvements by Andrew Rowls 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 * ========================================================= */ 20 21(function( $ ) { 22 23 var $window = $(window); 24 25 function UTCDate(){ 26 return new Date(Date.UTC.apply(Date, arguments)); 27 } 28 function UTCToday(){ 29 var today = new Date(); 30 return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); 31 } 32 33 34 // Picker object 35 36 var Datepicker = function(element, options) { 37 var that = this; 38 39 this._process_options(options); 40 41 this.element = $(element); 42 this.isInline = false; 43 this.isInput = this.element.is('input'); 44 this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false; 45 this.hasInput = this.component && this.element.find('input').length; 46 if(this.component && this.component.length === 0) 47 this.component = false; 48 49 this.picker = $(DPGlobal.template); 50 this._buildEvents(); 51 this._attachEvents(); 52 53 if(this.isInline) { 54 this.picker.addClass('datepicker-inline').appendTo(this.element); 55 } else { 56 this.picker.addClass('datepicker-dropdown dropdown-menu'); 57 } 58 59 if (this.o.rtl){ 60 this.picker.addClass('datepicker-rtl'); 61 this.picker.find('.prev i, .next i') 62 .toggleClass('icon-arrow-left icon-arrow-right'); 63 } 64 65 66 this.viewMode = this.o.startView; 67 68 if (this.o.calendarWeeks) 69 this.picker.find('tfoot th.today') 70 .attr('colspan', function(i, val){ 71 return parseInt(val) + 1; 72 }); 73 74 this._allow_update = false; 75 76 this.setStartDate(this._o.startDate); 77 this.setEndDate(this._o.endDate); 78 this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); 79 80 this.fillDow(); 81 this.fillMonths(); 82 83 this._allow_update = true; 84 85 this.update(); 86 this.showMode(); 87 88 if(this.isInline) { 89 this.show(); 90 } 91 }; 92 93 Datepicker.prototype = { 94 constructor: Datepicker, 95 96 _process_options: function(opts){ 97 // Store raw options for reference 98 this._o = $.extend({}, this._o, opts); 99 // Processed options 100 var o = this.o = $.extend({}, this._o); 101 102 // Check if "de-DE" style date is available, if not language should 103 // fallback to 2 letter code eg "de" 104 var lang = o.language; 105 if (!dates[lang]) { 106 lang = lang.split('-')[0]; 107 if (!dates[lang]) 108 lang = defaults.language; 109 } 110 o.language = lang; 111 112 switch(o.startView){ 113 case 2: 114 case 'decade': 115 o.startView = 2; 116 break; 117 case 1: 118 case 'year': 119 o.startView = 1; 120 break; 121 default: 122 o.startView = 0; 123 } 124 125 switch (o.minViewMode) { 126 case 1: 127 case 'months': 128 o.minViewMode = 1; 129 break; 130 case 2: 131 case 'years': 132 o.minViewMode = 2; 133 break; 134 default: 135 o.minViewMode = 0; 136 } 137 138 o.startView = Math.max(o.startView, o.minViewMode); 139 140 o.weekStart %= 7; 141 o.weekEnd = ((o.weekStart + 6) % 7); 142 143 var format = DPGlobal.parseFormat(o.format); 144 if (o.startDate !== -Infinity) { 145 if (!!o.startDate) { 146 if (o.startDate instanceof Date) 147 o.startDate = this._local_to_utc(this._zero_time(o.startDate)); 148 else 149 o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); 150 } else { 151 o.startDate = -Infinity; 152 } 153 } 154 if (o.endDate !== Infinity) { 155 if (!!o.endDate) { 156 if (o.endDate instanceof Date) 157 o.endDate = this._local_to_utc(this._zero_time(o.endDate)); 158 else 159 o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); 160 } else { 161 o.endDate = Infinity; 162 } 163 } 164 165 o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; 166 if (!$.isArray(o.daysOfWeekDisabled)) 167 o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); 168 o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) { 169 return parseInt(d, 10); 170 }); 171 172 var plc = String(o.orientation).toLowerCase().split(/\s+/g), 173 _plc = o.orientation.toLowerCase(); 174 plc = $.grep(plc, function(word){ 175 return (/^auto|left|right|top|bottom$/).test(word); 176 }); 177 o.orientation = {x: 'auto', y: 'auto'}; 178 if (!_plc || _plc === 'auto') 179 ; // no action 180 else if (plc.length === 1){ 181 switch(plc[0]){ 182 case 'top': 183 case 'bottom': 184 o.orientation.y = plc[0]; 185 break; 186 case 'left': 187 case 'right': 188 o.orientation.x = plc[0]; 189 break; 190 } 191 } 192 else { 193 _plc = $.grep(plc, function(word){ 194 return (/^left|right$/).test(word); 195 }); 196 o.orientation.x = _plc[0] || 'auto'; 197 198 _plc = $.grep(plc, function(word){ 199 return (/^top|bottom$/).test(word); 200 }); 201 o.orientation.y = _plc[0] || 'auto'; 202 } 203 }, 204 _events: [], 205 _secondaryEvents: [], 206 _applyEvents: function(evs){ 207 for (var i=0, el, ev; i<evs.length; i++){ 208 el = evs[i][0]; 209 ev = evs[i][1]; 210 el.on(ev); 211 } 212 }, 213 _unapplyEvents: function(evs){ 214 for (var i=0, el, ev; i<evs.length; i++){ 215 el = evs[i][0]; 216 ev = evs[i][1]; 217 el.off(ev); 218 } 219 }, 220 _buildEvents: function(){ 221 if (this.isInput) { // single input 222 this._events = [ 223 [this.element, { 224 focus: $.proxy(this.show, this), 225 keyup: $.proxy(this.update, this), 226 keydown: $.proxy(this.keydown, this) 227 }] 228 ]; 229 } 230 else if (this.component && this.hasInput){ // component: input + button 231 this._events = [ 232 // For components that are not readonly, allow keyboard nav 233 [this.element.find('input'), { 234 focus: $.proxy(this.show, this), 235 keyup: $.proxy(this.update, this), 236 keydown: $.proxy(this.keydown, this) 237 }], 238 [this.component, { 239 click: $.proxy(this.show, this) 240 }] 241 ]; 242 } 243 else if (this.element.is('div')) { // inline datepicker 244 this.isInline = true; 245 } 246 else { 247 this._events = [ 248 [this.element, { 249 click: $.proxy(this.show, this) 250 }] 251 ]; 252 } 253 254 this._secondaryEvents = [ 255 [this.picker, { 256 click: $.proxy(this.click, this) 257 }], 258 [$(window), { 259 resize: $.proxy(this.place, this) 260 }], 261 [$(document), { 262 mousedown: $.proxy(function (e) { 263 // Clicked outside the datepicker, hide it 264 if (!( 265 this.element.is(e.target) || 266 this.element.find(e.target).length || 267 this.picker.is(e.target) || 268 this.picker.find(e.target).length 269 )) { 270 this.hide(); 271 } 272 }, this) 273 }] 274 ]; 275 }, 276 _attachEvents: function(){ 277 this._detachEvents(); 278 this._applyEvents(this._events); 279 }, 280 _detachEvents: function(){ 281 this._unapplyEvents(this._events); 282 }, 283 _attachSecondaryEvents: function(){ 284 this._detachSecondaryEvents(); 285 this._applyEvents(this._secondaryEvents); 286 }, 287 _detachSecondaryEvents: function(){ 288 this._unapplyEvents(this._secondaryEvents); 289 }, 290 _trigger: function(event, altdate){ 291 var date = altdate || this.date, 292 local_date = this._utc_to_local(date); 293 294 this.element.trigger({ 295 type: event, 296 date: local_date, 297 format: $.proxy(function(altformat){ 298 var format = altformat || this.o.format; 299 return DPGlobal.formatDate(date, format, this.o.language); 300 }, this) 301 }); 302 }, 303 304 show: function(e) { 305 if (!this.isInline) 306 this.picker.appendTo('body'); 307 this.picker.show(); 308 this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); 309 this.place(); 310 this._attachSecondaryEvents(); 311 if (e) { 312 e.preventDefault(); 313 } 314 this._trigger('show'); 315 }, 316 317 hide: function(e){ 318 if(this.isInline) return; 319 if (!this.picker.is(':visible')) return; 320 this.picker.hide().detach(); 321 this._detachSecondaryEvents(); 322 this.viewMode = this.o.startView; 323 this.showMode(); 324 325 if ( 326 this.o.forceParse && 327 ( 328 this.isInput && this.element.val() || 329 this.hasInput && this.element.find('input').val() 330 ) 331 ) 332 this.setValue(); 333 //this._trigger('hide'); 334 }, 335 336 remove: function() { 337 this.hide(); 338 this._detachEvents(); 339 this._detachSecondaryEvents(); 340 this.picker.remove(); 341 delete this.element.data().datepicker; 342 if (!this.isInput) { 343 delete this.element.data().date; 344 } 345 }, 346 347 _utc_to_local: function(utc){ 348 return new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); 349 }, 350 _local_to_utc: function(local){ 351 return new Date(local.getTime() - (local.getTimezoneOffset()*60000)); 352 }, 353 _zero_time: function(local){ 354 return new Date(local.getFullYear(), local.getMonth(), local.getDate()); 355 }, 356 _zero_utc_time: function(utc){ 357 return new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); 358 }, 359 360 getDate: function() { 361 return this._utc_to_local(this.getUTCDate()); 362 }, 363 364 getUTCDate: function() { 365 return this.date; 366 }, 367 368 setDate: function(d) { 369 this.setUTCDate(this._local_to_utc(d)); 370 }, 371 372 setUTCDate: function(d) { 373 this.date = d; 374 this.setValue(); 375 }, 376 377 setValue: function() { 378 var formatted = this.getFormattedDate(); 379 if (!this.isInput) { 380 if (this.component){ 381 this.element.find('input').val(formatted).change(); 382 } 383 } else { 384 this.element.val(formatted).change(); 385 } 386 }, 387 388 getFormattedDate: function(format) { 389 if (format === undefined) 390 format = this.o.format; 391 return DPGlobal.formatDate(this.date, format, this.o.language); 392 }, 393 394 setStartDate: function(startDate){ 395 this._process_options({startDate: startDate}); 396 this.update(); 397 this.updateNavArrows(); 398 }, 399
400 setEndDate: function(endDate){ 401 this._process_options({endDate: endDate}); 402 this.update(); 403 this.updateNavArrows(); 404 }, 405 406 setDaysOfWeekDisabled: function(daysOfWeekDisabled){ 407 this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); 408 this.update(); 409 this.updateNavArrows(); 410 }, 411 412 place: function(){ 413 if(this.isInline) return; 414 var calendarWidth = this.picker.outerWidth(), 415 calendarHeight = this.picker.outerHeight(), 416 visualPadding = 10, 417 windowWidth = $window.width(), 418 windowHeight = $window.height(), 419 scrollTop = $window.scrollTop(); 420 421 var zIndex = parseInt(this.element.parents().filter(function() { 422 return $(this).css('z-index') != 'auto'; 423 }).first().css('z-index'))+10; 424 var offset = this.component ? this.component.parent().offset() : this.element.offset(); 425 var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); 426 var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); 427 var left = offset.left, 428 top = offset.top; 429 430 this.picker.removeClass( 431 'datepicker-orient-top datepicker-orient-bottom '+ 432 'datepicker-orient-right datepicker-orient-left' 433 ); 434 435 if (this.o.orientation.x !== 'auto') { 436 this.picker.addClass('datepicker-orient-' + this.o.orientation.x); 437 if (this.o.orientation.x === 'right') 438 left -= calendarWidth - width; 439 } 440 // auto x orientation is best-placement: if it crosses a window 441 // edge, fudge it sideways 442 else { 443 // Default to left 444 this.picker.addClass('datepicker-orient-left'); 445 if (offset.left < 0) 446 left -= offset.left - visualPadding; 447 else if (offset.left + calendarWidth > windowWidth) 448 left = windowWidth - calendarWidth - visualPadding; 449 } 450 451 // auto y orientation is best-situation: top or bottom, no fudging, 452 // decision based on which shows more of the calendar 453 var yorient = this.o.orientation.y, 454 top_overflow, bottom_overflow; 455 if (yorient === 'auto') { 456 top_overflow = -scrollTop + offset.top - calendarHeight; 457 bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight); 458 if (Math.max(top_overflow, bottom_overflow) === bottom_overflow) 459 yorient = 'top'; 460 else 461 yorient = 'bottom'; 462 } 463 this.picker.addClass('datepicker-orient-' + yorient); 464 if (yorient === 'top') 465 top += height; 466 else 467 top -= calendarHeight + parseInt(this.picker.css('padding-top')); 468 469 this.picker.css({ 470 top: top, 471 left: left, 472 zIndex: zIndex 473 }); 474 }, 475 476 _allow_update: true, 477 update: function(){ 478 if (!this._allow_update) return; 479 480 var oldDate = new Date(this.date), 481 date, fromArgs = false; 482 if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) { 483 date = arguments[0]; 484 if (date instanceof Date) 485 date = this._local_to_utc(date); 486 fromArgs = true; 487 } else { 488 date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val(); 489 delete this.element.data().date; 490 } 491 492 this.date = DPGlobal.parseDate(date, this.o.format, this.o.language); 493 494 if (fromArgs) { 495 // setting date by clicking 496 this.setValue(); 497 } else if (date) { 498 // setting date by typing 499 if (oldDate.getTime() !== this.date.getTime()) 500 this._trigger('changeDate'); 501 } else { 502 // clearing date 503 this._trigger('clearDate'); 504 } 505 506 if (this.date < this.o.startDate) { 507 this.viewDate = new Date(this.o.startDate); 508 this.date = new Date(this.o.startDate); 509 } else if (this.date > this.o.endDate) { 510 this.viewDate = new Date(this.o.endDate); 511 this.date = new Date(this.o.endDate); 512 } else { 513 this.viewDate = new Date(this.date); 514 this.date = new Date(this.date); 515 } 516 this.fill(); 517 }, 518 519 fillDow: function(){ 520 var dowCnt = this.o.weekStart, 521 html = '<tr>'; 522 if(this.o.calendarWeeks){ 523 var cell = '<th class="cw"> </th>'; 524 html += cell; 525 this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); 526 } 527 while (dowCnt < this.o.weekStart + 7) { 528 html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>'; 529 } 530 html += '</tr>'; 531 this.picker.find('.datepicker-days thead').append(html); 532 }, 533 534 fillMonths: function(){ 535 var html = '', 536 i = 0; 537 while (i < 12) { 538 html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>'; 539 } 540 this.picker.find('.datepicker-months td').html(html); 541 }, 542 543 setRange: function(range){ 544 if (!range || !range.length) 545 delete this.range; 546 else 547 this.range = $.map(range, function(d){ return d.valueOf(); }); 548 this.fill(); 549 }, 550 551 getClassNames: function(date){ 552 var cls = [], 553 year = this.viewDate.getUTCFullYear(),
vendor: 26,439 bytes, lines 554-1381
554 month = this.viewDate.getUTCMonth(), 555 currentDate = this.date.valueOf(), 556 today = new Date(); 557 if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) { 558 cls.push('old'); 559 } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) { 560 cls.push('new'); 561 } 562 // Compare internal UTC date with local today, not UTC today 563 if (this.o.todayHighlight && 564 date.getUTCFullYear() == today.getFullYear() && 565 date.getUTCMonth() == today.getMonth() && 566 date.getUTCDate() == today.getDate()) { 567 cls.push('today'); 568 } 569 if (currentDate && date.valueOf() == currentDate) { 570 cls.push('active'); 571 } 572 if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || 573 $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) { 574 cls.push('disabled'); 575 } 576 if (this.range){ 577 if (date > this.range[0] && date < this.range[this.range.length-1]){ 578 cls.push('range'); 579 } 580 if ($.inArray(date.valueOf(), this.range) != -1){ 581 cls.push('selected'); 582 } 583 } 584 return cls; 585 }, 586 587 fill: function() { 588 var d = new Date(this.viewDate), 589 year = d.getUTCFullYear(), 590 month = d.getUTCMonth(), 591 startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, 592 startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, 593 endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, 594 endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, 595 currentDate = this.date && this.date.valueOf(), 596 tooltip; 597 this.picker.find('.datepicker-days thead th.datepicker-switch') 598 .text(dates[this.o.language].months[month]+' '+year); 599 this.picker.find('tfoot th.today') 600 .text(dates[this.o.language].today) 601 .toggle(this.o.todayBtn !== false); 602 this.picker.find('tfoot th.clear') 603 .text(dates[this.o.language].clear) 604 .toggle(this.o.clearBtn !== false); 605 this.updateNavArrows(); 606 this.fillMonths(); 607 var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), 608 day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); 609 prevMonth.setUTCDate(day); 610 prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); 611 var nextMonth = new Date(prevMonth); 612 nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); 613 nextMonth = nextMonth.valueOf(); 614 var html = []; 615 var clsName; 616 while(prevMonth.valueOf() < nextMonth) { 617 if (prevMonth.getUTCDay() == this.o.weekStart) { 618 html.push('<tr>'); 619 if(this.o.calendarWeeks){ 620 // ISO 8601: First week contains first thursday. 621 // ISO also states week starts on Monday, but we can be more abstract here. 622 var 623 // Start of current week: based on weekstart/current date 624 ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), 625 // Thursday of this week 626 th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), 627 // First Thursday of year, year from thursday 628 yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), 629 // Calendar week: ms between thursdays, div ms per day, div 7 days 630 calWeek = (th - yth) / 864e5 / 7 + 1; 631 html.push('<td class="cw">'+ calWeek +'</td>'); 632 633 } 634 } 635 clsName = this.getClassNames(prevMonth); 636 clsName.push('day'); 637 638 if (this.o.beforeShowDay !== $.noop){ 639 var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); 640 if (before === undefined) 641 before = {}; 642 else if (typeof(before) === 'boolean') 643 before = {enabled: before}; 644 else if (typeof(before) === 'string') 645 before = {classes: before}; 646 if (before.enabled === false) 647 clsName.push('disabled'); 648 if (before.classes) 649 clsName = clsName.concat(before.classes.split(/\s+/)); 650 if (before.tooltip) 651 tooltip = before.tooltip; 652 } 653 654 clsName = $.unique(clsName); 655 html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>'); 656 if (prevMonth.getUTCDay() == this.o.weekEnd) { 657 html.push('</tr>'); 658 } 659 prevMonth.setUTCDate(prevMonth.getUTCDate()+1); 660 } 661 this.picker.find('.datepicker-days tbody').empty().append(html.join('')); 662 var currentYear = this.date && this.date.getUTCFullYear(); 663 664 var months = this.picker.find('.datepicker-months') 665 .find('th:eq(1)') 666 .text(year) 667 .end() 668 .find('span').removeClass('active'); 669 if (currentYear && currentYear == year) { 670 months.eq(this.date.getUTCMonth()).addClass('active'); 671 } 672 if (year < startYear || year > endYear) { 673 months.addClass('disabled'); 674 } 675 if (year == startYear) { 676 months.slice(0, startMonth).addClass('disabled'); 677 } 678 if (year == endYear) { 679 months.slice(endMonth+1).addClass('disabled'); 680 } 681 682 html = ''; 683 year = parseInt(year/10, 10) * 10; 684 var yearCont = this.picker.find('.datepicker-years') 685 .find('th:eq(1)') 686 .text(year + '-' + (year + 9)) 687 .end() 688 .find('td'); 689 year -= 1; 690 for (var i = -1; i < 11; i++) { 691 html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>'; 692 year += 1; 693 } 694 yearCont.html(html); 695 }, 696 697 updateNavArrows: function() { 698 if (!this._allow_update) return; 699 700 var d = new Date(this.viewDate), 701 year = d.getUTCFullYear(), 702 month = d.getUTCMonth(); 703 switch (this.viewMode) { 704 case 0: 705 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) { 706 this.picker.find('.prev').css({visibility: 'hidden'}); 707 } else { 708 this.picker.find('.prev').css({visibility: 'visible'}); 709 } 710 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) { 711 this.picker.find('.next').css({visibility: 'hidden'}); 712 } else { 713 this.picker.find('.next').css({visibility: 'visible'}); 714 } 715 break; 716 case 1: 717 case 2: 718 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) { 719 this.picker.find('.prev').css({visibility: 'hidden'}); 720 } else { 721 this.picker.find('.prev').css({visibility: 'visible'}); 722 } 723 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) { 724 this.picker.find('.next').css({visibility: 'hidden'}); 725 } else { 726 this.picker.find('.next').css({visibility: 'visible'}); 727 } 728 break; 729 } 730 }, 731 732 click: function(e) { 733 e.preventDefault(); 734 var target = $(e.target).closest('span, td, th'); 735 if (target.length == 1) { 736 switch(target[0].nodeName.toLowerCase()) { 737 case 'th': 738 switch(target[0].className) { 739 case 'datepicker-switch': 740 this.showMode(1); 741 break; 742 case 'prev': 743 case 'next': 744 var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); 745 switch(this.viewMode){ 746 case 0: 747 this.viewDate = this.moveMonth(this.viewDate, dir); 748 this._trigger('changeMonth', this.viewDate); 749 break; 750 case 1: 751 case 2: 752 this.viewDate = this.moveYear(this.viewDate, dir); 753 if (this.viewMode === 1) 754 this._trigger('changeYear', this.viewDate); 755 break; 756 } 757 this.fill(); 758 break; 759 case 'today': 760 var date = new Date(); 761 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); 762 763 this.showMode(-2); 764 var which = this.o.todayBtn == 'linked' ? null : 'view'; 765 this._setDate(date, which); 766 break; 767 case 'clear': 768 var element; 769 if (this.isInput) 770 element = this.element; 771 else if (this.component) 772 element = this.element.find('input'); 773 if (element) 774 element.val("").change(); 775 this._trigger('changeDate'); 776 this.update(); 777 if (this.o.autoclose) 778 this.hide(); 779 break; 780 } 781 break; 782 case 'span': 783 if (!target.is('.disabled')) { 784 this.viewDate.setUTCDate(1); 785 if (target.is('.month')) { 786 var day = 1; 787 var month = target.parent().find('span').index(target); 788 var year = this.viewDate.getUTCFullYear(); 789 this.viewDate.setUTCMonth(month); 790 this._trigger('changeMonth', this.viewDate); 791 if (this.o.minViewMode === 1) { 792 this._setDate(UTCDate(year, month, day,0,0,0,0)); 793 } 794 } else { 795 var year = parseInt(target.text(), 10)||0; 796 var day = 1; 797 var month = 0; 798 this.viewDate.setUTCFullYear(year); 799 this._trigger('changeYear', this.viewDate); 800 if (this.o.minViewMode === 2) { 801 this._setDate(UTCDate(year, month, day,0,0,0,0)); 802 } 803 } 804 this.showMode(-1); 805 this.fill(); 806 } 807 break; 808 case 'td': 809 if (target.is('.day') && !target.is('.disabled')){ 810 var day = parseInt(target.text(), 10)||1; 811 var year = this.viewDate.getUTCFullYear(), 812 month = this.viewDate.getUTCMonth(); 813 if (target.is('.old')) { 814 if (month === 0) { 815 month = 11; 816 year -= 1; 817 } else { 818 month -= 1; 819 } 820 } else if (target.is('.new')) { 821 if (month == 11) { 822 month = 0; 823 year += 1; 824 } else { 825 month += 1; 826 } 827 } 828 this._setDate(UTCDate(year, month, day,0,0,0,0)); 829 } 830 break; 831 } 832 } 833 }, 834 835 _setDate: function(date, which){ 836 if (!which || which == 'date') 837 this.date = new Date(date); 838 if (!which || which == 'view') 839 this.viewDate = new Date(date); 840 this.fill(); 841 this.setValue(); 842 this._trigger('changeDate'); 843 var element; 844 if (this.isInput) { 845 element = this.element; 846 } else if (this.component){ 847 element = this.element.find('input'); 848 } 849 if (element) { 850 element.change(); 851 } 852 if (this.o.autoclose && (!which || which == 'date')) { 853 this.hide(); 854 } 855 }, 856 857 moveMonth: function(date, dir){ 858 if (!dir) return date; 859 var new_date = new Date(date.valueOf()), 860 day = new_date.getUTCDate(), 861 month = new_date.getUTCMonth(), 862 mag = Math.abs(dir), 863 new_month, test; 864 dir = dir > 0 ? 1 : -1; 865 if (mag == 1){ 866 test = dir == -1 867 // If going back one month, make sure month is not current month 868 // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) 869 ? function(){ return new_date.getUTCMonth() == month; } 870 // If going forward one month, make sure month is as expected 871 // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) 872 : function(){ return new_date.getUTCMonth() != new_month; }; 873 new_month = month + dir; 874 new_date.setUTCMonth(new_month); 875 // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 876 if (new_month < 0 || new_month > 11) 877 new_month = (new_month + 12) % 12; 878 } else { 879 // For magnitudes >1, move one month at a time... 880 for (var i=0; i<mag; i++) 881 // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... 882 new_date = this.moveMonth(new_date, dir); 883 // ...then reset the day, keeping it in the new month 884 new_month = new_date.getUTCMonth(); 885 new_date.setUTCDate(day); 886 test = function(){ return new_month != new_date.getUTCMonth(); }; 887 } 888 // Common date-resetting loop -- if date is beyond end of month, make it 889 // end of month 890 while (test()){ 891 new_date.setUTCDate(--day); 892 new_date.setUTCMonth(new_month); 893 } 894 return new_date; 895 }, 896 897 moveYear: function(date, dir){ 898 return this.moveMonth(date, dir*12); 899 }, 900 901 dateWithinRange: function(date){ 902 return date >= this.o.startDate && date <= this.o.endDate; 903 }, 904 905 keydown: function(e){ 906 if (this.picker.is(':not(:visible)')){ 907 if (e.keyCode == 27) // allow escape to hide and re-show picker 908 this.show(); 909 return; 910 } 911 var dateChanged = false, 912 dir, day, month, 913 newDate, newViewDate; 914 switch(e.keyCode){ 915 case 27: // escape 916 this.hide(); 917 e.preventDefault(); 918 break; 919 case 37: // left 920 case 39: // right 921 if (!this.o.keyboardNavigation) break; 922 dir = e.keyCode == 37 ? -1 : 1; 923 if (e.ctrlKey){ 924 newDate = this.moveYear(this.date, dir); 925 newViewDate = this.moveYear(this.viewDate, dir); 926 this._trigger('changeYear', this.viewDate); 927 } else if (e.shiftKey){ 928 newDate = this.moveMonth(this.date, dir); 929 newViewDate = this.moveMonth(this.viewDate, dir); 930 this._trigger('changeMonth', this.viewDate); 931 } else { 932 newDate = new Date(this.date); 933 newDate.setUTCDate(this.date.getUTCDate() + dir); 934 newViewDate = new Date(this.viewDate); 935 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); 936 } 937 if (this.dateWithinRange(newDate)){ 938 this.date = newDate; 939 this.viewDate = newViewDate; 940 this.setValue(); 941 this.update(); 942 e.preventDefault(); 943 dateChanged = true; 944 } 945 break; 946 case 38: // up 947 case 40: // down 948 if (!this.o.keyboardNavigation) break; 949 dir = e.keyCode == 38 ? -1 : 1; 950 if (e.ctrlKey){ 951 newDate = this.moveYear(this.date, dir); 952 newViewDate = this.moveYear(this.viewDate, dir); 953 this._trigger('changeYear', this.viewDate); 954 } else if (e.shiftKey){ 955 newDate = this.moveMonth(this.date, dir); 956 newViewDate = this.moveMonth(this.viewDate, dir); 957 this._trigger('changeMonth', this.viewDate); 958 } else { 959 newDate = new Date(this.date); 960 newDate.setUTCDate(this.date.getUTCDate() + dir * 7); 961 newViewDate = new Date(this.viewDate); 962 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); 963 } 964 if (this.dateWithinRange(newDate)){ 965 this.date = newDate; 966 this.viewDate = newViewDate; 967 this.setValue(); 968 this.update(); 969 e.preventDefault(); 970 dateChanged = true; 971 } 972 break; 973 case 13: // enter 974 this.hide(); 975 e.preventDefault(); 976 break; 977 case 9: // tab 978 this.hide(); 979 break; 980 } 981 if (dateChanged){ 982 this._trigger('changeDate'); 983 var element; 984 if (this.isInput) { 985 element = this.element; 986 } else if (this.component){ 987 element = this.element.find('input'); 988 } 989 if (element) { 990 element.change(); 991 } 992 } 993 }, 994 995 showMode: function(dir) { 996 if (dir) { 997 this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); 998 } 999 /* 1000 vitalets: fixing bug of very special conditions: 1001 jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. 1002 Method show() does not set display css correctly and datepicker is not shown. 1003 Changed to .css('display', 'block') solve the problem. 1004 See https://github.com/vitalets/x-editable/issues/37 1005 1006 In jquery 1.7.2+ everything works fine. 1007 */ 1008 //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); 1009 this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); 1010 this.updateNavArrows(); 1011 } 1012 }; 1013 1014 var DateRangePicker = function(element, options){ 1015 this.element = $(element); 1016 this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; }); 1017 delete options.inputs; 1018 1019 $(this.inputs) 1020 .datepicker(options) 1021 .bind('changeDate', $.proxy(this.dateUpdated, this)); 1022 1023 this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); }); 1024 this.updateDates(); 1025 }; 1026 DateRangePicker.prototype = { 1027 updateDates: function(){ 1028 this.dates = $.map(this.pickers, function(i){ return i.date; }); 1029 this.updateRanges(); 1030 }, 1031 updateRanges: function(){ 1032 var range = $.map(this.dates, function(d){ return d.valueOf(); }); 1033 $.each(this.pickers, function(i, p){ 1034 p.setRange(range); 1035 }); 1036 }, 1037 dateUpdated: function(e){ 1038 var dp = $(e.target).data('datepicker'), 1039 new_date = dp.getUTCDate(), 1040 i = $.inArray(e.target, this.inputs), 1041 l = this.inputs.length; 1042 if (i == -1) return; 1043 1044 if (new_date < this.dates[i]){ 1045 // Date being moved earlier/left 1046 while (i>=0 && new_date < this.dates[i]){ 1047 this.pickers[i--].setUTCDate(new_date); 1048 } 1049 } 1050 else if (new_date > this.dates[i]){ 1051 // Date being moved later/right 1052 while (i<l && new_date > this.dates[i]){ 1053 this.pickers[i++].setUTCDate(new_date); 1054 } 1055 } 1056 this.updateDates(); 1057 }, 1058 remove: function(){ 1059 $.map(this.pickers, function(p){ p.remove(); }); 1060 delete this.element.data().datepicker; 1061 } 1062 }; 1063 1064 function opts_from_el(el, prefix){ 1065 // Derive options from element data-attrs 1066 var data = $(el).data(), 1067 out = {}, inkey, 1068 replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'), 1069 prefix = new RegExp('^' + prefix.toLowerCase()); 1070 for (var key in data) 1071 if (prefix.test(key)){ 1072 inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); }); 1073 out[inkey] = data[key]; 1074 } 1075 return out; 1076 } 1077 1078 function opts_from_locale(lang){ 1079 // Derive options from locale plugins 1080 var out = {}; 1081 // Check if "de-DE" style date is available, if not language should 1082 // fallback to 2 letter code eg "de" 1083 if (!dates[lang]) { 1084 lang = lang.split('-')[0] 1085 if (!dates[lang]) 1086 return; 1087 } 1088 var d = dates[lang]; 1089 $.each(locale_opts, function(i,k){ 1090 if (k in d) 1091 out[k] = d[k]; 1092 }); 1093 return out; 1094 } 1095 1096 var old = $.fn.datepicker; 1097 $.fn.datepicker = function ( option ) { 1098 var args = Array.apply(null, arguments); 1099 args.shift(); 1100 var internal_return, 1101 this_return; 1102 this.each(function () { 1103 var $this = $(this), 1104 data = $this.data('datepicker'), 1105 options = typeof option == 'object' && option; 1106 if (!data) { 1107 var elopts = opts_from_el(this, 'date'), 1108 // Preliminary otions 1109 xopts = $.extend({}, defaults, elopts, options), 1110 locopts = opts_from_locale(xopts.language), 1111 // Options priority: js args, data-attrs, locales, defaults 1112 opts = $.extend({}, defaults, locopts, elopts, options); 1113 if ($this.is('.input-daterange') || opts.inputs){ 1114 var ropts = { 1115 inputs: opts.inputs || $this.find('input').toArray() 1116 }; 1117 $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); 1118 } 1119 else{ 1120 $this.data('datepicker', (data = new Datepicker(this, opts))); 1121 } 1122 } 1123 if (typeof option == 'string' && typeof data[option] == 'function') { 1124 internal_return = data[option].apply(data, args); 1125 if (internal_return !== undefined) 1126 return false; 1127 } 1128 }); 1129 if (internal_return !== undefined) 1130 return internal_return; 1131 else 1132 return this; 1133 }; 1134 1135 var defaults = $.fn.datepicker.defaults = { 1136 autoclose: false, 1137 beforeShowDay: $.noop, 1138 calendarWeeks: false, 1139 clearBtn: false, 1140 daysOfWeekDisabled: [], 1141 endDate: Infinity, 1142 forceParse: true, 1143 format: 'mm/dd/yyyy', 1144 keyboardNavigation: true, 1145 language: 'en', 1146 minViewMode: 0, 1147 orientation: "auto", 1148 rtl: false, 1149 startDate: -Infinity, 1150 startView: 0, 1151 todayBtn: false, 1152 todayHighlight: false, 1153 weekStart: 0 1154 }; 1155 var locale_opts = $.fn.datepicker.locale_opts = [ 1156 'format', 1157 'rtl', 1158 'weekStart' 1159 ]; 1160 $.fn.datepicker.Constructor = Datepicker; 1161 var dates = $.fn.datepicker.dates = { 1162 en: { 1163 days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], 1164 daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], 1165 daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], 1166 months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], 1167 monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 1168 today: "Today", 1169 clear: "Clear" 1170 } 1171 }; 1172 1173 var DPGlobal = { 1174 modes: [ 1175 { 1176 clsName: 'days', 1177 navFnc: 'Month', 1178 navStep: 1 1179 }, 1180 { 1181 clsName: 'months', 1182 navFnc: 'FullYear', 1183 navStep: 1 1184 }, 1185 { 1186 clsName: 'years', 1187 navFnc: 'FullYear', 1188 navStep: 10 1189 }], 1190 isLeapYear: function (year) { 1191 return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); 1192 }, 1193 getDaysInMonth: function (year, month) { 1194 return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; 1195 }, 1196 validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, 1197 nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, 1198 parseFormat: function(format){ 1199 // IE treats \0 as a string end in inputs (truncating the value), 1200 // so it's a bad format delimiter, anyway 1201 var separators = format.replace(this.validParts, '\0').split('\0'), 1202 parts = format.match(this.validParts); 1203 if (!separators || !separators.length || !parts || parts.length === 0){ 1204 throw new Error("Invalid date format."); 1205 } 1206 return {separators: separators, parts: parts}; 1207 }, 1208 parseDate: function(date, format, language) { 1209 if (date instanceof Date) return date; 1210 if (typeof format === 'string') 1211 format = DPGlobal.parseFormat(format); 1212 if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) { 1213 var part_re = /([\-+]\d+)([dmwy])/, 1214 parts = date.match(/([\-+]\d+)([dmwy])/g), 1215 part, dir; 1216 date = new Date(); 1217 for (var i=0; i<parts.length; i++) { 1218 part = part_re.exec(parts[i]); 1219 dir = parseInt(part[1]); 1220 switch(part[2]){ 1221 case 'd': 1222 date.setUTCDate(date.getUTCDate() + dir); 1223 break; 1224 case 'm': 1225 date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); 1226 break; 1227 case 'w': 1228 date.setUTCDate(date.getUTCDate() + dir * 7); 1229 break; 1230 case 'y': 1231 date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); 1232 break; 1233 } 1234 } 1235 return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); 1236 } 1237 var parts = date && date.match(this.nonpunctuation) || [], 1238 date = new Date(), 1239 parsed = {}, 1240 setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], 1241 setters_map = { 1242 yyyy: function(d,v){ return d.setUTCFullYear(v); }, 1243 yy: function(d,v){ return d.setUTCFullYear(2000+v); }, 1244 m: function(d,v){ 1245 if (isNaN(d)) 1246 return d; 1247 v -= 1; 1248 while (v<0) v += 12; 1249 v %= 12; 1250 d.setUTCMonth(v); 1251 while (d.getUTCMonth() != v) 1252 d.setUTCDate(d.getUTCDate()-1); 1253 return d; 1254 }, 1255 d: function(d,v){ return d.setUTCDate(v); } 1256 }, 1257 val, filtered, part; 1258 setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; 1259 setters_map['dd'] = setters_map['d']; 1260 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); 1261 var fparts = format.parts.slice(); 1262 // Remove noop parts 1263 if (parts.length != fparts.length) { 1264 fparts = $(fparts).filter(function(i,p){ 1265 return $.inArray(p, setters_order) !== -1; 1266 }).toArray(); 1267 } 1268 // Process remainder 1269 if (parts.length == fparts.length) { 1270 for (var i=0, cnt = fparts.length; i < cnt; i++) { 1271 val = parseInt(parts[i], 10); 1272 part = fparts[i]; 1273 if (isNaN(val)) { 1274 switch(part) { 1275 case 'MM': 1276 filtered = $(dates[language].months).filter(function(){ 1277 var m = this.slice(0, parts[i].length), 1278 p = parts[i].slice(0, m.length); 1279 return m == p; 1280 }); 1281 val = $.inArray(filtered[0], dates[language].months) + 1; 1282 break; 1283 case 'M': 1284 filtered = $(dates[language].monthsShort).filter(function(){ 1285 var m = this.slice(0, parts[i].length), 1286 p = parts[i].slice(0, m.length); 1287 return m == p; 1288 }); 1289 val = $.inArray(filtered[0], dates[language].monthsShort) + 1; 1290 break; 1291 } 1292 } 1293 parsed[part] = val; 1294 } 1295 for (var i=0, _date, s; i<setters_order.length; i++){ 1296 s = setters_order[i]; 1297 if (s in parsed && !isNaN(parsed[s])){ 1298 _date = new Date(date); 1299 setters_map[s](_date, parsed[s]); 1300 if (!isNaN(_date)) 1301 date = _date; 1302 } 1303 } 1304 } 1305 return date; 1306 }, 1307 formatDate: function(date, format, language){ 1308 if (typeof format === 'string') 1309 format = DPGlobal.parseFormat(format); 1310 var val = { 1311 d: date.getUTCDate(), 1312 D: dates[language].daysShort[date.getUTCDay()], 1313 DD: dates[language].days[date.getUTCDay()], 1314 m: date.getUTCMonth() + 1, 1315 M: dates[language].monthsShort[date.getUTCMonth()], 1316 MM: dates[language].months[date.getUTCMonth()], 1317 yy: date.getUTCFullYear().toString().substring(2), 1318 yyyy: date.getUTCFullYear() 1319 }; 1320 val.dd = (val.d < 10 ? '0' : '') + val.d; 1321 val.mm = (val.m < 10 ? '0' : '') + val.m; 1322 var date = [], 1323 seps = $.extend([], format.separators); 1324 for (var i=0, cnt = format.parts.length; i <= cnt; i++) { 1325 if (seps.length) 1326 date.push(seps.shift()); 1327 date.push(val[format.parts[i]]); 1328 } 1329 return date.join(''); 1330 }, 1331 headTemplate: '<thead>'+ 1332 '<tr>'+ 1333 '<th class="prev">«</th>'+ 1334 '<th colspan="5" class="datepicker-switch"></th>'+ 1335 '<th class="next">»</th>'+ 1336 '</tr>'+ 1337 '</thead>', 1338 contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>', 1339 footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>' 1340 }; 1341 DPGlobal.template = '<div class="datepicker">'+ 1342 '<div class="datepicker-days">'+ 1343 '<table class=" table-condensed">'+ 1344 DPGlobal.headTemplate+ 1345 '<tbody></tbody>'+ 1346 DPGlobal.footTemplate+ 1347 '</table>'+ 1348 '</div>'+ 1349 '<div class="datepicker-months">'+ 1350 '<table class="table-condensed">'+ 1351 DPGlobal.headTemplate+ 1352 DPGlobal.contTemplate+ 1353 DPGlobal.footTemplate+ 1354 '</table>'+ 1355 '</div>'+ 1356 '<div class="datepicker-years">'+ 1357 '<table class="table-condensed">'+ 1358 DPGlobal.headTemplate+ 1359 DPGlobal.contTemplate+ 1360 DPGlobal.footTemplate+ 1361 '</table>'+ 1362 '</div>'+ 1363 '</div>'; 1364 1365 $.fn.datepicker.DPGlobal = DPGlobal; 1366 1367 1368 /* DATEPICKER NO CONFLICT 1369 * =================== */ 1370 1371 $.fn.datepicker.noConflict = function(){ 1372 $.fn.datepicker = old; 1373 return this; 1374 }; 1375 1376 1377 /* DATEPICKER DATA-API 1378 * ================== */ 1379 1380 $(document).on( 1381 'focus.datepicker.data-api click.datepicker.data-api',
1382 '[data-provide="datepicker"]', 1383 function(e){ 1384 var $this = $(this); 1385 if ($this.data('datepicker')) return; 1386 e.preventDefault(); 1387 // component click requires us to explicitly show it 1388 $this.datepicker('show'); 1389 } 1390 ); 1391 $(function(){ 1392 $('[data-provide="datepicker-inline"]').datepicker(); 1393 }); 1394 1395}( window.jQuery ));
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.