assets/js/DynamicIFrame.js

1037 lines
46 KiB
JavaScript

/*
Dieses Script wurde geschrieben von Mitja Stachowiak (www.mitjastachowiak.de)
Es benoetigt ausserdem frameCommunicator.js.
Dokumentation unter http://www.mitjastachowiak.de/code/js/dynamicIFrame
*/
function getTickCount () { // returns the milliseconds since beginning of the current year
var T = new Date();
return T.getMilliseconds() + T.getSeconds() * 1000 + T.getMinutes() * 60000 + T.getHours() * 3600000 + T.getDay() * 86400000;
}
function createOverscrollListener (doc, wait, callback) {
var o = new Object();
o.doc = doc;
o.lastScroll = 0;
o.lastTry = 0;
o.lastInnerWidth = window.innerWidth;
o.wait = wait / 2;
o.callback = callback;
o.scrollFkt = function () {
o.lastScroll = getTickCount();
}
o.scrollEventFkt = function (ev) {
if (getTickCount() < o.lastScroll + o.wait) { o.lastTry = 0; return; }
if (o.lastTry == 0) o.lastTry = getTickCount();
if (getTickCount() < o.lastTry + o.wait) { o.lastInnerWidth = window.innerWidth; return; }
if (o.lastInnerWidth != window.innerWidth) { o.lastInnerWidth = window.innerWidth; return; }
o.lastTry = 0;
if (!ev) ev = window.event;
var dir = ev.wheelDelta || -ev.detail;
if (!callback) return;
if (dir) { o.callback(dir < 0); return; }
if (window.pageYOffset > 0) { o.callback(true); return; }
if (ev.keyCode) { o.callback(ev.keyCode == 40); return; }
}
o.free = function () {
o.doc.removeEventListener('scroll', o.scrollFkt, false);
o.doc.removeEventListener('DOMMouseScroll', o.scrollEventFkt, false);
o.doc.removeEventListener('mousewheel', o.scrollEventFkt, false);
o.doc.removeEventListener('keypress', o.scrollEventFkt, false);
o.callback = null;
o.doc = null;
o = null;
}
// EventListener setzen
var d = o.doc;
var _d = null;
while (d && d != _d) {
try {
_d = d;
d.addEventListener('scroll', o.scrollFkt, false);
d = d.defaultView.parent.document;
} catch (e) { break; }
}
o.doc.addEventListener('DOMMouseScroll', o.scrollEventFkt, false);
o.doc.addEventListener('mousewheel', o.scrollEventFkt, false);
o.doc.addEventListener('keypress', o.scrollEventFkt, false);
return o;
}
/*
Dies sind zwei standard-Effekte. Diese koennen Sie als Vorlage fuer eigene Effekte verwenden
Parameter:
el : iframe-Element
s : Wert zwischen 0 und 1; Ausnahme -1 (0 = Effekt beginnt, 0.5 = Effekt ist bei der Haelfte, 1 = Effekt ist fertig, -1 = saemtliche von diesem Effekt benutzten css-Eigenschaften zuruecksetzen)
blendIn : Wenn true, soll das iframe eingeblendet werden, ansonsten ausgeblendet
*/
function EffectBlend (el, s, blendIn) {
if (s < 0) { el.style.opacity = 1; return; }
if (!blendIn) s = 1-s;
el.style.opacity = s;
}
function EffectBlendAndSlide (el, s, blendIn) {
if (s < 0) { el.style.marginTop = '0px'; el.style.opacity = 1; return; }
if (!blendIn) s = 1-s;
el.style.opacity = s;
el.style.marginTop = -100 * (1-s) * (1-s) + 'px';
}
function EffectAppendAndSlide (el1, el2, s, dir) {
if (s < 0) {
el1.style.marginTop = '0px';
el2.style.marginTop = '0px';
el1.parentElement.style.overflow = 'visible';
return;
}
el1.parentElement.style.overflow = 'hidden';
if (dir) {
el1.style.marginTop = -el1.offsetHeight * (1-s);
el2.style.marginTop = el1.offsetHeight * (s);
} else {
el2.style.marginTop = -el2.offsetHeight * (s);
el1.style.marginTop = el2.offsetHeight * (1-s);
}
}
/*
Dieses Objekt darf nur einmal in einem HTML-Dokument erstellt werden und erlaubt, das Laden von Seiten in einem iframe mit Effekt zu versehen.
Parameter:
FrameBox : ein DIV-Element, das genau 2 iframes enthaellt. Jedes IFrame benoetigt ein eindeutriges name-Attribut. Nur so kann die Kompatibilitaet mit Links ohne JavaScript gewaehrleistet werden (<A href="frameSeite.html" target="frameName">).
Eigenschaften und Funktionen:
whitelist : Array aus RegExp-Objekten. Alle Seiten, die im Frame geladen werden sollen, muessen entweder von der gleichen Domain sein, oder auf die Whitelist passen
addURLToWhitelist : Wandelt die uebergebene URL in ein RegExp-Objekt um und fuegt sie der whitelist hinzu. In der URL sind *-Zeichen erlaubt. Innerhalb des Hostname-Bereiches haben diese jedoch spezielle Aufgaben und es kann nicht jede beliebige Zeichenfolge fuer ein * stehen.
differentQueryDifferentSites : Boolean - wenn true, dann werden Menuelinks nur hervorgehoen, wenn auch das QUerystring des Menuelinks und der Seite im Frame gleich ist.
whitelist : Array aus Strigs (Darf Platzhalter (*) enthalten). Nur Seiten, die in dieses Profil passen, duerfen im Frame geladen werden. Seiten von der eigenen Domain duerfen immer geladen werden.
maxWaitForSite : diese Zeit (ms) wird maximal gewartet, bis die neue Seite eingeblendet wird. Ist die Seite bis dahin nicht vollstaendig geladen, kann der Besucher beobachten, wie sie im iframe fertig laedt.
blendOutTime : Dauer des Ausblendens (ms)
blendInDelay : Verzoegerung - so viele Millisekunden nach Beginn des Ausblendens beginnt das Einblenden
blendInTime : Dauer des Einblendens (ms)
blendInEffect : Uebergeben Sie hier die Effekt-Funktion fuer das Einblenden
blendOutEffect : Uebergeben Sie hier die Effekt-Funktion fuer das Ausblenden
function loadSite : Rufen Sie diese Funktion mit einer URL auf, um diese URL mit Effekt in den iframe zu laden (Die Seite wird nur geladen, wenn sie von der gleichen Domain ist oder auf die Whitelist passt)
function scrollTo : uebergeben werden kann eine y-Koordinate aus Sicht des aktuellen Iframes oder der Name eines Ankers innerhalb des aktuellen Iframes. ScrollTo scrollt dann zu diesem Ziel. Ein zweiter Parameter darf nur vom Objekt selbst uebergeben werden.
autoFillMenu : Falls die Menu-Links ein Inhaltsverzeichnis darstellen, kann fuer autoFillMenu eine Zahl eingesetzt werden, bis zu der H*-Elemente der Frame-Seite gescant und automatisch im Inhaltsverzeichnis ergaenzt werden. Ein Wert von 3 z.B. bedeutet, dass fuer alle H1, H2 und H3-Elemente automatisch Links im Inhaltsverzeichnis beim Laden der Seite angelegt werden.
*/
function DynamicIFrame (FrameBox, debugLogging=false) { with (this) {
// private
var box = FrameBox;
var frames = FrameBox.getElementsByTagName('iframe');
var current = 0;
var next = 1;
var communicators = new Array();
var aTarget, aTarget2;
var isAutoFrameHeight = true;
var menuLinks = new Array();
var printLinks = new Array();
var blendInfo = new Object();
var scrollInfo = new Object();
var autoHeightEl = new Array(); // Alle Elemente, deren NoJsHeight-Classname gesetzt/entfernt wird
var loadWithoutEffect = false;
var startPage = '';
var incompatibleBrowser = false;
var domWasReady = false;
var selfHostname;
var currentSite = '';
var loadingSiteAnchor = ''; // Anker der zu ladenden Seite aus URL, wird gespeichert fuer scroll-Effekt
var currentSiteAnchors = new Array(); // Array aus allen Ueberschrifen-Ankern der aktuellen Seite (kann nach absOffsetTop der Anker sortiert werden)
var anchorPos = 0; // index in currentSiteAnchors des gerade aktiven Ankers
var overscrollListener = null;
var currentHirachy = null;
var oldQuery = '';
var scriptLoadingPage = '';
// public
this.differentQueryDifferentSites = false;
this.whitelist = new Array();
this.maxWaitForSite = 1000; // Nach dieser Zeit wird die Seite anzegeigt, auch wenn noch nicht vollst. geladen
this.blendOutTime = 0;
this.blendInDelay = 0;
this.blendInTime = 0;
this.scrollTime = 500;
this.blendInEffect = null;
this.blendOutEffect = null;
this.browseEffect = EffectAppendAndSlide;
this.browseDuration = 2000;
this.scrollFunction = function (s) { return (-2*s+3)*s*s; }
this.browseScrollFunction = function (s) { return s; }
this.autoFillMenu = 0; // Bis zu dieser Zahl * werden H*-Elemente im Menue ergaenzt
this.autoChangeAnchor = false;
this.nummerateMenu = false;
this.onSiteLoaded = null;
this.onLinkActivate = null;
this.onMenuChange = null;
this.onTitleChange = null;
this.onContentResize = null;
var _this = this;
var onEvent = /* private */ function (el, event, fkt, param, behavior) { // provides easy eventListeners. el: Element to set the listener; event: string-name of the event(without "on"); fkt: a Function to call ift the event occours, fkt is called in the scope of _this; param: any parameter, that is given to fkt; behavior: flags - 1=only the param is given to fkt (otherwise there is also given the element and the event), 2=event is not given to the element
var f;
if (behavior & 1) f = function (ev) {
if (!ev) ev = window.event;
fkt.call(_this,param);
if (behavior & 2) { if (ev.preventDefault) ev.preventDefault(); return false; }
}
else f = function (ev) {
if (!ev) ev = window.event;
fkt.call(_this,this,ev,param);
if (behavior & 2) { if (ev.preventDefault) ev.preventDefault(); return false; }
}
if (window.addEventListener) el.addEventListener(event, f, false);
else el.attachEvent('on'+event,f);
return false;
};
var getAbsOffsetTop = function (el) { // returns the offset top of the given element seen from the document top (el CAN be in the current IFRAME)
var pos = el.offsetTop;
while (el.offsetParent) {
if (el.offsetParent == el) break;
el = el.offsetParent;
pos += el.offsetTop;
}
try {
if (el == frames[current].contentWindow.document.body || el == frames[current].contentWindow.document.documentElement) pos += getAbsOffsetTop(frames[current]);
} catch (e) { }
return pos;
};
var getAnchorFromURL = /* private */ function (url) {
var i = url.lastIndexOf('#');
if (i < 0) return '';
var a = url.substring(i+1, url.length);
if ((/[^(A-Za-z0-9_)]/).test(a)) return '';
return a;
};
var getSiteFromURL = /* private */ function (url) {
var i = url.lastIndexOf('#');
var j = url.indexOf('?');
if (!differentQueryDifferentSites && j >= 0 && j < j) i = j;
if (i < 0) i = url.length;
return url.substring(0,i);
};
var create = /* private */ function () {
if ((!window.addEventListener && !window.attachEvent) || !document.body || !document.getElementsByTagName) { incompatibleBrowser = true; return; }
if (!window.fapi) { incompatibleBrowser = true; throw 'DynamicIFrame benoetigt frameCommunicator.js!'; }
if (window.jsw && jsw.units.switchableDesign) jsw.units.switchableDesign.unit.requireDesignFile('dynamicIFrame', box);
selfHostname = getHostnameAndProtocol(self.location.href);
// Re-Create Frame elements, because browser does stupid things on hard coded iframes when re-loading a site...
startPage = frames[0].src;
aTarget = frames[0].getAttribute('name', 0);
aTarget2 = frames[1].getAttribute('name', 0);
frames[1].parentElement.removeChild(frames[1]);
frames[0].parentElement.removeChild(frames[0]);
if (frames.length > 0) throw 'Not more than two frames allowed in HTML!';
var f = document.createElement('iframe');
f.setAttribute('name', aTarget);
f.id = aTarget;
FrameBox.appendChild(f);
f = document.createElement('iframe');
f.setAttribute('name', aTarget2);
f.id = aTarget2;
FrameBox.appendChild(f);
// prepare communicators
communicators[0] = new FrameCommunicator(frames[0], (debugLogging)?'0':'');
communicators[1] = new FrameCommunicator(frames[1], (debugLogging)?'1':'');
communicators[0].autoHeight = true;
communicators[1].autoHeight = true;
communicators[0].onContentResize = function (w, h) { siteResize(0, h); };
communicators[1].onContentResize = function (w, h) { siteResize(1, h); };
communicators[0].onURLChanged = setQueryString;
communicators[1].onURLChanged = setQueryString;
communicators[0].onload = manipulateCurrentSite;
communicators[1].onload = manipulateCurrentSite;
communicators[0].onTitleChanged = titleChange;
communicators[1].onTitleChanged = titleChange;
if (document.addEventListener) document.addEventListener("DOMContentLoaded", domReady, false);
else {
var oldReadyStateChange = document.onreadystatechange;
document.onreadystatechange = function () {
try { oldReadyStateChange(); } catch (e) { }
if(document.readyState == "interactive" || document.readyState == "complete") domReady();
}
}
// QueryString untersuchen
if (window.addEventListener) window.addEventListener('popstate', evalQueryString, false);
}
var domReady = /* private */ function () {
if (domWasReady) return;
domWasReady = true;
// Links auf JavaScript umstellen
for (var i = 0; i < document.getElementsByTagName('a').length; i++)
linkJSenhance(document.getElementsByTagName('a')[i]);
if (nummerateMenu) buildMenuNumbers();
activateCurrentLink();
// AutoHeight-Elemente finden
for (var i = 0; i < document.getElementsByTagName('div').length; i++) if (document.getElementsByTagName('div')[i].className.indexOf('NoJsHeight') >= 0) autoHeightEl.push(document.getElementsByTagName('div')[i])
if (!isAutoFrameHeight) {
isAutoFrameHeight = true;
autoFrameHeight(false);
}
// Scroll-Event setzen
window.addEventListener('scroll', sitescroll, false);
// Seite aus Querystring laden
window.setTimeout(function () { // for whatever reason, events do not pass before dom content is loaded.
if (scriptLoadingPage != '' || !evalQueryString()) {
if (scriptLoadingPage != '') {
debugLog('Load site set by script: '+scriptLoadingPage);
loadSite(scriptLoadingPage, 1);
} else {
debugLog('Load start site: '+startPage);
var hashtag = window.location.href.split('#');
if (hashtag.length > 1) hashtag = '#'+hashtag.pop();
else hashtag = '';
loadSite(startPage+hashtag, 1);
}
}
}, 0);
};
var debugLog = /* private */ function (msg) {
if (!debugLogging) return;
var d = new Date();
console.log('['+d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()+' '+d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds()+'] '+msg);
}
var evalQueryString = /* private */ function (effect) {
// Wird in domready und beim klicken auf den Vorwaerts/Rueckwaerts-Button im Browser aufgerufen
var newSite = self.location.href.split('?');
newSite.shift();
if (newSite.length == 0) return false;
newSite[0] = newSite[0].split('&');
var isFB = false;
for (var i = 0; i < newSite[0].length; i++)
if (newSite[0][i].indexOf('fbclid=') == 0 || newSite[0][i].indexOf('\/gclid=') == 0) {
newSite[0].splice(i, 1);
isFB = true;
break;
}
if (newSite[0].length == 0) return false;
newSite[0] = newSite[0].join('&');
if (isFB) newSite[0] = newSite[0].split('%2F').join('\/');
var newSite = absolutePath(newSite.join('?'));
if (isNotInWhitelist(newSite)) return false;
debugLog('Load site by query: '+newSite);
loadSite(newSite, (effect)?0:1);
return true;
};
this.addURLToWhitelist = /* public */ function (url) {
var rep = new RegExp('([^\\w&^\\*])', 'g');
var host = getHostnameAndProtocol(url);
var i = host.length;
host = host.replace(rep, '\\$1');
var path = url.substring(i, url.length).replace(rep, '\\$1');
if (path == '' && host.length > 0 && host.charAt(host.length-1) == '*') path = '*';
while (host.length > 0 && host.charAt(host.length-1) == '*') host = host.substring(0, host.length-1);
if (host == '') throw 'URL has wrong syntax';
rep = new RegExp('\\*', 'g');
path = path.replace(rep, '[a|^a]*'); // innerhalb des path-Bereiches koennen die * fuer beliebige Inhalte stehen
// innerhalb des hostname-Bereiches haben die * spezielle Aufgaben
var a = host.split('\\\:\\\/\\\/');
if (a.length == 1) {
a = a[0].split('*');
if (a.length == 2)
host = a[0] + '(\\w)*\\:\\/\\/((\\w)+\\.)*' + a[1];
else throw 'URL has wrong syntax';
} else if (a.length == 2) {
rep = new RegExp('(\\*)', 'g');
a[0] = a[0].replace(rep, '(\\w)*');
a[1] = a[1].replace(rep, '((\\w)+\\.)*');
host = a[0] + '\\:\\/\\/' + a[1];
} else throw 'URL has wrong syntax';
whitelist.push(new RegExp('\^'+host+path));
};
var isNotInWhitelist = /* private */ function (src) {
if (getHostnameAndProtocol(src) == selfHostname) return false;
for (var i = 0; i < whitelist.length; i++) if (whitelist[i].test(src)) return false;
return true;
};
var buildMenuNumbers = /* private */ function () {
if (!nummerateMenu) return;
for (var i = 0; i < menuLinks.length; i++) {
if (menuLinks[i]['el'].parentElement.tagName.toLowerCase() != 'li') continue;
var ol = menuLinks[i]['el'].parentElement;
var a = new Array();
while (ol.parentElement && ol.parentElement.tagName.toLowerCase() == 'ol') {
var nr = 1;
var el = ol.parentElement.firstElementChild;
while (el && el != ol) {
el = el.nextElementSibling;
if (el.tagName.toLowerCase() == 'li') nr++;
}
a.unshift(nr);
ol = ol.parentElement;
}
if (!menuLinks[i]['numberEl']) {
menuLinks[i]['numberEl'] = document.createElement('span');
menuLinks[i]['el'].insertBefore(menuLinks[i]['numberEl'], menuLinks[i]['el'].firstChild);
}
menuLinks[i]['numberEl'].innerHTML = a.join('.')+'. ';
}
};
var activateLink = /* private */ function (link) { // das in link uebergebene MenuLink-Objekt wird aktiviert, ggf. akuell aktive Links werden deaktiviert
if (window.opera && link) { link['el'].style.visibility = 'hidden'; link['el'].style.visibility = 'visible'; } // Komisches Bug
if (link && link['active']) return;
for (var i = 0; i < menuLinks.length; i++) if (menuLinks[i]['active']) {
menuLinks[i]['active'] = false;
var c = menuLinks[i]['el'].className.split(' ');
for (var j = c.length-1; j >= 0; j--) if (c[j] == 'active') c.splice(j, 1);
menuLinks[i]['el'].className = c.join(' ');
}
if (!link) { if (onLinkActivate) onLinkActivate(null); return; }
link['active'] = true;
link['el'].className += ' active';
if (onLinkActivate) onLinkActivate(link['el']);
activateCurrentHirachy();
};
var activateCurrentHirachy = /* private */ function () {
var link = null;
for (var i = 0; i < menuLinks.length; i++)
if (menuLinks[i]['active']) {
link = menuLinks[i];
break;
}
if (!link || !currentHirachy) return;
var found = false;
for (var i = currentHirachy.length-1; i >= 0; i--)
if (found) {
if (currentHirachy[i].link && currentHirachy[i].link['el'].getAttribute('alwaysactive') == 'yes' && !currentHirachy[i].link['active']) {
currentHirachy[i].link['active'] = true;
currentHirachy[i].link['el'].className += ' active';
}
} else if (currentHirachy[i].link == link) found = true;
};
var activateCurrentLink = /* private */ function () {
// Seite und Anker ermitteln
var site = currentSite;
var anchor = getAnchorFromURL(communicators[current].contentURL);
if (site.indexOf('?') != -1) site = site.substring(0, site.indexOf('?'));
// Links mit gleicher Seite suchen
var a = new Array();
for (var i = 0; i < menuLinks.length; i++)
if (site == menuLinks[i]['site'])
a.push(menuLinks[i]);
// Keinen passenden Link gefunden
if (a.length == 0) {
activateLink(null);
return;
}
// Falls aktueller Anker direkt existiert
for (var i = 0; i < a.length; i++)
if (a[i]['anchor'] == anchor) {
activateLink(a[i]);
return;
}
// Falls keine Ueberschriften-Anker bekannt
if (currentSiteAnchors.length == 0) {
activateLink(a[0]);
return;
}
// Falls verknuepfter menuLink schon bekannt
if ((currentSiteAnchors.length > anchorPos) && currentSiteAnchors[anchorPos]['link']) {
activateLink(currentSiteAnchors[anchorPos]['link']);
return;
}
// Anker-Rangfolge erstellen
var b = new Array();
var j = anchorPos-1;
var h = currentSiteAnchors[anchorPos]['hNr'];
b[currentSiteAnchors[anchorPos]['hNr']] = currentSiteAnchors[anchorPos];
for (var i = anchorPos-1; i >= 0; i--)
if (currentSiteAnchors[i]['hNr'] < h) {
h = currentSiteAnchors[i]['hNr'];
b[h] = currentSiteAnchors[i];
}
// naheliegensten Menulink aktivieren
for (var i = b.length-1; i >= 0; i--) {
if (!b[i]) continue;
for (var j = 0; j < a.length; j++) if (a[j]['anchor'] == b[i]['name']) { activateLink(a[j]); return; }
}
activateLink(a[0]);
};
var setQueryString = /* private */ function () { // Diese Funktion haengt an die URL der aktuellen Seite das ?UrlVonFrame an.
var P = communicators[current].contentURL;
if (P == '') P = communicators[current].contentHostname;
if (P == '' || P.indexOf('about:blank') == 0) return;
currentSite = getSiteFromURL(P);
if (isNotInWhitelist(P)) {
frames[current].style.visibility = 'hidden';
frames[current].setAttribute('notInWhitelist', 'yes');
} else {
frames[current].style.visibility = 'visible';
frames[current].removeAttribute('notInWhitelist');
}
activateCurrentLink();
var L = self.location.href;
i = L.indexOf('?');
if (i < 0) i = L.length;
var Q = L.substring(i+1, L.length);
L = L.substring(0, i);
i = L.indexOf('#');
if (i > 0) L = L.substring(0, i);
if (P.split('#')[0].toLowerCase() == startPage.toLowerCase()) {
if (P.split('#').length == 2) P = '#'+P.split('#').pop();
else P = '';
} else {
var H = getHostnameAndProtocol(communicators[current].contentURL);
if (H == '') return;
if (selfHostname == H) P = P.substring(H.length, P.length);
if (P == Q) return;
P = '?' + P;
}
if (history.pushState && window.location.href != L+P) {
var stateObj = { foo: "bar" };
var title = '';
try { title = frames[current].contentWindow.document.title; } catch (e) {}
if (oldQuery.split('#')[0] == P.split('#')[0]) history.replaceState(stateObj, document.title+': '+title, L+P);
else history.pushState(stateObj, document.title+': '+title, L+P);
oldQuery = P;
}
};
this.linkJSenhance = function (el, inFrameSite=false) { // replace simple links by javascript-enhanced links
if (inFrameSite) {
if ((el.target == '_self' || el.target == '') && el.href != '') {
var a = el.href.split('#');
if (a[0].indexOf('javascript:') != 0 && a[0].indexOf('callto:') != 0 && a[0].indexOf('mailto:') != 0 && a[0].indexOf('webcal:') != 0 && el.download == '') { // check, this is a common link
if (isNotInWhitelist(el.href)) el.target = '_blank';
else if (a[0] == frames[current].contentWindow.location.href.split('#')[0] && a.length > 1) onEvent(el, 'click', scrollTo, a[1], 3);
else if ((' '+el.className+' ').indexOf(' WindowLink ') == -1) onEvent(el, 'click', loadSite, el.href, 3);
}
}
} else {
if (el.target == aTarget) {
if ((' '+el.className+' ').indexOf(' MenuLink ') >= 0) { // Menue-Links, die hervorgehoben werden sollen, vorbereiten
var href = absolutePath(el.href);
menuLinks.push({ // menuLinks koennen auch in manipulateCurrentSite erzeugt werden!
el : el,
href : href,
anchor : getAnchorFromURL(href),
site : getSiteFromURL(href),
subMenuId : el.getAttribute('submenu'),
active : false
});
}
onEvent(el, 'click', loadSite, el.href, 3);
}
if (el.href.toLowerCase() == 'javascript:'+aTarget.toLowerCase()+'.print()') printLinks.push(el);
}
};
var scanElements = function (el, comm) {
for (var i = 0; i < el.childNodes.length; i++)
if (el.childNodes[i].nodeType == 1) {
var eli = el.childNodes[i];
var tagName = eli.tagName.toLowerCase();
if (tagName == 'a')
linkJSenhance(eli, true);
else if ((/h\d/i).test(tagName)) { // add headlines to currentSiteAnchors
// find or create headline's id
var id = eli.id;
if (!id && eli.getElementsByTagName('a').length > 0) id = eli.getElementsByTagName('a')[0].id;
if (!id) { // auto-generate an id
var txt = eli.textContent.toLowerCase();
id = '';
for (var j = 0; j < txt.length; j++)
if (txt.charAt(j) == ' ' && id != '') id += '_';
else if (txt.charCodeAt(j) >= 97 && txt.charCodeAt(j) <= 122) id += txt.charAt(j);
else if (txt.charCodeAt(j) >= 48 && txt.charCodeAt(j) <= 57 && id != '') id += txt.charAt(j);
else if (txt.charCodeAt(j) == 228) id += 'ae';
else if (txt.charCodeAt(j) == 246) id += 'oe';
else if (txt.charCodeAt(j) == 252) id += 'ue';
else if (txt.charCodeAt(j) == 223) id += 'ss';
var j = 2;
while (document.getElementById(id)) {
id += '_'+j;
j++;
}
eli.id = id;
}
// add headline to current site anchors
currentSiteAnchors.push({
el: eli,
hNr: parseInt(tagName.substring(1)),
id: id,
content: eli.textContent,
site: comm.contentURL.split('#')[0],
pos: getAbsOffsetTop(eli),
link: null
});
}
scanElements(eli, comm);
}
}
var manipulateCurrentSite = /* private */ function (comm) { // wird von iframe.communicator.onload aufgerufen, sobald der Inhalt des frames geladen hat und dessen Hoehe bekannt ist.
var _this = this;
var frameNr = communicators.indexOf(comm);
if (frameNr != current)
return;
debugLog('manipulate frame '+frameNr+'; url='+comm.contentURL);
currentSiteAnchors = new Array();
anchorPos = 0;
try { // Innerhalb von try-catch: Aenderungen an der Frame-Seite
frames[current].contentWindow.document.body.style.overflow = (isAutoFrameHeight)?'auto':'hidden';
if (window.jsw) window.jsw.processSite(frames[current].contentWindow.document.body, jswMainWindow);
scanElements(frames[current].contentWindow.document.body, comm);
} catch (e) {}
// find this site's root menu link (first menu link, that points to this site)
var hirachy = new Array();
hirachy.push(new Object());
hirachy[0]['link'] = null;
hirachy[0]['subMenu'] = null;
for (var i = 0; i < menuLinks.length; i++)
if (menuLinks[i]['site'] == comm.contentURL.split('#')[0]) {
hirachy[0]['link'] = menuLinks[i];
hirachy[0]['subMenu'] = null;
if (menuLinks[i]['el'].getAttribute('submenu'))
hirachy[0]['subMenu'] = document.getElementById(menuLinks[i]['el'].getAttribute('submenu'));
if (!hirachy[0]['subMenu'] && menuLinks[i]['el'].parentElement.tagName.toLowerCase() == 'li') {
var nextEl = menuLinks[i]['el'].parentElement.nextElementSibling;
while (nextEl)
if (nextEl.tagName.toLowerCase() == 'li' || nextEl.tagName.toLowerCase() == 'ol') break;
else nextEl = nextEl.nextElementSibling;
if (nextEl && nextEl.tagName.toLowerCase() == 'ol') hirachy[0]['subMenu'] = nextEl;
else if (autoFillMenu > 0) {
hirachy[0]['subMenu'] = document.createElement('ol');
if (nextEl) menuLinks[i]['el'].parentElement.parentElement.insertBefore(hirachy[0]['subMenu'], nextEl);
else menuLinks[i]['el'].parentElement.parentElement.appendChild(hirachy[0]['subMenu']);
}
}
break;
}
// here, hirachy[0] contains the root link to this site and the destination ol-list for headline anchors. Now expand hirachy towards higher levels
linkBaseSearch:
while (hirachy[0]['link'] && hirachy[0]['link']['el'].parentElement.tagName.toLowerCase() == 'li') {
var ol = hirachy[0]['link']['el'].parentElement.parentElement;
while (ol.tagName.toLowerCase() == 'ol') {
hirachy.unshift({
link: null,
subMenu: ol
});
if (ol.id != '')
for (var i = 0; i < menuLinks.length; i++)
if (menuLinks[i].subMenuId == ol.id) {
hirachy[0].link = menuLinks[i];
continue linkBaseSearch;
}
ol = ol.parentElement;
}
break;
}
// actualize menu with currentSiteAnchors
var h = hirachy.length-1;
if (h >= 0 && hirachy[h]['subMenu'] && currentSiteAnchors.length > 0) {
for (var i = 0; i < currentSiteAnchors.length; i++) { // search or create related links for all anchors (don't do this in try..catch)
// find related menu link to this anchor
var isSingleTopHeadline = (currentSiteAnchors[i]['el'].tagName.toLowerCase() == 'h1') && (currentSiteAnchors[i]['el'].ownerDocument.getElementsByTagName('h1').length == 1);
for (var j = 0; j < menuLinks.length; j++)
if (menuLinks[j]['site'] == comm.contentURL.split('#')[0] && (menuLinks[j]['anchor'] == currentSiteAnchors[i]['id'] || isSingleTopHeadline && menuLinks[j]['anchor'] == '')) {
var pel = menuLinks[j]['el'].parentElement;
if (pel.tagName.toLowerCase() != 'li')
if (isSingleTopHeadline) {
currentSiteAnchors[i]['link'] = menuLinks[j];
currentSiteAnchors[i]['isSingleTopHeadline'] = true;
break;
} else continue; // this link is not a list-element. Ignore!
while (pel)
if (pel == hirachy[0]['subMenu']) break
else pel = pel.parentElement;
if (!pel) continue; // this link is outside of the hirachy-ol and has to remain untouched!
currentSiteAnchors[i]['link'] = menuLinks[j];
break;
}
var link = currentSiteAnchors[i]['link'];
if (link) {
// remove li-element from DOM (maybe add it to an other location, later)
if (link == hirachy[h]['link']) continue; // don't touch root link
link['el'].parentElement.parentElement.removeChild(currentSiteAnchors[i]['link']['el'].parentElement);
} else if (currentSiteAnchors[i]['hNr'] <= autoFillMenu && (!currentSiteAnchors[i]['el'] || currentSiteAnchors[i]['el'].getAttribute('autocompletemenu') !== 'no')) {
// create a new menu link, if missing
link = {
href: comm.contentURL.split('#')[0] + '#' + currentSiteAnchors[i]['id'],
anchor: currentSiteAnchors[i]['id'],
site: comm.contentURL.split('#')[0],
active: false,
el: document.createElement('a')
};
link['el'].textContent = currentSiteAnchors[i]['content'];
link['el'].className = 'MenuLink';
link['el'].target = aTarget;
link['el'].href = link['href'];
onEvent(link['el'], 'click', loadSite, link['href'], 3);
currentSiteAnchors[i]['link'] = link;
document.createElement('li').appendChild(link['el']);
menuLinks.push(link);
}
if (!link) continue;
// insert menu link at correct position, create missing ol-hirachies
if (i == 0 || currentSiteAnchors[i-1]['isSingleTopHeadline'])
hirachy[h]['subMenu'].insertBefore(link['el'].parentElement, hirachy[h]['subMenu'].firstChild);
else {
var hnr = currentSiteAnchors[i-1]['hNr'];
var nextSiblingForLi = currentSiteAnchors[i-1]['el'].parentElement.nextSibling;
// enter necessary sub-menus
while (hnr < currentSiteAnchors[i]['hNr']) {
var newOl = null;
if (hnr == currentSiteAnchors[i-1]['hNr']) {
if (currentSiteAnchors[i-1]['link']['el'].parentElement.nextElementSibling && currentSiteAnchors[i-1]['link']['el'].parentElement.nextElementSibling.tagName.toLowerCase() == 'ol') newOl = currentSiteAnchors[i-1]['link']['el'].parentElement.nextElementSibling;
else {
newOl = document.createElement('ol');
hirachy[h]['subMenu'].insertBefore(newOl, currentSiteAnchors[i-1]['el'].parentElement.nextSibling);
}
} else {
if (hirachy[h]['subMenu'].firstElementChild && hirachy[h]['subMenu'].firstElementChild.tagName.toLowerCase() == 'ol')
newOl = hirachy[h]['subMenu'].firstElementChild;
else {
newOl = document.createElement('ol');
hirachy[h]['subMenu'].insertBefore(newOl, hirachy[h]['subMenu'].firstChild);
}
}
hnr++;
h++;
hirachy.push({
link: null,
subMenu: newOl
});
nextSiblingForLi = newOl.firstChild;
}
// exit unneccessary sub-menus
while (hnr > currentSiteAnchors[i]['hNr'] && h > 0) {
nextSiblingForLi = hirachy[h]['subMenu'].nextSibling;
h--;
hnr--;
hirachy.pop();
}
hirachy[h]['subMenu'].insertBefore(link['el'].parentElement, nextSiblingForLi);
}
}
}
currentHirachy = hirachy;
activateCurrentHirachy();
// Nummerierung
if (nummerateMenu) {
buildMenuNumbers();
for (var i = 0; i < currentSiteAnchors.length; i++)
if (currentSiteAnchors[i]['link'] && currentSiteAnchors[i]['link']['numberEl']) {
var nrEl = currentSiteAnchors[i]['el'].getElementsByTagName('span')[0];
if (!nrEl || (' '+nrEl.className+' ').indexOf(' number ') < 0) {
nrEl = frames[current].contentWindow.document.createElement('span');
currentSiteAnchors[i]['el'].insertBefore(nrEl, currentSiteAnchors[i]['el'].firstChild);
}
nrEl.innerHTML = currentSiteAnchors[i]['link']['numberEl'].innerHTML;
}
}
if (onMenuChange) onMenuChange();
// sonstiges
setQueryString();
blendInfo['currentSiteReady'] = true;
if (onSiteLoaded) {
debugLog('Call onSiteLoaded'+((loadWithoutEffect)?' without effect':''));
onSiteLoaded(frames[current].contentWindow, loadWithoutEffect);
}
loadingSiteAnchor = getAnchorFromURL(communicators[current].contentURL);
if (loadWithoutEffect && loadingSiteAnchor != '') {
debugLog('Try to scroll to #'+loadingSiteAnchor);
scrollTo(loadingSiteAnchor, 2);
} else if (loadingSiteAnchor == '') scrollTo(0, 2);
};
var sitescroll = /* private */ function () { // funktion fuer Scrollevent
if (!autoChangeAnchor || currentSiteAnchors.length == 0 || scrollInfo['scrolling']) return;
// richtigen Anker ermitteln
var pos = window.pageYOffset;
var nr = anchorPos;
while (nr > 0 && currentSiteAnchors[nr]['pos'] > pos + 100) nr--;
while (nr < currentSiteAnchors.length-1 && currentSiteAnchors[nr+1]['pos'] <= pos + 100) nr++;
// Ankerposition testen
if (!currentSiteAnchors[nr]) return; // ToDo: unknown error!
var top = getAbsOffsetTop(currentSiteAnchors[nr]['el']);
if (Math.abs(top - currentSiteAnchors[nr]['pos']) > 5) {
currentSiteAnchors[nr]['pos'] = top;
sitescroll();
return;
}
if (pos < getAbsOffsetTop(frames[current])) nr = -1;
// Anker wechseln
if (nr != anchorPos) {
anchorPos = nr;
var i = frames[current].contentWindow.location.href.lastIndexOf('#');
if (i < 0) i = frames[current].contentWindow.location.href.length;
var stateObj = { foo: "bar" };
var anchorHashtag = '';
if (nr >= 0) anchorHashtag = '#'+currentSiteAnchors[nr]['id'];
frames[current].contentWindow.history.replaceState(stateObj, '', frames[current].contentWindow.location.href.substring(0,i)+anchorHashtag);
communicators[current].newURL(frames[current].contentWindow.location.href.substring(0,i)+anchorHashtag); // Bugfix: Beim reload der Seite werden im Frame die Funktionen replaceState und pushState nicht ueberwacht
}
};
var autoFrameHeight = /* private */ function (auto) {
// wenn true uebergeben wird, wird die Hoehe des iframe-Bereiches auf ~100% der Seitenhoehe (das ClassName 'NoJsHeight' wird wieder an alle Elemente, die dies zu Anfang trugen, angehaengt) angepasst, ansonsten richtet sich die Hoehe nach dem Inhalt des aktuellen frames.
if (auto == isAutoFrameHeight) {
debugLog('Auto frame height already '+((auto)?'activated':'deactivated')+'.');
return;
}
debugLog(((auto)?'Activate':'Deactivate')+' auto frame height...');
isAutoFrameHeight = auto;
for (var i = 0; i < autoHeightEl.length; i++) {
var a = autoHeightEl[i].className.split(' ');
var n = '';
for (var j = 0; j < a.length; j++) {
if (a[j] == 'NoJsHeight') continue;
if (n != '') n += ' ';
n += a[j];
}
autoHeightEl[i].className = n;
}
if (auto) {
for (var i = 0; i < autoHeightEl.length; i++) {
if (autoHeightEl[i].className != '') autoHeightEl[i].className = autoHeightEl[i].className + ' ';
autoHeightEl[i].className = autoHeightEl[i].className + 'NoJsHeight';
}
frames[current].style.height = '100%';
try { frames[current].contentWindow.document.body.style.overflow = 'auto'; } catch (e) { }
} else {
try { frames[current].contentWindow.document.body.style.overflow = 'hidden'; } catch (e) { }
}
};
var siteResize = /* private */ function (nr, h) {
if (nr != current) return;
debugLog('Resize info from frame '+nr+' received. Height = '+h);
if (loadingSiteAnchor != '') scrollTo(loadingSiteAnchor, 2);
if (onContentResize) onContentResize(communicators[current].contentWidth, communicators[current].contentHeight, communicators[current]);
if (blendInfo['blending'] && !(blendInfo['state'] & 2)) return; // kein Resize fuer noch nicht sichtbare Seiten
if (blendInfo['blending'] && isAutoFrameHeight && h >= 0) frames[next].style.height = frames[next].offsetHeight + 'px'; // automatisch gegebene Hoehe einfrieren, bevor autoFrameHeight false wird
autoFrameHeight(h < 0);
};
var titleChange = /* private */ function (title, communicator) {
if (communicator != communicators[current]) return;
if (onTitleChange) onTitleChange(title);
};
var blend = /* private */ function () {
// Loop fuer Einblendeeffekt
if (!blendInfo['timer']) blendInfo['timer'] = window.setInterval(blend, 40);
var t = getTickCount();
// blendState - Flags: 1 = alter Frame wurde ausgeblendet 2 = neue Seite hat geladen und Verzoegerung ist um 4 = neue Seite wurde eingeblendet
if ((blendInfo['state'] & 1) != 1 && !blendInfo['browse']) { // Wenn Flag 1 nicht gesetzt (alte Seite ausblenden)
if (t - blendInfo['start'] < blendOutTime) {
if (blendOutEffect) blendOutEffect(frames[next], (t - blendInfo['start'])/blendOutTime, false);
} else {
blendInfo['state'] += 1;
frames[next].style.display = 'none';
communicators[next].setSrc('about:blank');
if (blendOutEffect) blendOutEffect(frames[next], -1, false);
}
}
if (((blendInfo['state'] & 2) != 2) && (blendInfo['currentSiteReady'] || t - blendInfo['start'] > maxWaitForSite) && (t - blendInfo['start'] > blendInDelay)) { // Wenn Flag 2 nicht gesetzt und Bedingungen fuer Einblenden von neuer Seite erfuellt (neue Seite anzeigen)
if (blendInEffect && !blendInfo['browse']) blendInEffect(frames[current], 0, true);
else if (browseEffect && blendInfo['browse']) browseEffect(frames[current], frames[next], 0, blendInfo['dir']);
frames[current].style.maxHeight = 'none';
blendInfo['startDelay'] = t;
blendInfo['state'] += 2;
siteResize(current, communicators[current].contentHeight);
if (blendInfo['browse']) {
if (blendInfo['dir']) loadingSiteAnchor = communicators[current].contentHeight + frames[current].parentElement.offsetTop - window.innerHeight;
else loadingSiteAnchor = 0;
}
if (blendInfo['browse']) scrollTo(loadingSiteAnchor, 5);
else if (loadingSiteAnchor != '') scrollTo(loadingSiteAnchor, 1);
}
if (((blendInfo['state'] & 4) != 4) && (blendInfo['state'] & 2)) { // wenn Flag 2 gesetzt aber Flag 4 noch nicht (neue Seite einblenden)
if (blendInfo['browse']) {
if (t - blendInfo['startDelay'] < browseDuration) {
if (browseEffect) browseEffect(frames[current], frames[next], (t-blendInfo['startDelay'])/browseDuration, blendInfo['dir']);
} else {
blendInfo['state'] |= 5;
frames[next].style.display = 'none';
communicators[next].setSrc('about:blank');
if (browseEffect) browseEffect(frames[current], frames[next], -1, blendInfo['dir']);
}
} else {
if (t - blendInfo['startDelay'] < blendInTime) {
if (blendInEffect) blendInEffect(frames[current], (t - blendInfo['startDelay'])/blendInTime, true);
} else {
blendInfo['state'] |= 4;
if (blendInEffect) blendInEffect(frames[current], -1, true);
}
}
}
if ((blendInfo['state'] & 5) == 5) { // wenn Flag 1 und 4 gesetzt (Blendvorgang beenden)
if (blendInfo['timer']) window.clearInterval(blendInfo['timer']);
blendInfo['timer'] = null;
blendInfo['blending'] = false;
}
};
this.loadSite = /* public */ function (src, behavior) { // behavior : 0=normal, 1=kein Effekt, 2=blaettern, 4=nach oben blaettern
if (!behavior) behavior = 0;
if (incompatibleBrowser || src == 'about:blank') {
debugLog('Incompatible site load blocked: '+src);
return;
}
src = absolutePath(src);
if (isNotInWhitelist(src)) {
debugLog('Not whitelisted site blocked: '+src);
return;
}
if (!domWasReady && behavior == 1) {
this.scriptLoadingPage = src;
return;
}
// Wenn neue Seite gleich aktueller Seite, nur scrollTo ausfuehren
if (getSiteFromURL(src) == currentSite) {
if (~behavior & 2) scrollTo(getAnchorFromURL(src));
debugLog('Loading of site blocked, because it already is loaded! ('+src+')');
return;
}
// Einstellungen
loadWithoutEffect = (behavior & 1) == 1;
if (loadWithoutEffect) debugLog('Load without effect');
else debugLog('Load with effect');
currentSiteAnchors = new Array();
anchorPos = 0;
// Wenn Effekt aktiv, nur neue Seite in aktuellen Frame laden
if (blendInfo['blending']) {
debugLog('Replacing url of currently loading site in frame '+current+' with '+src);
communicators[current].setSrc(src);
return;
}
// current und next wechseln
current = next;
next = (current == 0)?1:0;
// neue Seite laden
if (!loadWithoutEffect) {
blendInfo['currentSiteReady'] = false;
blendInfo['blending'] = true;
}
debugLog('Frame '+current+' starts loading of '+src+' | behavior = '+behavior);
communicators[current].setSrc(src);
// PrintLinks umstellen
if (current == 0)
for (var i = 0; i < printLinks.length; i++)
printLinks[i].href = 'javascript:'+aTarget+'.print()';
else
for (var i = 0; i < printLinks.length; i++)
printLinks[i].href = 'javascript:'+aTarget2+'.print()';
// Blenduebergang starten
if (loadWithoutEffect) {
frames[next].style.display = 'none';
frames[current].style.display = 'block';
} else {
frames[current].style.maxHeight = '0px';
frames[current].style.display = 'block'; // display = block ist notwendig, damit Seite im Frame beginnt zu laden!
blendInfo['state'] = 0;
blendInfo['browse'] = (behavior & 2) == 2;
blendInfo['dir'] = (behavior & 4) == 4;
blendInfo['start'] = getTickCount();
blend();
}
};
var scroll = /* private */ function () {
// Loop fuer Scroll-Effekt
if (!scrollInfo['timer']) scrollInfo['timer'] = window.setInterval(scroll, 40);
var t = getTickCount();
var d = scrollInfo['blendEffectTime'];
if (scrollInfo['start'] + d > t) {
var s = (t - scrollInfo['start']) / d;
s = scrollInfo['fkt'](s);
s = scrollInfo['startPos'] * (1-s) + scrollInfo['endPos'] * s;
window.scrollTo(window.pageXOffset, s);
} else {
scrollInfo['scrolling'] = false;
if (scrollInfo['timer']) window.clearInterval(scrollInfo['timer'], 40);
scrollInfo['timer'] = null;
window.scrollTo(window.pageXOffset, scrollInfo['endPos']);
}
};
this.scrollTo = /* public */ function (target, scrollbehavior) { // scrollbehavior: 0=normal, 1=scrollt so lange, wie Einblendeeffekt, 2=springt sofort zu neuer Position (kein Effekt), 4=scrollFkt = browseScrollFkt
if (!scrollbehavior) scrollbehavior = 0;
if (incompatibleBrowser) return;
if (scrollbehavior & 4) scrollInfo['fkt'] = browseScrollFunction;
else scrollInfo['fkt'] = scrollFunction;
// Scrollziel suchen
var pos = 0;
if (!isNaN(target)) pos = target;
else {
try {
var el = frames[current].contentWindow.document.getElementById(target);
if (!el) el = frames[current].contentWindow.document.getElementsByName(target)[0];
if (el) pos = getAbsOffsetTop(el);
} catch (e) { return; }
try {
var L = frames[current].contentWindow.location.href;
var i = L.lastIndexOf('#');
if (i > 0 && L.lastIndexOf('?') < i) L = L.substring(0, i);
var stateObj = { foo: "bar" };
if (frames[current].contentWindow.location.href != L+'#'+target)
frames[current].contentWindow.history.replaceState(stateObj, document.title+': '+frames[current].contentWindow.document.title, L+'#'+target);
} catch (e) { }
}
// Scrollziel setzen
if (loadingSiteAnchor != '' && getAbsOffsetTop(frames[current]) + frames[current].offsetHeight >= pos) {
debugLog('Anchor of loading site is reachable.');
loadingSiteAnchor = '';
}
scrollInfo['endPos'] = pos;
if (scrollInfo['scrolling']) return;
// Scrolleffekt starten
scrollInfo['scrolling'] = true;
scrollInfo['start'] = getTickCount();
if (scrollbehavior & 2) scrollInfo['start'] = 0;
scrollInfo['startPos'] = window.pageYOffset;
scrollInfo['blendEffectTime'] = scrollTime;
if (scrollbehavior & 1) scrollInfo['blendEffectTime'] = blendInTime;
if (scrollbehavior & 5 == 5) scrollInfo['blendEffectTime'] = browseDuration;
scroll();
return;
};
this.enableOverscroll = function () {
if (overscrollListener) return;
overscrollListener = createOverscrollListener(document, 1000, function (dir) {
for (var i = 0; i < menuLinks.length; i++) if (menuLinks[i]['active']) {
if (dir && i < menuLinks.length - 1) { loadSite(menuLinks[i+1].href, 2); activateLink(menuLinks[i+1]); }
else if (!dir && i > 0) { loadSite(menuLinks[i-1].href, 6); activateLink(menuLinks[i-1]); }
return;
}
});
};
this.getActiveContentWindow = function () {
return frames[current].contentWindow;
};
this.getActiveCommunicator = function () {
return communicators[current];
};
create(); // Objekt sollte vollstaendig geladen sein, bevor Create aufgerufen wird
} }