jsondiffpatch
Version:
Diff & Patch for Javascript objects
637 lines (594 loc) • 20.1 kB
JavaScript
(function demo() {
var getExampleJson = function() {
var data = {
name: 'South America',
summary: 'South America (Spanish: América del Sur, Sudamérica or \n' +
'Suramérica; Portuguese: América do Sul; Quechua and Aymara: \n' +
'Urin Awya Yala; Guarani: Ñembyamérika; Dutch: Zuid-Amerika; \n' +
'French: Amérique du Sud) is a continent situated in the \n' +
'Western Hemisphere, mostly in the Southern Hemisphere, with \n' +
'a relatively small portion in the Northern Hemisphere. \n' +
'The continent is also considered a subcontinent of the \n' +
'Americas.[2][3] It is bordered on the west by the Pacific \n' +
'Ocean and on the north and east by the Atlantic Ocean; \n' +
'North America and the Caribbean Sea lie to the northwest. \n' +
'It includes twelve countries: Argentina, Bolivia, Brazil, \n' +
'Chile, Colombia, Ecuador, Guyana, Paraguay, Peru, Suriname, \n' +
'Uruguay, and Venezuela. The South American nations that \n' +
'border the Caribbean Sea—including Colombia, Venezuela, \n' +
'Guyana, Suriname, as well as French Guiana, which is an \n' +
'overseas region of France—are also known as Caribbean South \n' +
'America. South America has an area of 17,840,000 square \n' +
'kilometers (6,890,000 sq mi). Its population as of 2005 \n' +
'has been estimated at more than 371,090,000. South America \n' +
'ranks fourth in area (after Asia, Africa, and North America) \n' +
'and fifth in population (after Asia, Africa, Europe, and \n' +
'North America). The word America was coined in 1507 by \n' +
'cartographers Martin Waldseemüller and Matthias Ringmann, \n' +
'after Amerigo Vespucci, who was the first European to \n' +
'suggest that the lands newly discovered by Europeans were \n' +
'not India, but a New World unknown to Europeans.',
surface: 17840000,
timezone: [-4, -2],
demographics: {
population: 385742554,
largestCities: ['São Paulo', 'Buenos Aires', 'Rio de Janeiro', 'Lima', 'Bogotá']
},
languages: ['spanish', 'portuguese', 'english', 'dutch',
'french', 'quechua', 'guaraní', 'aimara', 'mapudungun'
],
countries: [{
name: 'Argentina',
capital: 'Buenos Aires',
independence: new Date(1816, 6, 9),
unasur: true
}, {
name: 'Bolivia',
capital: 'La Paz',
independence: new Date(1825, 7, 6),
unasur: true
}, {
name: 'Brazil',
capital: 'Brasilia',
independence: new Date(1822, 8, 7),
unasur: true
}, {
name: 'Chile',
capital: 'Santiago',
independence: new Date(1818, 1, 12),
unasur: true
}, {
name: 'Colombia',
capital: 'Bogotá',
independence: new Date(1810, 6, 20),
unasur: true
}, {
name: 'Ecuador',
capital: 'Quito',
independence: new Date(1809, 7, 10),
unasur: true
}, {
name: 'Guyana',
capital: 'Georgetown',
independence: new Date(1966, 4, 26),
unasur: true
}, {
name: 'Paraguay',
capital: 'Asunción',
independence: new Date(1811, 4, 14),
unasur: true
}, {
name: 'Peru',
capital: 'Lima',
independence: new Date(1821, 6, 28),
unasur: true
}, {
name: 'Suriname',
capital: 'Paramaribo',
independence: new Date(1975, 10, 25),
unasur: true
}, {
name: 'Uruguay',
capital: 'Montevideo',
independence: new Date(1825, 7, 25),
unasur: true
}, {
name: 'Venezuela',
capital: 'Caracas',
independence: new Date(1811, 6, 5),
unasur: true
}]
};
var json = [JSON.stringify(data, null, 2)];
data.summary = data.summary.replace('Brazil', 'Brasil').replace('also known as', 'a.k.a.');
data.languages[2] = 'inglés';
data.countries.pop();
data.countries.pop();
data.countries[0].capital = 'Rawson';
data.countries.push({
name: 'Antártida',
unasur: false
});
// modify and move
data.countries[4].population = 42888594;
data.countries.splice(11, 0, data.countries.splice(4, 1)[0]);
data.countries.splice(2, 0, data.countries.splice(7, 1)[0]);
delete data.surface;
data.spanishName = 'Sudamérica';
data.demographics.population += 2342;
json.push(JSON.stringify(data, null, 2));
return json;
};
/* global jsondiffpatch */
var instance = jsondiffpatch.create({
objectHash: function(obj, index) {
if (typeof obj._id !== 'undefined') {
return obj._id;
}
if (typeof obj.id !== 'undefined') {
return obj.id;
}
if (typeof obj.name !== 'undefined') {
return obj.name;
}
return '$$index:' + index;
}
});
var dom = {
addClass: function(el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className += ' ' + className;
}
},
removeClass: function(el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' +
className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
},
text: function(el, text) {
if (typeof el.textContent !== 'undefined') {
if (typeof text === 'undefined') {
return el.textContent;
}
el.textContent = text;
} else {
if (typeof text === 'undefined') {
return el.innerText;
}
el.innerText = text;
}
},
on: function(el, eventName, handler) {
if (el.addEventListener) {
el.addEventListener(eventName, handler);
} else {
el.attachEvent('on' + eventName, handler);
}
},
ready: function(fn) {
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn);
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'interactive') {
fn();
}
});
}
},
getJson: function(url, callback) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
var data;
try {
data = JSON.parse(this.responseText, jsondiffpatch.dateReviver);
} catch (parseError) {
callback('parse error: ' + parseError);
}
if (this.status >= 200 && this.status < 400) {
callback(null, data);
} else {
callback(new Error('request failed'), data);
}
}
};
request.send();
request = null;
},
runScriptTags: function(el) {
var scripts = el.querySelectorAll('script');
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
/* jshint evil: true */
eval(s.innerHTML);
}
}
};
var trim = function(str) {
return str.replace(/^\s+|\s+$/g, '');
};
var JsonArea = function JsonArea(element) {
this.element = element;
this.container = element.parentNode;
var self = this;
var prettifyButton = this.container.querySelector('.prettyfy');
if (prettifyButton) {
dom.on(prettifyButton, 'click', function() {
self.prettyfy();
});
}
};
JsonArea.prototype.error = function(err) {
var errorElement = this.container.querySelector('.error-message');
if (!err) {
dom.removeClass(this.container, 'json-error');
errorElement.innerHTML = '';
return;
}
errorElement.innerHTML = err + '';
dom.addClass(this.container, 'json-error');
};
JsonArea.prototype.getValue = function() {
if (!this.editor) {
return this.element.value;
}
return this.editor.getValue();
};
JsonArea.prototype.parse = function() {
var txt = trim(this.getValue());
try {
this.error(false);
if (/^\d+(.\d+)?(e[\+\-]?\d+)?$/i.test(txt) ||
/^(true|false)$/.test(txt) ||
/^["].*["]$/.test(txt) ||
/^[\{\[](.|\n)*[\}\]]$/.test(txt)) {
return JSON.parse(txt, jsondiffpatch.dateReviver);
}
return this.getValue();
} catch (err) {
this.error(err);
throw err;
}
};
JsonArea.prototype.setValue = function(value) {
if (!this.editor) {
this.element.value = value;
return;
}
this.editor.setValue(value);
};
JsonArea.prototype.prettyfy = function() {
var value = this.parse();
var prettyJson = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
this.setValue(prettyJson);
};
/* global CodeMirror */
JsonArea.prototype.makeEditor = function(readOnly) {
if (typeof CodeMirror === 'undefined') {
return;
}
this.editor = CodeMirror.fromTextArea(this.element, {
mode: 'javascript',
json: true,
readOnly: readOnly
});
if (!readOnly) {
this.editor.on('change', compare);
}
};
var areas = {
left: new JsonArea(document.getElementById('json-input-left')),
right: new JsonArea(document.getElementById('json-input-right')),
delta: new JsonArea(document.getElementById('json-delta'))
};
var compare = function() {
var left, right, error;
document.getElementById('results').style.display = 'none';
try {
left = areas.left.parse();
} catch (err) {
error = err;
}
try {
right = areas.right.parse();
} catch (err) {
error = err;
}
areas.delta.error(false);
if (error) {
areas.delta.setValue('');
return;
}
var selectedType = getSelectedDeltaType();
var visualdiff = document.getElementById('visualdiff');
var annotateddiff = document.getElementById('annotateddiff');
var jsondifflength = document.getElementById('jsondifflength');
try {
var delta = instance.diff(left, right);
if (typeof delta === 'undefined') {
switch (selectedType) {
case 'visual':
visualdiff.innerHTML = 'no diff';
break;
case 'annotated':
annotateddiff.innerHTML = 'no diff';
break;
case 'json':
areas.delta.setValue('no diff');
jsondifflength.innerHTML = '0';
break;
}
} else {
switch (selectedType) {
case 'visual':
visualdiff.innerHTML = jsondiffpatch.formatters.html.format(delta, left);
if (!document.getElementById('showunchanged').checked) {
jsondiffpatch.formatters.html.hideUnchanged();
}
dom.runScriptTags(visualdiff);
break;
case 'annotated':
annotateddiff.innerHTML = jsondiffpatch.formatters.annotated.format(delta);
break;
case 'json':
areas.delta.setValue(JSON.stringify(delta, null, 2));
jsondifflength.innerHTML = (Math.round(JSON.stringify(delta).length / 102.4) / 10.0) + '';
break;
}
}
} catch (err) {
jsondifflength.innerHTML = '0';
visualdiff.innerHTML = '';
annotateddiff.innerHTML = '';
areas.delta.setValue('');
areas.delta.error(err);
if (typeof console !== 'undefined' && console.error) {
console.error(err);
console.error(err.stack);
}
}
document.getElementById('results').style.display = '';
};
areas.left.makeEditor();
areas.right.makeEditor();
dom.on(areas.left.element, 'change', compare);
dom.on(areas.right.element, 'change', compare);
dom.on(areas.left.element, 'keyup', compare);
dom.on(areas.right.element, 'keyup', compare);
var getSelectedDeltaType = function() {
if (document.getElementById('show-delta-type-visual').checked) {
return 'visual';
}
if (document.getElementById('show-delta-type-annotated').checked) {
return 'annotated';
}
if (document.getElementById('show-delta-type-json').checked) {
return 'json';
}
};
var showSelectedDeltaType = function() {
var type = getSelectedDeltaType();
document.getElementById('delta-panel-visual').style.display =
type === 'visual' ? '' : 'none';
document.getElementById('delta-panel-annotated').style.display =
type === 'annotated' ? '' : 'none';
document.getElementById('delta-panel-json').style.display =
type === 'json' ? '' : 'none';
compare();
};
dom.on(document.getElementById('show-delta-type-visual'), 'click', showSelectedDeltaType);
dom.on(document.getElementById('show-delta-type-annotated'), 'click', showSelectedDeltaType);
dom.on(document.getElementById('show-delta-type-json'), 'click', showSelectedDeltaType);
dom.on(document.getElementById('swap'), 'click', function() {
var leftValue = areas.left.getValue();
areas.left.setValue(areas.right.getValue());
areas.right.setValue(leftValue);
compare();
});
dom.on(document.getElementById('clear'), 'click', function() {
areas.left.setValue('');
areas.right.setValue('');
compare();
});
dom.on(document.getElementById('showunchanged'), 'change', function() {
jsondiffpatch.formatters.html.showUnchanged(document.getElementById('showunchanged').checked, null, 800);
});
dom.ready(function(){
setTimeout(compare);
}, 1);
var load = {};
load.data = function(data) {
data = data || {};
dom.text(document.getElementById('description'), data.description || '');
if (data.url && trim(data.url).substr(0, 10) !== 'javascript') {
document.getElementById('external-link').setAttribute('href', data.url);
document.getElementById('external-link').style.display = '';
} else {
document.getElementById('external-link').style.display = 'none';
}
var leftValue = data.left ? (data.left.content || data.left) : '';
areas.left.setValue(leftValue);
var rightValue = data.right ? (data.right.content || data.right) : '';
areas.right.setValue(rightValue);
dom.text(document.getElementById('json-panel-left').querySelector('h2'), (data.left && data.left.name) || 'left.json');
dom.text(document.getElementById('json-panel-right').querySelector('h2'), (data.right && data.right.name) || 'right.json');
document.getElementById('json-panel-left').querySelector('h2').setAttribute(
'title', (data.left && data.left.fullname) || '');
document.getElementById('json-panel-right').querySelector('h2').setAttribute(
'title', (data.right && data.right.fullname) || '');
if (data.error) {
areas.left.setValue('ERROR LOADING: ' + data.error);
areas.right.setValue('');
}
};
load.gist = function(id) {
dom.getJson('https://api.github.com/gists/' + id, function(error, data) {
if (error) {
var message = error + ((data && data.message) ? data.message : '');
load.data({
error: message
});
return;
}
var filenames = [];
for (var filename in data.files) {
var file = data.files[filename];
if (file.language === 'JSON') {
filenames.push(filename);
}
}
filenames.sort();
var files = [
data.files[filenames[0]],
data.files[filenames[1]]
];
/*jshint camelcase: false */
load.data({
url: data.html_url,
description: data.description,
left: {
name: files[0].filename,
content: files[0].content
},
right: {
name: files[1].filename,
content: files[1].content
}
});
});
};
load.leftright = function(description, leftValue, rightValue) {
try {
description = decodeURIComponent(description || '');
leftValue = decodeURIComponent(leftValue);
rightValue = decodeURIComponent(rightValue);
var urlmatch = /https?:\/\/.*\/([^\/]+\.json)(?:[\?#].*)?/;
var dataLoaded = {
description: description,
left: {},
right: {}
};
var loadIfReady = function() {
if (typeof dataLoaded.left.content !== 'undefined' &&
typeof dataLoaded.right.content !== 'undefined') {
load.data(dataLoaded);
}
};
if (urlmatch.test(leftValue)) {
dataLoaded.left.name = urlmatch.exec(leftValue)[1];
dataLoaded.left.fullname = leftValue;
dom.getJson(leftValue, function(error, data) {
if (error) {
dataLoaded.left.content = error + ((data && data.message) ? data.message : '');
} else {
dataLoaded.left.content = JSON.stringify(data, null, 2);
}
loadIfReady();
});
} else {
dataLoaded.left.content = leftValue;
}
if (urlmatch.test(rightValue)) {
dataLoaded.right.name = urlmatch.exec(rightValue)[1];
dataLoaded.right.fullname = rightValue;
dom.getJson(rightValue, function(error, data) {
if (error) {
dataLoaded.right.content = error + ((data && data.message) ? data.message : '');
} else {
dataLoaded.right.content = JSON.stringify(data, null, 2);
}
loadIfReady();
});
} else {
dataLoaded.right.content = rightValue;
}
loadIfReady();
} catch (err) {
load({
error: err
});
}
};
load.key = function(key) {
var matchers = {
gist: /^(?:https?:\/\/)?(?:gist\.github\.com\/)?(?:[\w0-9\-a-f]+\/)?([0-9a-f]+)$/i,
leftright: /^(?:desc=(.*)?&)?left=(.*)&right=(.*)&?$/i,
};
for (var loader in matchers) {
var match = matchers[loader].exec(key);
if (match) {
return load[loader].apply(load, match.slice(1));
}
}
load.data({
error: 'unsupported source: ' + key
});
};
var urlQuery = /^[^?]*\?([^\#]+)/.exec(document.location.href);
if (urlQuery) {
load.key(urlQuery[1]);
} else {
var exampleJson = getExampleJson();
load.data({
left: exampleJson[0],
right: exampleJson[1]
});
}
dom.on(document.getElementById('examples'), 'change', function() {
var example = trim(this.value);
switch (example) {
case 'text':
var exampleJson = getExampleJson();
load.data({
left: {
name: 'left.txt',
content: JSON.parse(exampleJson[0]).summary
},
right: {
name: 'right.txt',
content: JSON.parse(exampleJson[1]).summary
}
});
break;
case 'gist':
document.location = '?benjamine/9188826';
break;
case 'moving':
document.location = '?desc=moving%20around&left=' +
encodeURIComponent(JSON.stringify([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) +
'&right=' +
encodeURIComponent(JSON.stringify([10, 0, 1, 7, 2, 4, 5, 6, 88, 9, 3]));
break;
case 'query':
document.location = '?desc=encoded%20in%20url&left=' +
/* jshint quotmark: false */
encodeURIComponent(JSON.stringify({
"don't": "abuse",
"with": ["large", "urls"]
})) +
'&right=' +
encodeURIComponent(JSON.stringify({
"don't": "use",
"with": [">", 2, "KB urls"]
}));
break;
case 'urls':
document.location = '?desc=http%20raw%20file%20urls&left=' +
encodeURIComponent('https://rawgithub.com/benjamine/JsonDiffPatch/' +
'c83e942971c627f61ef874df3cfdd50a95f1c5a2/package.json') +
'&right=' +
encodeURIComponent('https://rawgithub.com/benjamine/JsonDiffPatch/master/package.json');
break;
default:
document.location = '?';
break;
}
});
})();