solid-ui
Version:
UI library for writing Solid read-write-web applications
644 lines (503 loc) • 16.7 kB
JavaScript
"use strict";
// Solid-UI general Utilities
// ==========================
//
// This must load AFTER the rdflib.js and log-ext.js (or log.js).
//
module.exports = {
addLoadEvent: addLoadEvent,
// not used anywhere
AJARImage: AJARImage,
ancestor: ancestor,
beep: beep,
clearVariableNames: clearVariableNames,
emptyNode: emptyNode,
escapeForXML: escapeForXML,
findPos: findPos,
genUuid: genUuid,
getAbout: getAbout,
getEyeFocus: getEyeFocus,
getTarget: getTarget,
getTerm: getTerm,
hashColor: hashColor,
include: include,
label: label,
labelForXML: labelForXML,
labelWithOntology: labelWithOntology,
newVariableName: newVariableName,
ontologyLabel: ontologyLabel,
predicateLabelForXML: predicateLabelForXML,
predParentOf: predParentOf,
RDFComparePredicateObject: RDFComparePredicateObject,
RDFComparePredicateSubject: RDFComparePredicateSubject,
shortName: shortName,
stackString: stackString,
syncTableToArray: syncTableToArray
};
var UI = {
log: require('./log'),
ns: require('./ns'),
rdf: require('rdflib'),
store: require('./store')
};
var nextVariable = 0;
function newVariableName() {
return 'v' + nextVariable++;
}
function clearVariableNames() {
nextVariable = 0;
} // http://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep
// http://www.tsheffler.com/blog/2013/05/14/audiocontext-noteonnoteoff-and-time-units/
var audioContext;
if (typeof AudioContext !== 'undefined') {
audioContext = AudioContext;
} else if (typeof window !== 'undefined') {
audioContext = window.AudioContext || window.webkitAudioContext;
}
function beep() {
if (!audioContext) {
return;
} // Safari 2015
var ContextClass = audioContext;
var ctx = new ContextClass();
return function (duration, frequency, type, finishedCallback) {
duration = +(duration || 0.3); // Only 0-4 are valid types.
type = type || 'sine'; // sine, square, sawtooth, triangle
if (typeof finishedCallback !== 'function') {
finishedCallback = function finishedCallback() {};
}
var osc = ctx.createOscillator();
osc.type = type;
osc.frequency.value = frequency || 256;
osc.connect(ctx.destination);
osc.start(0);
osc.stop(duration);
};
} // Make pseudorandom color from a uri
// NOT USED ANYWHERE
function hashColor(who) {
who = who.uri || who;
var hash = function hash(x) {
return x.split('').reduce(function (a, b) {
a = (a << 5) - a + b.charCodeAt(0);
return a & a;
}, 0);
};
return '#' + (hash(who) & 0xffffff | 0xc0c0c0).toString(16); // c0c0c0 or 808080 forces pale
}
function genUuid() {
// http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : r & 0x3 | 0x8;
return v.toString(16);
});
}
/** Sync a DOM table with an array of things
*
* @param {DomElement} table - will have a tr for each thing
* @param {Array<NamedNode>} things - ORDERED array of NamedNode objects
* @param {function({NamedNode})} createNewRow(thing) returns a TR table row for a new thing
*
* Tolerates out of order elements but puts new ones in order.
*/
function syncTableToArray(table, things, createNewRow) {
var foundOne;
var row;
var i;
for (i = 0; i < table.children.length; i++) {
row = table.children[i];
row.trashMe = true;
}
for (var g = 0; g < things.length; g++) {
var thing = things[g];
foundOne = false;
for (i = 0; i < table.children.length; i++) {
row = table.children[i];
if (row.subject && row.subject.sameTerm(thing)) {
row.trashMe = false;
foundOne = true;
break;
}
}
if (!foundOne) {
var newRow = createNewRow(thing); // Insert new row in position g in the table to match array
if (g >= table.children.length) {
table.appendChild(newRow);
} else {
var ele = table.children[g];
table.insertBefore(newRow, ele);
}
newRow.subject = thing;
} // if not foundOne
} // loop g
for (i = 0; i < table.children.length; i++) {
row = table.children[i];
if (row.trashMe) {
table.removeChild(row);
}
}
} // syncTableToArray
/* Error stack to string for better diagnotsics
**
** See http://snippets.dzone.com/posts/show/6632
*/
function stackString(e) {
var str = '' + e + '\n';
var i;
if (!e.stack) {
return str + 'No stack available.\n';
}
var lines = e.stack.toString().split('\n');
var toPrint = [];
for (i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.indexOf('ecmaunit.js') > -1) {
// remove useless bit of traceback
break;
}
if (line.charAt(0) === '(') {
line = 'function' + line;
}
var chunks = line.split('@');
toPrint.push(chunks);
} // toPrint.reverse(); No - I prefer the latest at the top by the error message -tbl
for (i = 0; i < toPrint.length; i++) {
str += ' ' + toPrint[i][1] + '\n ' + toPrint[i][0];
}
return str;
}
function emptyNode(node) {
var nodes = node.childNodes;
var len = nodes.length;
var i;
for (i = len - 1; i >= 0; i--) {
node.removeChild(nodes[i]);
}
return node;
}
function getTarget(e) {
var target;
e = e || window.event;
if (e.target) target = e.target;else if (e.srcElement) target = e.srcElement;
if (target.nodeType === 3) {
// defeat Safari bug [sic]
target = target.parentNode;
} // UI.log.debug("Click on: " + target.tagName)
return target;
}
function ancestor(target, tagName) {
var level;
for (level = target; level; level = level.parentNode) {
// UI.log.debug("looking for "+tagName+" Level: "+level+" "+level.tagName)
try {
if (level.tagName === tagName) return level;
} catch (e) {
// can hit "TypeError: can't access dead object" in ffox
return undefined;
}
}
return undefined;
}
function getAbout(kb, target) {
var level, aa;
for (level = target; level && level.nodeType === 1; level = level.parentNode) {
// UI.log.debug("Level "+level + ' '+level.nodeType + ': '+level.tagName)
aa = level.getAttribute('about');
if (aa) {
// UI.log.debug("kb.fromNT(aa) = " + kb.fromNT(aa))
return kb.fromNT(aa); // } else {
// if (level.tagName=='TR') return undefined//this is to prevent literals passing through
}
}
UI.log.debug('getAbout: No about found');
return undefined;
}
function getTerm(target) {
var statementTr = target.parentNode;
var st = statementTr ? statementTr.AJAR_statement : undefined;
var className = st ? target.className : ''; // if no st then it's necessary to use getAbout
switch (className) {
case 'pred':
case 'pred selected':
return st.predicate;
case 'obj':
case 'obj selected':
if (!statementTr.AJAR_inverse) {
return st.object;
} else {
return st.subject;
}
case '':
case 'selected':
// header TD
return getAbout(UI.store, target);
// kb to be changed
case 'undetermined selected':
return target.nextSibling ? st.predicate : !statementTr.AJAR_inverse ? st.object : st.subject;
}
}
function include(document, linkstr) {
var lnk = document.createElement('script');
lnk.setAttribute('type', 'text/javascript');
lnk.setAttribute('src', linkstr); // TODO:This needs to be fixed or no longer used.
// document.getElementsByTagName('head')[0].appendChild(lnk)
return lnk;
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload !== 'function') {
window.onload = func;
} else {
window.onload = function () {
oldonload();
func();
};
}
} // addLoadEvent
// Find the position of an object relative to the window
function findPos(obj) {
// C&P from http://www.quirksmode.org/js/findpos.html
var myDocument = obj.ownerDocument;
var DocBox = myDocument.documentElement.getBoundingClientRect();
var box = obj.getBoundingClientRect();
return [box.left - DocBox.left, box.top - DocBox.top];
}
function getEyeFocus(element, instantly, isBottom, myWindow) {
if (!myWindow) myWindow = window;
var elementPosY = findPos(element)[1];
var totalScroll = elementPosY - 52 - myWindow.scrollY; // magic number 52 for web-based version
if (instantly) {
if (isBottom) {
myWindow.scrollBy(0, elementPosY + element.clientHeight - (myWindow.scrollY + myWindow.innerHeight));
return;
}
myWindow.scrollBy(0, totalScroll);
return;
}
var id = myWindow.setInterval(scrollAmount, 50);
var times = 0;
function scrollAmount() {
myWindow.scrollBy(0, totalScroll / 10);
times++;
if (times === 10) {
myWindow.clearInterval(id);
}
}
}
function AJARImage(src, alt, tt, doc) {
if (!doc) {
doc = document;
}
var image = doc.createElement('img');
image.setAttribute('src', src);
image.addEventListener('copy', function (e) {
e.clipboardData.setData('text/plain', '');
e.clipboardData.setData('text/html', '');
e.preventDefault(); // We want no title data to be written to the clipboard
}); // if (typeof alt != 'undefined') // Messes up cut-and-paste of text
// image.setAttribute('alt', alt)
if (typeof tt !== 'undefined') {
image.setAttribute('title', tt);
}
return image;
} // Make short name for ontology
function shortName(uri) {
var p = uri;
if ('#/'.indexOf(p[p.length - 1]) >= 0) p = p.slice(0, -1);
var namespaces = [];
for (var ns in this.prefixes) {
namespaces[this.prefixes[ns]] = ns; // reverse index
}
var pok;
var canUse = function canUse(pp) {
// if (!__Serializer.prototype.validPrefix.test(pp)) return false; // bad format
if (pp === 'ns') return false; // boring
// if (pp in this.namespaces) return false; // already used
// this.prefixes[uri] = pp;
// this.namespaces[pp] = uri;
pok = pp;
return true;
};
var i;
var hash = p.lastIndexOf('#');
if (hash >= 0) p = p.slice(hash - 1); // lop off localid
for (;;) {
var slash = p.lastIndexOf('/');
if (slash >= 0) p = p.slice(slash + 1);
i = 0;
while (i < p.length) {
if (this.prefixchars.indexOf(p[i])) i++;else break;
}
p = p.slice(0, i);
if (p.length < 6 && canUse(p)) return pok; // exact i sbest
if (canUse(p.slice(0, 3))) return pok;
if (canUse(p.slice(0, 2))) return pok;
if (canUse(p.slice(0, 4))) return pok;
if (canUse(p.slice(0, 1))) return pok;
if (canUse(p.slice(0, 5))) return pok;
for (i = 0;; i++) {
if (canUse(p.slice(0, 3) + i)) return pok;
}
}
} // Short name for an ontology
function ontologyLabel(term) {
if (term.uri === undefined) return '??';
var s = term.uri;
var namespaces = [];
var i = s.lastIndexOf('#');
var part;
if (i >= 0) {
s = s.slice(0, i + 1);
} else {
i = s.lastIndexOf('/');
if (i >= 0) {
s = s.slice(0, i + 1);
} else {
return term.uri + '?!'; // strange should have # or /
}
}
for (var ns in UI.ns) {
namespaces[UI.ns[ns]] = ns; // reverse index
}
try {
return namespaces[s];
} catch (e) {}
s = s.slice(0, -1); // Chop off delimiter ... now have just
while (s) {
i = s.lastIndexOf('/');
if (i >= 0) {
part = s.slice(i + 1);
s = s.slice(0, i);
if (part !== 'ns' && '0123456789'.indexOf(part[0]) < 0) {
return part;
}
} else {
return term.uri + '!?'; // strange should have a nice part
}
}
}
function labelWithOntology(x, initialCap) {
var t = UI.store.findTypeURIs(x);
if (t[UI.ns.rdf('Predicate').uri] || t[UI.ns.rdfs('Class').uri]) {
return label(x, initialCap) + ' (' + ontologyLabel(x) + ')';
}
return label(x, initialCap);
} // This ubiquitous function returns the best label for a thing
//
// The hacks in this code make a major difference to the usability
//
// @returns string
//
function label(x, initialCap) {
// x is an object
function doCap(s) {
// s = s.toString()
if (initialCap) return s.slice(0, 1).toUpperCase() + s.slice(1);
return s;
}
function cleanUp(s1) {
var s2 = '';
if (s1.slice(-1) === '/') s1 = s1.slice(0, -1); // chop trailing slash
for (var i = 0; i < s1.length; i++) {
if (s1[i] === '_' || s1[i] === '-') {
s2 += ' ';
continue;
}
s2 += s1[i];
if (i + 1 < s1.length && s1[i].toUpperCase() !== s1[i] && s1[i + 1].toLowerCase() !== s1[i + 1]) {
s2 += ' ';
}
}
if (s2.slice(0, 4) === 'has ') s2 = s2.slice(4);
return doCap(s2);
} // The tabulator labeler is more sophisticated if it exists
// Todo: move it to a solid-ui option.
var lab;
if (typeof tabulator !== 'undefined' && tabulator.lb) {
lab = tabulator.lb.label(x);
if (lab) {
return doCap(lab.value);
}
} // Hard coded known label predicates
// @@ TBD: Add subproperties of rdfs:label
var kb = UI.store;
var lab1 = kb.any(x, UI.ns.link('message')) || kb.any(x, UI.ns.vcard('fn')) || kb.any(x, UI.ns.foaf('name')) || kb.any(x, UI.ns.dct('title')) || kb.any(x, UI.ns.dc('title')) || kb.any(x, UI.ns.rss('title')) || kb.any(x, UI.ns.contact('fullName')) || kb.any(x, kb.sym('http://www.w3.org/2001/04/roadmap/org#name')) || kb.any(x, UI.ns.cal('summary')) || kb.any(x, UI.ns.foaf('nick')) || kb.any(x, UI.ns.rdfs('label'));
if (lab1) {
return doCap(lab1.value);
} // Default to label just generated from the URI
if (x.termType === 'BlankNode') {
return '...';
}
if (x.termType === 'Collection') {
return '(' + x.elements.length + ')';
}
var s = x.uri;
if (typeof s === 'undefined') return x.toString(); // can't be a symbol
// s = decodeURI(s) // This can crash is random valid @ signs are presentation
// The idea was to clean up eg URIs encoded in query strings
// Also encoded character in what was filenames like @ [] {}
try {
s = s.split('/').map(decodeURIComponent).join('/'); // If it is properly encoded
} catch (e) {
// try individual decoding of ASCII code points
for (var i = s.length - 3; i > 0; i--) {
var hex = '0123456789abcefABCDEF'; // The while upacks multiple layers of encoding
while (s[i] === '%' && hex.indexOf(s[i + 1]) >= 0 && hex.indexOf(s[i + 2]) >= 0) {
s = s.slice(0, i) + String.fromCharCode(parseInt(s.slice(i + 1, i + 3), 16)) + s.slice(i + 3);
}
}
}
if (s.slice(-5) === '#this') s = s.slice(0, -5);else if (s.slice(-3) === '#me') s = s.slice(0, -3);
var hash = s.indexOf('#');
if (hash >= 0) return cleanUp(s.slice(hash + 1));
if (s.slice(-9) === '/foaf.rdf') s = s.slice(0, -9);else if (s.slice(-5) === '/foaf') s = s.slice(0, -5); // Eh? Why not do this? e.g. dc:title needs it only trim URIs, not rdfs:labels
var slash = s.lastIndexOf('/', s.length - 2); // (len-2) excludes trailing slash
if (slash >= 0 && slash < x.uri.length) return cleanUp(s.slice(slash + 1));
return doCap(decodeURIComponent(x.uri));
}
function escapeForXML(str) {
return str.replace(/&/g, '&').replace(/</g, '<');
} // As above but escaped for XML and chopped of contains a slash
function labelForXML(x) {
return escapeForXML(label(x));
} // As above but for predicate, possibly inverse
function predicateLabelForXML(p, inverse) {
var lab;
if (inverse) {
// If we know an inverse predicate, use its label
var ip = UI.store.any(p, UI.ns.owl('inverseOf'));
if (!ip) ip = UI.store.any(undefined, UI.ns.owl('inverseOf'), p);
if (ip) return labelForXML(ip);
}
lab = labelForXML(p);
if (inverse) {
if (lab === 'type') return '...'; // Not "is type of"
return 'is ' + lab + ' of';
}
return lab;
} // Not a method. For use in sorts
function RDFComparePredicateObject(self, other) {
var x = self.predicate.compareTerm(other.predicate);
if (x !== 0) return x;
return self.object.compareTerm(other.object);
}
function RDFComparePredicateSubject(self, other) {
var x = self.predicate.compareTerm(other.predicate);
if (x !== 0) return x;
return self.subject.compareTerm(other.subject);
} // ends
function predParentOf(node) {
var n = node;
while (true) {
if (n.getAttribute('predTR')) {
return n;
} else if (n.previousSibling && n.previousSibling.nodeName === 'TR') {
n = n.previousSibling;
} else {
UI.log.error('Could not find predParent');
return node;
}
}
} // makeQueryRow moved to outline mode
//# sourceMappingURL=utils.js.map