Skip to content

Instantly share code, notes, and snippets.

@fbender
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fbender/398ec9064a3d430942c4 to your computer and use it in GitHub Desktop.
Save fbender/398ec9064a3d430942c4 to your computer and use it in GitHub Desktop.
SPAN toggler with JSON import / export
// License: MIT
// Wrote this to help me with merging two spreadsheets using a WinMerge diff report. This is in no way optimized.
// The report HTML only requires adding the JS and desired CSS for the toggled state, e.g.:
// <style>span.toggled{background-color:lightcyan !important;}</style>
// Gecko-based browsers with context menu support have "Import" and "Export" items added to their context menu.
"use strict";
function getNodes(selector) {
var nodelist = document.querySelectorAll(selector);
var arr = [];
for(var i = nodelist.length; i--; arr.unshift(nodelist[i]));
return arr;
}
function setId(node) {
var newId = '';
if (typeof window.setAutoIdIndex != "number") {
window.setAutoIdIndex = -1;
}
if (node.id.length < 1) {
do {
newId = 'auto' + (window.setAutoIdIndex++);
}
while (document.getElementById(newId) != null);
return (node.id = newId);
}
else
console.log('Found node ' + node.id);
}
function toggleClass(event) {
if (event.target.nodeName == 'SPAN')
window.toggledIds[event.target.id] = event.target.classList.toggle('toggled');
else
console.log('Not a span: ' + event.target.nodeName);
}
function handleImport(event) {
var file = event.target.files[0];
//alert('File: ' + file.name);
var reader = new FileReader();
reader.addEventListener('load', function(event) {
try {
JSON.parse(reader.result);
localStorage.setItem('toggledIds', reader.result);
resetToggleState();
loadToggledIds();
alert('Import succeded.');
}
catch (e) {
alert('Reading failed. Invalid file?');
}
});
reader.readAsText(file);
}
function resetToggleState() {
getNodes('span.toggled').forEach(function(node){
node.classList.remove('toggled');
});
}
function loadToggledIds() {
// get toggled Ids from localStorage
var toggledIds = localStorage.getItem('toggledIds');
try {
window.toggledIds = JSON.parse(toggledIds);
}
catch (e) {
window.toggledIds = Object.create(null);
}
window.lastSavedTime = toggledIds['lastSave'];
window.__toggledIds_ = toggledIds['__toggledIds_'];
delete window.toggledIds.lastSave;
delete window.toggledIds.__toggledIds_;
// get hard-toggled nodes
getNodes('span.toggled').forEach(function(node){
window.toggledIds[node.id] = true;
});
//resetToggleState();
Object.getOwnPropertyNames(window.toggledIds).forEach(function(id){
var node = document.getElementById(id);
if (node && window.toggledIds[id]) {
node.classList.add('toggled');
}
else
console.log('Node '+id+' not toggled');
});
}
function saveToggledIds(toggledObject) {
var lastSavedTime = Date().toString();
console.log('Saving toggled Ids ...');
var toggledIds = Object.getOwnPropertyNames(toggledObject).filter(function(id){
return toggledObject[id];
});
Object.getOwnPropertyNames(toggledObject).forEach(function(id){
if (!toggledObject[id]) { // delete un-toggled entries
delete toggledObject[id];
}
});
toggledObject.lastSave = lastSavedTime;
toggledObject.__toggledIds_ = toggledIds;
localStorage.setItem('toggledIds', JSON.stringify(toggledObject));
delete toggledObject.lastSave;
delete toggledObject.__toggledIds_;
var info = document.getElementById('lastSaved');
if (!info) {
info = document.createElement('div');
info.id = 'lastSaved';
info.style.position = 'fixed';
info.style.bottom = 0;
info.style.right = 0;
info.style.padding = '0.2em 0.4em';
info.style.transition = 'background-color 0.3s ease-out';
document.body.appendChild(info);
}
info.style.backgroundColor = 'white';
info.textContent = 'Last saved: ' + lastSavedTime;
setTimeout(function() {
info.style.backgroundColor = 'lightpink';
}, 200);
window.lastSavedTime = lastSavedTime;
}
function createContextMenu() {
var menu = document.createElement('menu');
menu.id = 'importexport';
menu.type = 'context';
var items = [
{
label: 'Export',
funct: function(){
saveToggledIds(window.toggledIds);
var data = [localStorage.getItem('toggledIds')];
//var aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
var blob = new Blob(data, {type : 'application/json'}); // the blob
var down = document.getElementById('fileexport');
down.href = window.URL.createObjectURL(blob);
down.click();
}
},
{
label: 'Import',
funct: function(){
var up = document.getElementById('fileimport');
up.click();
}
}
];
items.forEach(function(item) {
var menuitem = document.createElement('menuitem');
menuitem.setAttribute('label', item.label);
menuitem.addEventListener('click', item.funct);
menu.appendChild(menuitem);
});
var oldMenu = document.getElementById(menu.id);
if (oldMenu) {
oldMenu.parentElement.removeChild(oldMenu);
}
oldMenu = null; // destroy old menu for good
document.body.appendChild(menu);
document.body.setAttribute('contextmenu', menu.id);
var upload = document.createElement('input');
upload.id = 'fileimport';
upload.type = 'file';
upload.style.opacity = 0;
upload.style.position = 'fixed';
upload.style.top = 0;
upload.style.left = 0;
upload.style.zIndex = -1;
upload.addEventListener('change', handleImport);
document.body.appendChild(upload);
var download = document.createElement('a');
download.id = 'fileexport';
download.setAttribute('download', 'state.json');
download.style.opacity = 0;
download.style.position = 'fixed';
download.style.top = 0;
download.style.left = 0;
download.style.zIndex = -1;
document.body.appendChild(download);
}
//mod = document.querySelectorAll('.sf17b16');
// add Id to each span
var spans = getNodes('span');
spans.forEach(function(node){setId(node);});
loadToggledIds();
createContextMenu();
// add events
document.body.addEventListener('click', toggleClass);
window.addEventListener('beforeunload', function(){saveToggledIds(window.toggledIds);});
setInterval(function(){saveToggledIds(window.toggledIds);}, 15000); // save every 10s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment