2
var DeviceTable = function() { this.initialize.apply(this, arguments) };
3
DeviceTable.prototype = {
5
initialize: function(options) {
6
this.parent = options.parent;
8
document.body.addEventListener("click", this.onClose.bind(this), true);
10
var rows = this.parent.querySelectorAll('tbody tr');
11
for (var r = 0; r < rows.length; r++) {
12
rows[r].addEventListener("click", this.onShow.bind(this, rows[r]), true);
18
onClose: function(e) {
22
while (el.parentNode) {
23
if (el == this.parent) {
31
if (!child) this.close();
34
onShow: function(row) {
38
if (window.XMLHttpRequest) {
39
httpRequest = new XMLHttpRequest();
40
} else if (window.ActiveXObject) {
41
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
44
httpRequest.open('POST','/api/loadLabDevice', true);
45
httpRequest.onreadystatechange = process.bind(this);
46
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
47
httpRequest.send('id=' + encodeURIComponent(row.getAttribute('data-id')));
50
if (httpRequest.readyState == 4 && httpRequest.responseText != '') {
51
var data = JSON.parse(httpRequest.responseText);
54
[ 'ie5', 'ie6', 'IE 5'],
55
[ 'ie6', 'ie6', 'IE 6'],
56
[ 'ie7', 'ie7', 'IE 7'],
57
[ 'ie8', 'ie7', 'IE 8'],
58
[ 'ie9', 'ie9', 'IE 9'],
59
[ 'ie10', 'ie10', 'IE 10'],
60
[ 'ie11', 'ie10', 'IE 11'],
61
[ 'ie10_metro', 'ie-metro', 'IE 10'],
62
[ 'ie11_metro', 'ie-metro', 'IE 11'],
63
[ 'wp7', 'ie-metro', 'IE 9'],
64
[ 'wp8', 'ie-metro', 'IE 10'],
65
[ 'safari_ios_6', 'safari-ios-6', 'Safari'],
66
[ 'safari_ios_7', 'safari-ios-7', 'Safari'],
67
[ 'xpress', 'nokia', 'Xpress'],
68
[ 'nokia', 'nokia', 'Nokia'],
69
[ 'maemo', 'nokia', 'Nokia'],
70
[ 'meego', 'nokia', 'Nokia'],
71
[ 'webos', 'webos', 'webOS'],
72
[ 'android', 'android', 'Android'],
73
[ 'bb', 'bb', 'Browser'],
74
[ 'bb10', 'bb10', 'Browser'],
75
[ 'tabletos', 'tabletos', 'Browser'],
77
[ 'chrome', 'chrome', 'Chrome'],
78
[ 'coast', 'coast', 'Coast'],
79
[ 'diigo', 'diigo', 'Diigo'],
80
[ 'dolphin', 'dolphin', 'Dolphin'],
81
[ 'firefox', 'firefox', 'Firefox'],
82
[ 'ilunascape', 'ilunascape', 'iLunascape'],
83
[ 'maxthon', 'maxthon', 'Maxthon'],
84
[ 'mercury', 'mercury', 'Mercury'],
85
[ 'puffin', 'puffin', 'Puffin'],
86
[ 'silk', 'silk', 'Silk'],
87
[ 'sleipnir', 'sleipnir', 'Sleipnir'],
88
[ 'one', 'one', 'One'],
89
[ 'opera', 'opera', 'Opera'],
91
[ 'uc-iphone', 'uc-iphone', 'UC Web'],
92
[ 'uc-old', 'uc-old', 'UC Web'],
95
var browsers = "<ul class='browsers'>";
97
if (data.defaultBrowser || data.defaultFingerprint) {
98
browsers += "<li class='default'>";
101
if (data.defaultBrowser) {
102
for (var b = 0; b < bb.length; b++) {
103
if (data.defaultBrowser == bb[b][0]) {
105
browsers += "<img src='/images/browsers/" + bb[b][1] +".png'><span>" + bb[b][2] + "</span>";
111
browsers += "<span class='stock'>Default browser</span>";
114
if (data.defaultFingerprint) {
115
browsers += "<div class='score'><a href='" + document.location.protocol + "//html5te.st/" + data.defaultFingerprint + "'>" + data.defaultResults.score + "</a></div>";
122
for (var b = 0; b < bb.length; b++) {
123
if (data.otherBrowsers[bb[b][0]]) browsers += "<li><img src='/images/browsers/" + bb[b][1] +".png'><span>" + bb[b][2] + "</span></li>";
126
if (data.hasInspect) browsers += "<li><img src='/images/browsers/inspect.png'><span>Inspect</span></li>";
129
this.popup = document.createElement('div');
130
this.popup.className = 'popupPanel pointsLeft deviceInfo';
131
this.popup.innerHTML =
132
"<h2>" + data.deviceManufacturer + " " + data.deviceModel + ( data.url ? "<a class='external' href='" + data.url + "'></a>" : "") + "</h2>" +
133
"<div class='image'" + (data.image ? " style='background-image: url(/images/devices/" + data.image + ");'" : "") + "></div>" +
134
"<div class='information'>" +
136
"<tr><th>Type</th><td>" + data.type + (data.deviceSize ? ", " + data.deviceSize + " inch": "") + "</td></tr>" +
137
"<tr><th>Display</th><td>" + (data.deviceWidth && data.deviceHeight ? data.deviceWidth + " x " + data.deviceHeight + " pixels" : "") + (data.devicePPI ? ", " + data.devicePPI + " ppi" : "") + "</td></tr>" +
138
"<tr><th>OS</th><td>" + (data.osName ? data.osName + (data.osVersion ? " " + data.osVersion : "") : "-") + "</td></tr>" +
139
"<tr><th>Wi-Fi</th><td>" + (data.hasWifi ? "<span class='check'>✔</span> Yes" : "<span class='ballot'>✘</span> No") + "</td></tr>" +
140
"<tr><th>Cellular</th><td>" + (data.simSize ? "<span class='check'>✔</span> " + data.simSize + " sim" + (data.simLocked ? ', locked' : ', unlocked') : "<span class='ballot'>✘</span> No") + "</td></tr>" +
144
(data.comment ? "<div class='comment'>" + data.comment + "</div>" : "" ) +
147
row.cells[0].appendChild(this.popup);
154
this.popup.parentNode.removeChild(this.popup);
162
var SearchField = function() { this.initialize.apply(this, arguments) };
163
SearchField.prototype = {
165
initialize: function(options) {
166
this.parent = options.parent;
168
value: options.value || null,
169
onQuery: options.onQuery || null,
170
onSubmit: options.onSubmit || null
173
this.interval = null;
175
this.container = document.createElement('div');
176
this.container.className = 'search';
177
this.parent.appendChild(this.container);
179
this.container.innerHTML =
180
"<input type='text' placeholder='Search...' value='" + (this.options.value || "") + "'>" +
181
"<button>×</button>";
183
this.container.addEventListener("click", this.onClick.bind(this), false);
185
this.container.firstChild.addEventListener("keyup", this.onUpdate.bind(this), true);
186
this.container.firstChild.addEventListener("change", this.onUpdate.bind(this), true);
187
this.container.firstChild.nextSibling.addEventListener("click", this.onClear.bind(this), true);
190
onUpdate: function(e) {
192
window.clearTimeout(this.interval);
195
this.interval = window.setTimeout(this.onQuery.bind(this), 250);
197
if (e.keyCode == 13) {
198
if (this.options.onSubmit) {
199
this.options.onSubmit(this.container.firstChild.value);
204
onClick: function(e) {
208
onClear: function(e) {
213
if (this.options.onQuery) {
214
this.options.onQuery('');
217
if (this.options.onSubmit) {
218
this.options.onSubmit('');
222
onQuery: function() {
223
var value = this.container.firstChild.value;
224
if (value.length < 3) return;
226
if (this.options.onQuery) {
227
this.options.onQuery(value);
232
this.container.firstChild.value = '';
236
var ToggleSwitch = function() { this.initialize.apply(this, arguments) };
237
ToggleSwitch.prototype = {
239
initialize: function(options) {
240
this.parent = options.parent;
242
inactive: options.inactive || '',
243
active: options.active || '',
244
onChange: options.onChange || null
249
this.container = document.createElement('div');
250
this.container.className = 'toggle';
251
this.parent.appendChild(this.container);
253
this.container.innerHTML =
254
"<div class='background'></div>" +
255
"<div class='part first'>" + this.options.inactive + "</div>" +
256
"<div class='part second'>" + this.options.active + "</div>";
259
this.container.addEventListener("click", this.onToggle.bind(this), true);
262
onToggle: function() {
263
this.active = ! this.active;
266
this.container.className += ' selected';
268
this.container.className = this.container.className.replace(' selected', '');
271
if (this.options.onChange) {
272
this.options.onChange(this.active);
276
activate: function() {
279
this.container.className += ' selected';
283
deactivate: function() {
286
this.container.className = this.container.className.replace(' selected', '');
292
var FeatureTable = function() { this.initialize.apply(this, arguments) };
293
FeatureTable.prototype = {
295
initialize: function(options) {
296
this.parent = options.parent;
297
this.tests = options.tests;
299
title: options.title || '',
300
browsers: options.browsers || [],
301
columns: options.columns || 2,
302
distribute: options.distribute || false,
303
header: options.header || false,
304
links: options.links || false,
305
grading: options.grading || false,
306
features: options.features || false,
307
explainations: options.explainations || false,
310
onChange: options.onChange || false
318
for (var i = 0; i < this.options.columns; i++) {
322
this.createCategories(this.parent, this.tests)
323
this.results = document.createElement('div');
324
this.parent.appendChild(this.results);
326
this.filter(options.filter || '');
329
filter: function(filter, force) {
332
if (!force && this.options.filter == filter) {
336
this.options.filter = filter;
339
this.results.innerHTML = '';
340
for (var i = 0; i < this.tests.length; i++) {
341
this.createSections(this.results, this.tests[i].items);
352
function retrieveItems(items, level) {
353
for (var i = 0; i < items.length; i++) {
354
if (typeof items[i] == 'object') {
355
name[level] = items[i].name;
356
status[level] = items[i].status || null;
358
if (!filterItem(items[i], level)) {
359
if (items[i].items) {
360
retrieveItems(items[i].items, level + 1);
367
function addItems(items, level) {
368
for (var i = 0; i < items.length; i++) {
369
if (typeof items[i] == 'object') {
370
name[level] = items[i].name;
371
status[level] = items[i].status || null;
375
for (var l = level; l >= 0; l--) {
387
if (items[i].value) r.value = items[i].value;
388
if (items[i].url) r.url = items[i].url;
389
if (items[i].urls) r.urls = items[i].urls;
390
if (items[i].items) r.items = items[i].items;
394
else if (items[i].items) {
395
addItems(items[i].items, level + 1);
403
function filterItem(item, level) {
404
name[level] = item.name;
405
status[level] = item.status || null;
408
if (filter == ':diff')
409
selected = level > 1 ? that.diff[item.key] : false;
411
selected = item.name.toLowerCase().indexOf(filter) != -1;
417
for (var l = level; l >= 0; l--) {
426
name: name.slice(2, level + 1).join(' ▸ '),
429
if (item.value) r.value = item.value;
430
if (item.url) r.url = item.url;
431
if (item.urls) r.urls = item.urls;
432
if (item.items) r.items = item.items;
434
return result.push(r);
436
else if (item.items) {
437
return addItems(item.items, level + 1);
443
retrieveItems(this.tests, 0);
445
this.results.innerHTML = '';
446
this.createSections(this.results, [{ name: filter == ':diff' ? 'Difference' : filter, items: result }]);
451
loadColumn: function(column, browser) {
454
if (typeof browser == 'object') {
455
id = browser.platform + (browser.version ? '-' + browser.version : '');
461
if (window.XMLHttpRequest) {
462
httpRequest = new XMLHttpRequest();
463
} else if (window.ActiveXObject) {
464
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
467
httpRequest.open('POST','/api/loadBrowser', true);
468
httpRequest.onreadystatechange = process;
469
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
470
httpRequest.send('id=' + encodeURIComponent(id));
473
if (httpRequest.readyState == 4 && httpRequest.responseText != '') {
474
var data = JSON.parse(httpRequest.responseText);
476
var f = that.options.filter;
478
that.updateColumn(column, data);
484
calculateColumn: function(column) {
489
function process(r) {
490
var c = new Calculate(r, tests);
492
that.updateColumn(column, {
494
nickname: 'My browser',
502
clearColumn: function(column) {
503
this.data[column] = null;
506
if (this.options.onChange) {
508
for (var i = 0; i < this.options.columns; i++) {
511
ids.push(this.data[i].id);
512
else if (this.data[i].version)
513
ids.push(this.data[i].platform + '-' + this.data[i].version);
515
ids.push(this.data[i].platform);
519
this.options.onChange(ids);
522
var row = document.getElementById('row-header');
523
var cell = row.childNodes[column + 1];
524
cell.className = 'empty';
525
cell.firstChild.firstChild.innerHTML = '';
526
cell.firstChild.childNodes[1].selectedIndex = 0;
528
for (var c = 0; c < this.tests.length; c++)
529
for (var i = 0; i < this.tests[c].items.length; i++) {
530
var test = this.tests[c].items[i];
532
if (typeof test != 'string') {
533
if (typeof test.items != 'undefined') {
534
var row = document.getElementById('head-' + test.id);
536
var cell = row.childNodes[column + 1];
540
this.clearItems(column, test.id, test.items);
545
this.filter(this.options.filter, true);
548
clearItems: function(column, id, tests) {
549
for (var i = 0; i < tests.length; i++) {
550
if (typeof tests[i] != 'string') {
551
var key = tests[i].key;
553
var row = document.getElementById('row-' + key);
555
var cell = row.childNodes[column + 1];
560
if (typeof tests[i].items != 'undefined') {
561
this.clearItems(column, key, tests[i].items);
568
for (var c = 0; c < this.options.columns; c++) {
570
if (match = (new RegExp(key + '=(-?[0-9]+)')).exec(this.data[c].results)) {
571
var result = parseInt(match[1], 10);
577
if (result != base) {
586
this.diff[key] = diff;
592
for (var i = 0; i < this.options.columns; i++) {
594
this.updateColumn(i, this.data[i]);
599
updateColumn: function(column, data) {
600
this.data[column] = data;
603
if (this.options.onChange) {
607
for (var i = 0; i < this.options.columns; i++) {
610
ids.push(this.data[i].id);
611
else if (this.data[i].version)
612
ids.push(this.data[i].platform + '-' + this.data[i].version);
614
ids.push(this.data[i].platform);
618
this.options.onChange(ids);
621
var row = document.getElementById('row-header');
622
var cell = row.childNodes[column + 1];
624
cell.firstChild.firstChild.innerHTML = '<span class="nickname">' + data.nickname + '</span><span class="score">' + data.score + '</span>';
626
for (var c = 0; c < this.tests.length; c++)
627
for (var i = 0; i < this.tests[c].items.length; i++) {
628
var test = this.tests[c].items[i];
630
if (typeof test != 'string') {
631
if (typeof test != 'undefined') {
635
if (match = (new RegExp(test.id + "=([0-9]+)(?:\\/([0-9]+))?(?:\\+([0-9]+))?")).exec(data.points)) {
637
if (match[2]) maximum = match[2];
640
var row = document.getElementById('head-' + test.id);
642
var cell = row.childNodes[column + 1];
644
var content = "<div><div class='grade'>";
646
if (this.options.grading) {
648
var percent = parseInt(points / maximum * 100, 10);
650
case percent == 0: grade = 'none'; break;
651
case percent <= 30: grade = 'badly'; break;
652
case percent <= 60: grade = 'reasonable'; break;
653
case percent <= 95: grade = 'good'; break;
654
default: grade = 'great'; break;
657
if (points == maximum)
658
content += "<span class='" + grade + "'>" + points + "</span>";
660
content += "<span class='" + grade + "'>" + points + "/" + maximum + "</span>";
662
content += "<span>" + points + "</span>";
665
content += "</div></div>";
666
cell.innerHTML = content;
669
this.updateItems(column, data, test.items);
675
updateItems: function(column, data, tests) {
676
var count = [ 0, 0 ];
678
for (var i = 0; i < tests.length; i++) {
679
if (typeof tests[i] != 'string') {
680
var key = tests[i].key;
682
var row = document.getElementById('row-' + key);
684
var cell = row.childNodes[column + 1];
685
var classes = [ 'used' ];
687
if (typeof tests[i].items != 'undefined') {
688
var results = this.updateItems(column, data, tests[i].items);
690
if (results[0] == results[1]) {
691
cell.innerHTML = '<div>' + 'Yes' + ' <span class="check">✔</span></div>';
693
} else if (results[1] == 0) {
694
cell.innerHTML = '<div>' + 'No' + ' <span class="ballot">✘</span></div>';
697
cell.innerHTML = '<div><span class="partially">' + 'Partial' + '</span> <span class="partial">○</span></div>';
702
if (match = (new RegExp(key + '=(-?[0-9]+)')).exec(data.results)) {
703
var result = parseInt(match[1], 10);
707
case !! (result & BUGGY): cell.innerHTML = '<div>Buggy <span class="buggy"></span></div>'; break;
708
case !! (result & OLD): cell.innerHTML = '<div>Partial <span class="partial">○</span></div>'; count[1]++; break;
709
case !! (result & PREFIX): cell.innerHTML = '<div>Prefixed <span class="check">✔</span></div>'; classes.push('yes'); count[1]++; break;
710
case !! (result & EXPERIMENTAL): cell.innerHTML = '<div>Prefixed <span class="check">✔</span></div>'; classes.push('yes'); count[1]++; break;
711
default: cell.innerHTML = '<div>Yes <span class="check">✔</span></div>'; classes.push('yes'); count[1]++; break;
716
case !! (result & UNKNOWN): cell.innerHTML = '<div>Unknown <span class="buggy">?</span></div>'; break;
717
case !! (result & BLOCKED): cell.innerHTML = '<div>Broken <span class="buggy">!</span></div>'; classes.push('no'); break;
718
case !! (result & DISABLED): cell.innerHTML = '<div>Disabled <span class="ballot">✘</span></div>'; classes.push('no'); break;
719
default: cell.innerHTML = '<div>No <span class="ballot">✘</span></div>'; classes.push('no'); break;
723
cell.innerHTML = '<div><span class="partially">Unknown</span> <span class="partial">?</span></div>';
727
cell.className = classes.join(' ');
733
for (var c = 0; c < this.options.columns; c++) {
735
if (match = (new RegExp(key + '=(-?[0-9]+)')).exec(this.data[c].results)) {
736
var result = parseInt(match[1], 10);
742
if (result != base) {
751
this.diff[key] = diff;
753
if (typeof tests[i].items != 'undefined') {
754
var results = this.updateItems(column, data, tests[i].items);
765
askForUniqueId: function(c) {
766
var id = prompt('Enter the unique id of the results you want to see')
768
this.loadColumn(c, 'custom:' + id);
772
createCategories: function(parent, tests) {
773
var table = document.createElement('table');
774
table.id = 'table-header';
775
parent.appendChild(table);
777
var tbody = document.createElement('tbody');
778
table.appendChild(tbody);
780
var tr = document.createElement('tr');
781
tr.id = 'row-header';
782
tbody.appendChild(tr);
784
var th = document.createElement('th');
785
th.innerHTML = this.options.title;
788
for (var c = 0; c < this.options.columns; c++) {
791
var td = document.createElement('td');
792
td.className = 'empty';
795
var wrapper = document.createElement('div');
796
td.appendChild(wrapper);
798
var div = document.createElement('div');
799
div.className = 'name';
800
wrapper.appendChild(div);
802
var menu = document.createElement('div');
803
menu.className = 'popup popupPanel pointsRight hasSearch';
804
wrapper.appendChild(menu);
806
var header = document.createElement('div');
807
header.className = 'toolbar';
808
menu.appendChild(header);
810
var scroll = document.createElement('div');
811
scroll.className = 'scroll';
812
menu.appendChild(scroll);
814
var list = document.createElement('ul');
815
scroll.appendChild(list);
818
(function(c, menu, list, header) {
819
var search = new SearchField({
821
onQuery: function(query) {
822
build(list, that.options.browsers, query != "", query);
827
document.body.addEventListener('click', function(e) {
828
menu.className = menu.className.replace(' visible', '');
831
div.addEventListener('click', function(e) {
832
if (that.data[c] == null) {
834
that.askForUniqueId(c);
837
menu.className += ' visible';
845
list.addEventListener('click', function(e) {
849
var target = e.target;
851
while (target.tagName != 'LI' && target.parentNode) {
852
target = target.parentNode;
855
if (target.hasAttribute('data-action')) {
856
var action = target.getAttribute('data-action');
858
if (action == 'more') {
859
build(list, that.options.browsers, true);
863
if (action == 'less') {
864
build(list, that.options.browsers, false);
868
if (action == 'calculate') {
869
that.calculateColumn(c);
872
if (action == 'custom') {
873
window.setTimeout(function() {
874
that.askForUniqueId(c);
878
if (action == 'load') {
879
var id = target.getAttribute('data-id');
880
that.loadColumn(c, that.options.browsers[id]);
886
menu.className = menu.className.replace(' visible', '');
891
})(c, menu, list, header);
894
build(list, this.options.browsers, false);
897
function build(list, browsers, all, filter) {
901
var item = document.createElement('li');
902
item.setAttribute('data-action', 'calculate');
903
item.innerHTML = 'My browser';
904
list.appendChild(item);
906
var item = document.createElement('li');
907
item.setAttribute('data-action', 'custom');
908
item.innerHTML = 'Enter unique id...';
909
list.appendChild(item);
914
for (var i = 0; i < browsers.length; i++) {
915
if (!filter || browsers[i].nickname.toLowerCase().indexOf(filter.toLowerCase()) != -1) {
916
if (all || browsers[i].visible) {
917
if (type != browsers[i].type) {
918
var item = document.createElement('li');
919
item.className = 'indent-0 title';
920
list.appendChild(item);
922
switch(browsers[i].type) {
923
case 'desktop': item.innerHTML = 'Desktop browsers'; break;
924
case 'gaming': item.innerHTML = 'Gaming'; break;
925
case 'mobile': item.innerHTML = 'Mobiles'; break;
926
case 'tablet': item.innerHTML = 'Tablets'; break;
927
case 'television': item.innerHTML = 'Television'; break;
931
var item = document.createElement('li');
932
item.setAttribute('data-action', 'load');
933
item.setAttribute('data-id', i);
934
item.innerHTML = browsers[i].nickname + (browsers[i].details ? ' <em>(' + browsers[i].details + ')</em>' : '');
935
list.appendChild(item);
937
type = browsers[i].type;
944
var item = document.createElement('li');
945
item.className = 'more';
946
item.setAttribute('data-action', 'more');
947
item.innerHTML = 'Show more';
948
list.appendChild(item);
950
var item = document.createElement('li');
951
item.className = 'less';
952
item.setAttribute('data-action', 'less');
953
item.innerHTML = 'Show less';
954
list.appendChild(item);
961
createSections: function(parent, tests) {
962
for (var i = 0; i < tests.length; i++) {
963
if (typeof tests[i] == 'string') {
964
var h2 = document.createElement('h2');
965
h2.innerHTML = tests[i];
966
parent.appendChild(h2);
968
var table = document.createElement('table');
969
if (tests[i].id) table.id = 'table-' + tests[i].id;
970
parent.appendChild(table);
972
var thead = document.createElement('thead');
973
table.appendChild(thead);
975
var tr = document.createElement('tr');
976
if (tests[i].id) tr.id = 'head-' + tests[i].id;
977
thead.appendChild(tr);
979
var th = document.createElement('th');
980
if (tests[i].name) th.innerHTML = tests[i].name;
983
for (var c = 0; c < this.options.columns; c++) {
984
var td = document.createElement('td');
988
if (typeof tests[i].items != 'undefined') {
989
var tbody = document.createElement('tbody');
990
table.appendChild(tbody);
992
var status = typeof tests[i].status != 'undefined' ? tests[i].status : '';
994
this.createItems(tbody, 0, tests[i].items, {
1004
createItems: function(parent, level, tests, data) {
1007
for (var i = 0; i < tests.length; i++) {
1008
var tr = document.createElement('tr');
1009
parent.appendChild(tr);
1011
if (typeof tests[i] == 'string') {
1012
if (this.options.explainations || tests[i].substr(0, 4) != '<em>') {
1013
var th = document.createElement('th');
1014
th.colSpan = this.options.columns + 1;
1015
th.className = 'details';
1018
th.innerHTML = tests[i];
1021
var key = tests[i].key;
1023
var th = document.createElement('th');
1024
th.innerHTML = "<div><span>" + tests[i].name + "</span></div>";
1027
for (var c = 0; c < this.options.columns; c++) {
1028
var td = document.createElement('td');
1032
tr.id = 'row-' + key;
1035
tr.className = 'isChild';
1038
if (typeof tests[i].items != 'undefined') {
1041
if (this.options.links) {
1042
if (typeof tests[i].urls != 'undefined') {
1043
urls = tests[i].urls;
1045
else if (typeof tests[i].url != 'undefined') {
1046
urls = { 'w3c': tests[i].url };
1050
tr.className += 'hasChild';
1052
var children = this.createItems(parent, level + 1, tests[i].items, {
1054
status: typeof tests[i].status != 'undefined' ? tests[i].status : data.status,
1058
this.hideChildren(tr, children);
1060
(function(that, tr, th, children) {
1061
th.onclick = function() {
1062
that.toggleChildren(tr, children);
1064
})(this, tr, th, children);
1069
if (typeof tests[i].value != 'undefined') {
1070
value = tests[i].value;
1073
if (typeof tests[i].urls != 'undefined') {
1074
urls = tests[i].urls;
1076
else if (typeof tests[i].url != 'undefined') {
1077
urls = [ [ 'w3c', tests[i].url ] ];
1080
th.className = 'hasLink';
1082
(function(th, data){
1083
th.onclick = function() {
1084
new FeaturePopup(th, data);
1088
name: tests[i].name,
1090
status: typeof tests[i].status != 'undefined' ? tests[i].status : data.status,
1091
urls: (urls || []).concat(data.urls || [])
1102
toggleChildren: function(element, ids) {
1103
if (element.className.indexOf(' hidden') == -1) {
1104
this.hideChildren(element, ids);
1106
this.showChildren(element, ids);
1110
showChildren: function(element, ids) {
1111
element.className = element.className.replace(' hidden', '');
1113
for (var i = 0; i < ids.length; i++) {
1114
var e = document.getElementById(ids[i]);
1115
e.style.display = '';
1119
hideChildren: function(element, ids) {
1120
element.className = element.className.replace(' hidden', '');
1121
element.className += ' hidden';
1123
for (var i = 0; i < ids.length; i++) {
1124
var e = document.getElementById(ids[i]);
1125
e.style.display = 'none';
1132
var BrowserTable = function() { this.initialize.apply(this, arguments) };
1133
BrowserTable.prototype = {
1135
initialize: function(options) {
1136
this.parent = options.parent;
1137
this.browsers = options.browsers;
1139
title: options.title || '',
1140
tests: options.tests || [],
1141
columns: options.columns || 2,
1142
header: options.header || false,
1143
links: options.links || false,
1144
grading: options.grading || false,
1145
explainations: options.explainations || false,
1148
onChange: options.onChange || false
1152
for (var i = 0; i < this.options.columns; i++) {
1153
this.data[i] = null;
1156
this.createSections(this.parent);
1158
this.filter(options.filter || '');
1161
filter: function(filter) {
1162
if (this.options.filter != filter) {
1163
this.options.filter = filter;
1165
filter = filter.toLowerCase();
1167
for (var i = 0; i < this.browsers.length; i++) {
1168
var row = document.getElementById('row-' + this.browsers[i].uid);
1172
if (filter == ':mostused') {
1173
visible = this.browsers[i].visible;
1177
visible = this.browsers[i].nickname.toLowerCase().indexOf(filter) != -1
1181
row.style.display = visible ? '' : 'none';
1186
loadColumn: function(column, key) {
1188
if (window.XMLHttpRequest) {
1189
httpRequest = new XMLHttpRequest();
1190
} else if (window.ActiveXObject) {
1191
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
1194
httpRequest.open('POST','/api/loadFeature', true);
1195
httpRequest.onreadystatechange = process;
1196
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
1197
httpRequest.send('key=' + encodeURIComponent(key));
1201
function process() {
1202
if (httpRequest.readyState == 4 && httpRequest.responseText != '') {
1203
var data = JSON.parse(httpRequest.responseText);
1204
that.updateColumn(column, data);
1209
clearColumn: function(column) {
1210
this.data[column] = null;
1212
if (this.options.onChange) {
1214
for (var i = 0; i < this.options.columns; i++) {
1216
ids.push(this.data[i].id);
1219
this.options.onChange(ids);
1222
if (this.options.header) {
1223
var row = document.getElementById('row-header');
1224
var cell = row.childNodes[column + 1];
1225
cell.className = 'empty';
1226
cell.firstChild.firstChild.innerHTML = '';
1227
cell.firstChild.childNodes[1].selectedIndex = 0;
1230
for (var i = 0; i < this.browsers.length; i++) {
1231
var row = document.getElementById('row-' + this.browsers[i].uid);
1232
var cell = row.childNodes[column + 1];
1233
cell.className = '';
1234
cell.innerHTML = '';
1238
updateColumn: function(column, data) {
1239
this.data[column] = data;
1241
if (this.options.onChange) {
1243
for (var i = 0; i < this.options.columns; i++) {
1245
keys.push(this.data[i].key);
1248
this.options.onChange(keys);
1251
if (this.options.header) {
1252
var row = document.getElementById('row-header');
1253
var cell = row.childNodes[column + 1];
1254
cell.className = '';
1257
if (item = this.getItemByKey(this.options.tests, data.key)) {
1258
if (data.key.split('.').length > 2) {
1259
if (parent = this.getItemByKey(this.options.tests, data.key.split('.').slice(0,-1).join('.'))) {
1260
cell.firstChild.firstChild.innerHTML = '<span class="feature">' + parent.name + '<hr>' + item.name + '</span>';
1262
cell.firstChild.firstChild.innerHTML = '<span class="feature">' + item.name + '</span>';
1266
cell.firstChild.firstChild.innerHTML = '<span class="feature">' + item.name + '</span>';
1271
for (var i = 0; i < this.browsers.length; i++) {
1272
var row = document.getElementById('row-' + this.browsers[i].uid);
1273
var cell = row.childNodes[column + 1];
1274
var classes = [ 'used' ];
1276
cell.className = 'used';
1278
if (match = (new RegExp(this.browsers[i].platform + '-' + this.browsers[i].version + '=(-?[0-9]+)')).exec(data.supported)) {
1279
var result = parseInt(match[1], 10);
1283
case !! (result & BUGGY): cell.innerHTML = '<div>Buggy <span class="buggy"></span></div>'; break;
1284
case !! (result & OLD): cell.innerHTML = '<div>Partial <span class="partial">○</span></div>'; break;
1285
case !! (result & PREFIX): cell.innerHTML = '<div>Prefixed <span class="check">✔</span></div>'; classes.push('yes'); break;
1286
case !! (result & EXPERIMENTAL): cell.innerHTML = '<div>Prefixed <span class="check">✔</span></div>'; classes.push('yes'); break;
1287
default: cell.innerHTML = '<div>Yes <span class="check">✔</span></div>'; classes.push('yes'); break;
1292
case !! (result & UNKNOWN): cell.innerHTML = '<div>Unknown <span class="partial">?</span></div>'; break;
1293
case !! (result & BLOCKED): cell.innerHTML = '<div>Broken <span class="buggy">!</span></div>'; classes.push('no'); break;
1294
case !! (result & DISABLED): cell.innerHTML = '<div>Disabled <span class="ballot">✘</span></div>'; classes.push('no'); break;
1295
default: cell.innerHTML = '<div>No <span class="ballot">✘</span></div>'; classes.push('no'); break;
1300
cell.innerHTML = '<div><span class="partially">Unknown</span> <span class="partial">?</span></div>';
1302
cell.className = classes.join(' ');
1306
createSections: function(parent) {
1308
var tests = this.getList(this.options.tests);
1310
if (this.options.header) {
1311
var table = document.createElement('table');
1312
table.id = 'table-header';
1313
parent.appendChild(table);
1315
var tbody = document.createElement('tbody');
1316
table.appendChild(tbody);
1318
var tr = document.createElement('tr');
1319
tr.id = 'row-header';
1320
tbody.appendChild(tr);
1322
var th = document.createElement('th');
1323
th.innerHTML = this.options.title;
1326
for (var c = 0; c < this.options.columns; c++) {
1327
var td = document.createElement('td');
1328
td.className = 'empty';
1331
var wrapper = document.createElement('div');
1332
td.appendChild(wrapper);
1334
var div = document.createElement('div');
1335
div.className = 'name';
1336
wrapper.appendChild(div);
1338
var menu = document.createElement('div');
1339
menu.className = 'popup popupPanel pointsRight hasSearch';
1340
wrapper.appendChild(menu);
1342
var header = document.createElement('div');
1343
header.className = 'toolbar';
1344
menu.appendChild(header);
1346
var scroll = document.createElement('div');
1347
scroll.className = 'scroll';
1348
menu.appendChild(scroll);
1350
var list = document.createElement('ul');
1351
scroll.appendChild(list);
1354
(function(c, menu, list, header) {
1355
var search = new SearchField({
1357
onQuery: function(query) {
1358
build(list, tests, query);
1363
document.body.addEventListener('click', function(e) {
1364
menu.className = menu.className.replace(' visible', '');
1367
div.addEventListener('click', function(e) {
1368
if (that.data[c] == null)
1369
menu.className += ' visible';
1371
that.clearColumn(c);
1373
e.stopPropagation();
1376
list.addEventListener('click', function(e) {
1380
var target = e.target;
1382
while (target.tagName != 'LI' && target.parentNode) {
1383
target = target.parentNode;
1386
if (target.hasAttribute('data-action')) {
1387
var action = target.getAttribute('data-action');
1389
if (action == 'load') {
1390
var key = target.getAttribute('data-key');
1391
that.loadColumn(c, key);
1397
menu.className = menu.className.replace(' visible', '');
1400
e.stopPropagation();
1402
})(c, menu, list, header);
1408
function build(list, tests, filter) {
1409
list.innerHTML = '';
1413
for (var i = 0; i < tests.length; i++) {
1414
if (!filter || (typeof tests[i].key != 'undefined' && tests[i].name.toLowerCase().indexOf(filter.toLowerCase()) != -1)) {
1415
var item = document.createElement('li');
1417
if (!filter) item.className = 'indent-' + tests[i].indent;
1419
if (typeof tests[i].key != 'undefined') {
1420
item.setAttribute('data-action', 'load');
1421
item.setAttribute('data-key', tests[i].key);
1423
item.className += ' title';
1426
item.innerHTML = tests[i].name;
1427
list.appendChild(item);
1434
var table = document.createElement('table');
1435
parent.appendChild(table);
1437
var tbody = document.createElement('tbody');
1438
table.appendChild(tbody);
1441
for (var i = 0; i < this.browsers.length; i++) {
1442
if (type != this.browsers[i].type) {
1443
var tr = document.createElement('tr');
1444
tbody.appendChild(tr);
1446
var th = document.createElement('th');
1447
th.className = 'details';
1448
th.colSpan = this.options.columns + 1;
1451
switch(this.browsers[i].type) {
1452
case 'desktop': th.innerHTML = '<h3>Desktop browsers</h3>'; break;
1453
case 'gaming': th.innerHTML = '<h3>Gaming</h3>'; break;
1454
case 'mobile': th.innerHTML = '<h3>Mobiles</h3>'; break;
1455
case 'tablet': th.innerHTML = '<h3>Tablets</h3>'; break;
1456
case 'television': th.innerHTML = '<h3>Television</h3>'; break;
1460
var tr = document.createElement('tr');
1461
tr.id = 'row-' + this.browsers[i].uid;
1462
tbody.appendChild(tr);
1464
var th = document.createElement('th');
1465
th.className = 'hasLink';
1466
th.innerHTML = this.browsers[i].nickname + (this.browsers[i].details ? ' <em>(' + this.browsers[i].details + ')</em>' : '');
1469
(function(th, type, data){
1470
th.onclick = function() {
1471
new BrowserPopup(th, type, data);
1474
platform: this.browsers[i].platform,
1475
version: this.browsers[i].version,
1476
id: this.browsers[i].id,
1477
name: this.browsers[i].nickname,
1478
score: this.browsers[i].score,
1482
for (var c = 0; c < this.options.columns; c++) {
1483
var td = document.createElement('td');
1487
type = this.browsers[i].type;
1491
getList: function(items, level) {
1492
if (typeof level == 'undefined') level = 0;
1496
for (var i = 0; i < items.length; i++) {
1497
if (typeof items[i] == 'object') {
1498
if (typeof items[i].items == 'undefined') {
1502
name: items[i].name,
1508
if (typeof items[i].items != 'undefined') {
1511
name: items[i].name,
1516
if (children = this.getList(items[i].items, level + 1)) {
1517
for (var c = 0; c < children.length; c++) {
1518
result.push(children[c]);
1528
getItemByKey: function(items, key, level) {
1529
if (typeof level == 'undefined') level = 0;
1531
for (var i = 0; i < items.length; i++) {
1532
if (typeof items[i] == 'object') {
1533
if (items[i].key == key) return items[i];
1534
if (typeof items[i].items != 'undefined') {
1535
if (result = this.getItemByKey(items[i].items, key, level + 1)) {
1548
var DiffTable = function() { this.initialize.apply(this, arguments) };
1549
DiffTable.prototype = {
1551
initialize: function(options) {
1552
this.parent = options.parent;
1553
this.metadata = options.metadata;
1554
this.data = options.data;
1556
this.createSections(this.parent);
1559
createSections: function(parent) {
1560
var table = document.createElement('table');
1561
parent.appendChild(table);
1563
var tbody = document.createElement('tbody');
1564
table.appendChild(tbody);
1566
for (var i = 0; i < this.data.length; i++) {
1567
if (this.metadata.getItem(this.data[i].id)) {
1568
var tr = document.createElement('tr');
1569
tbody.appendChild(tr);
1571
var th = document.createElement('th');
1572
th.innerHTML = "<a href='/compare/feature/" + this.data[i].id + ".html'>" + this.metadata.getTrail(this.data[i].id, ' ▸ ') + "</a>";
1575
var td = document.createElement('td');
1576
td.innerHTML = "<div>" + this.getStatus(this.data[i].from) + " <span>→</span> " + this.getStatus(this.data[i].to) + "</div>";
1582
getStatus: function(status) {
1584
status = parseInt(status, 10);
1588
case !! (status & BUGGY): html = '<div>Buggy <span class="buggy"></span></div>'; break;
1589
case !! (status & OLD): html = '<div>Partial <span class="partial">○</span></div>'; break;
1590
case !! (status & PREFIX): html = '<div>Prefixed <span class="check">✔</span></div>'; break;
1591
case !! (status & EXPERIMENTAL): html = '<div>Prefixed <span class="check">✔</span></div>'; break;
1592
default: html = '<div>Yes <span class="check">✔</span></div>'; break;
1597
case !! (status & UNKNOWN): html = '<div>Unknown <span class="partial">?</span></div>'; break;
1598
case !! (status & BLOCKED): html = '<div>Not functional <span class="buggy">!</span></div>'; break;
1599
case !! (status & DISABLED): html = '<div>Disabled <span class="ballot">✘</span></div>'; break;
1600
default: html = '<div>No <span class="ballot">✘</span></div>'; break;
1610
var BrowserPopup = function() { this.initialize.apply(this, arguments) };
1611
BrowserPopup.current = null;
1612
BrowserPopup.prototype = {
1613
initialize: function(parent, type, data) {
1614
if (BrowserPopup.current) {
1615
BrowserPopup.current.close();
1618
var browser = data.platform + (data.version ? "-" + data.version : "");
1621
content += "<div class='info'>";
1622
content += "<div class='column left score'><h2>" + data.score + "</h2><span>Points</span></div>";
1623
content += "<div class='column middle'><a href='/results/" + type + "/timeline/" + data.id +".html' class='timeline'><span>Timeline</span></a></div>";
1624
content += "<div class='column right'><a href='/compare/browser/" + browser +".html' class='compare'><span>Compare</span></a></div>";
1625
content += "</div>";
1627
if (typeof data.urls != 'undefined') {
1628
content += "<div class='links'>";
1630
for (var i = 0; i < data.urls.length; i++) {
1633
content += "</div>";
1636
this.panel = document.createElement('div');
1637
this.panel.className = 'linksPanel popupPanel pointsLeft';
1638
this.panel.innerHTML = content;
1639
parent.appendChild(this.panel);
1641
BrowserPopup.current = this;
1645
this.panel.parentNode.removeChild(this.panel);
1646
BrowserPopup.current = null;
1650
document.addEventListener('click', function() { if (BrowserPopup.current) BrowserPopup.current.close() }, true)
1651
document.addEventListener('touchstart', function() { if (BrowserPopup.current) BrowserPopup.current.close() }, true)