altinn-designsystem
Version:
Altinn Design system based on Pattern Lab.
623 lines (491 loc) • 21.8 kB
JavaScript
/*!
* Basic postMessage Support
*
* Copyright (c) 2013-2016 Dave Olsen, http://dmolsen.com
* Licensed under the MIT license
*
* Handles the postMessage stuff in the pattern, view-all, and style guide templates.
*
*/
// alert the iframe parent that the pattern has loaded assuming this view was loaded in an iframe
if (self != top) {
// handle the options that could be sent to the parent window
// - all get path
// - pattern & view all get a pattern partial, styleguide gets all
// - pattern shares lineage
var path = window.location.toString();
var parts = path.split("?");
var options = { "event": "patternLab.pageLoad", "path": parts[0] };
patternData = document.getElementById('sg-pattern-data-footer').innerHTML;
patternData = JSON.parse(patternData);
options.patternpartial = (patternData.patternPartial !== undefined) ? patternData.patternPartial : "all";
if (patternData.lineage !== "") {
options.lineage = patternData.lineage;
}
var targetOrigin = (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host;
parent.postMessage(options, targetOrigin);
// find all links and add an onclick handler for replacing the iframe address so the history works
var aTags = document.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
aTags[i].onclick = function(e) {
var href = this.getAttribute("href");
var target = this.getAttribute("target");
if ((target !== undefined) && ((target == "_parent") || (target == "_blank"))) {
// just do normal stuff
} else if (href && href !== "#") {
e.preventDefault();
window.location.replace(href);
} else {
e.preventDefault();
return false;
}
};
}
}
// watch the iframe source so that it can be sent back to everyone else.
function receiveIframeMessage(event) {
// does the origin sending the message match the current host? if not dev/null the request
if ((window.location.protocol != "file:") && (event.origin !== window.location.protocol+"//"+window.location.host)) {
return;
}
var path;
var data = {};
try {
data = (typeof event.data !== 'string') ? event.data : JSON.parse(event.data);
} catch(e) {}
if ((data.event !== undefined) && (data.event == "patternLab.updatePath")) {
if (patternData.patternPartial !== undefined) {
// handle patterns and the view all page
var re = /(patterns|snapshots)\/(.*)$/;
path = window.location.protocol+"//"+window.location.host+window.location.pathname.replace(re,'')+data.path+'?'+Date.now();
window.location.replace(path);
} else {
// handle the style guide
path = window.location.protocol+"//"+window.location.host+window.location.pathname.replace("styleguide\/html\/styleguide.html","")+data.path+'?'+Date.now();
window.location.replace(path);
}
} else if ((data.event !== undefined) && (data.event == "patternLab.reload")) {
// reload the location if there was a message to do so
window.location.reload();
}
}
window.addEventListener("message", receiveIframeMessage, false);
/*!
* URL Handler
*
* Copyright (c) 2013-2014 Dave Olsen, http://dmolsen.com
* Licensed under the MIT license
*
* Helps handle the initial iFrame source. Parses a string to see if it matches
* an expected pattern in Pattern Lab. Supports Pattern Labs fuzzy pattern partial
* matching style.
*
*/
var urlHandler = {
// set-up some default vars
skipBack: false,
targetOrigin: (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host,
/**
* get the real file name for a given pattern name
* @param {String} the shorthand partials syntax for a given pattern
* @param {Boolean} with the file name should be returned with the full rendered suffix or not
*
* @return {String} the real file path
*/
getFileName: function (name, withRenderedSuffix) {
var baseDir = "patterns";
var fileName = "";
if (name === undefined) {
return fileName;
}
if (withRenderedSuffix === undefined) {
withRenderedSuffix = true;
}
if (name == "all") {
return "styleguide/html/styleguide.html";
} else if (name == "snapshots") {
return "snapshots/index.html";
}
var paths = (name.indexOf("viewall-") != -1) ? viewAllPaths : patternPaths;
var nameClean = name.replace("viewall-","");
// look at this as a regular pattern
var bits = this.getPatternInfo(nameClean, paths);
var patternType = bits[0];
var pattern = bits[1];
if ((paths[patternType] !== undefined) && (paths[patternType][pattern] !== undefined)) {
fileName = paths[patternType][pattern];
} else if (paths[patternType] !== undefined) {
for (var patternMatchKey in paths[patternType]) {
if (patternMatchKey.indexOf(pattern) != -1) {
fileName = paths[patternType][patternMatchKey];
break;
}
}
}
if (fileName === "") {
return fileName;
}
var regex = /\//g;
if ((name.indexOf("viewall-") !== -1) && (name.indexOf("viewall-") === 0) && (fileName !== "")) {
fileName = baseDir+"/"+fileName.replace(regex,"-")+"/index.html";
} else if (fileName !== "") {
fileName = baseDir+"/"+fileName.replace(regex,"-")+"/"+fileName.replace(regex,"-");
if (withRenderedSuffix) {
var fileSuffixRendered = ((config.outputFileSuffixes !== undefined) && (config.outputFileSuffixes.rendered !== undefined)) ? config.outputFileSuffixes.rendered : '';
fileName = fileName+fileSuffixRendered+".html";
}
}
return fileName;
},
/**
* break up a pattern into its parts, pattern type and pattern name
* @param {String} the shorthand partials syntax for a given pattern
* @param {Object} the paths to be compared
*
* @return {Array} the pattern type and pattern name
*/
getPatternInfo: function (name, paths) {
var patternBits = name.split("-");
var i = 1;
var c = patternBits.length;
var patternType = patternBits[0];
while ((paths[patternType] === undefined) && (i < c)) {
patternType += "-"+patternBits[i];
i++;
}
var pattern = name.slice(patternType.length+1,name.length);
return [patternType, pattern];
},
/**
* search the request vars for a particular item
*
* @return {Object} a search of the window.location.search vars
*/
getRequestVars: function() {
// the following is taken from https://developer.mozilla.org/en-US/docs/Web/API/window.location
var oGetVars = new (function (sSearch) {
if (sSearch.length > 1) {
for (var aItKey, nKeyId = 0, aCouples = sSearch.substr(1).split("&"); nKeyId < aCouples.length; nKeyId++) {
aItKey = aCouples[nKeyId].split("=");
this[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : "";
}
}
})(window.location.search);
return oGetVars;
},
/**
* push a pattern onto the current history based on a click
* @param {String} the shorthand partials syntax for a given pattern
* @param {String} the path given by the loaded iframe
*/
pushPattern: function (pattern, givenPath) {
var data = { "pattern": pattern };
var fileName = urlHandler.getFileName(pattern);
var path = window.location.pathname;
path = (window.location.protocol === "file") ? path.replace("/public/index.html","public/") : path.replace(/\/index\.html/,"/");
var expectedPath = window.location.protocol+"//"+window.location.host+path+fileName;
if (givenPath != expectedPath) {
// make sure to update the iframe because there was a click
var obj = JSON.stringify({ "event": "patternLab.updatePath", "path": fileName });
document.getElementById("sg-viewport").contentWindow.postMessage(obj, urlHandler.targetOrigin);
} else {
// add to the history
var addressReplacement = (window.location.protocol == "file:") ? null : window.location.protocol+"//"+window.location.host+window.location.pathname.replace("index.html","")+"?p="+pattern;
if (history.pushState !== undefined) {
history.pushState(data, null, addressReplacement);
}
document.getElementById("title").innerHTML = "Pattern Lab - "+pattern;
if (document.getElementById("sg-raw") !== null) {
document.getElementById("sg-raw").setAttribute("href",urlHandler.getFileName(pattern));
}
}
},
/**
* based on a click forward or backward modify the url and iframe source
* @param {Object} event info like state and properties set in pushState()
*/
popPattern: function (e) {
var patternName;
var state = e.state;
if (state === null) {
this.skipBack = false;
return;
} else if (state !== null) {
patternName = state.pattern;
}
var iFramePath = "";
iFramePath = this.getFileName(patternName);
if (iFramePath === "") {
iFramePath = "styleguide/html/styleguide.html";
}
var obj = JSON.stringify({ "event": "patternLab.updatePath", "path": iFramePath });
document.getElementById("sg-viewport").contentWindow.postMessage( obj, urlHandler.targetOrigin);
document.getElementById("title").innerHTML = "Pattern Lab - "+patternName;
if (document.getElementById("sg-raw") !== null) {
document.getElementById("sg-raw").setAttribute("href",urlHandler.getFileName(patternName));
}
/*
if (wsnConnected !== undefined) {
wsn.send( '{"url": "'+iFramePath+'", "patternpartial": "'+patternName+'" }' );
}
*/
}
};
/**
* handle the onpopstate event
*/
window.onpopstate = function (event) {
urlHandler.skipBack = true;
urlHandler.popPattern(event);
};
/*!
* Panels Util
* For both styleguide and viewer
*
* Copyright (c) 2013-16 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com
* Licensed under the MIT license
*
* @requires url-handler.js
*
*/
var panelsUtil = {
/**
* Add click events to the template that was rendered
* @param {String} the rendered template for the modal
* @param {String} the pattern partial for the modal
*/
addClickEvents: function(templateRendered, patternPartial) {
var els = templateRendered.querySelectorAll('#sg-'+patternPartial+'-tabs li');
for (var i = 0; i < els.length; ++i) {
els[i].onclick = function(e) {
e.preventDefault();
var patternPartial = this.getAttribute('data-patternpartial');
var panelID = this.getAttribute('data-panelid');
panelsUtil.show(patternPartial, panelID);
};
}
return templateRendered;
},
/**
* Show a specific modal
* @param {String} the pattern partial for the modal
* @param {String} the ID of the panel to be shown
*/
show: function(patternPartial, panelID) {
var els;
// turn off all of the active tabs
els = document.querySelectorAll('#sg-'+patternPartial+'-tabs li');
for (i = 0; i < els.length; ++i) {
els[i].classList.remove('sg-tab-title-active');
}
// hide all of the panels
els = document.querySelectorAll('#sg-'+patternPartial+'-panels div.sg-tabs-panel');
for (i = 0; i < els.length; ++i) {
els[i].style.display = 'none';
}
// add active tab class
document.getElementById('sg-'+patternPartial+'-'+panelID+'-tab').classList.add('sg-tab-title-active');
// show the panel
document.getElementById('sg-'+patternPartial+'-'+panelID+'-panel').style.display = 'flex';
}
};
/*!
* Modal for the Styleguide Layer
* For both annotations and code/info
*
* Copyright (c) 2016 Dave Olsen, http://dmolsen.com
* Licensed under the MIT license
*
* @requires panels-util.js
* @requires url-handler.js
*
*/
var modalStyleguide = {
// set up some defaults
active: [ ],
targetOrigin: (window.location.protocol === 'file:') ? '*' : window.location.protocol+'//'+window.location.host,
/**
* initialize the modal window
*/
onReady: function() {
// go through the panel toggles and add click event
var els = document.querySelectorAll('.sg-pattern-extra-toggle');
for (var i = 0; i < els.length; ++i) {
els[i].onclick = (function(e) {
e.preventDefault();
var patternPartial = this.getAttribute('data-patternpartial');
modalStyleguide.toggle(patternPartial);
});
}
},
/**
* toggle the modal window open and closed based on clicking the pip
* @param {String} the patternPartial that identifies what needs to be toggled
*/
toggle: function(patternPartial) {
if ((modalStyleguide.active[patternPartial] === undefined) || !modalStyleguide.active[patternPartial]) {
var el = document.getElementById('sg-pattern-data-'+patternPartial);
modalStyleguide.collectAndSend(el, true, false);
} else {
modalStyleguide.highlightsHide();
modalStyleguide.close(patternPartial);
}
},
/**
* open the modal window for a view-all entry
* @param {String} the patternPartial that identifies what needs to be opened
* @param {String} the content that should be inserted
*/
open: function(patternPartial, content) {
// make sure templateRendered is modified to be an HTML element
var div = document.createElement('div');
div.innerHTML = content;
content = document.createElement('div').appendChild(div).querySelector('div');
// add click events
content = panelsUtil.addClickEvents(content, patternPartial);
// make sure the modal viewer and other options are off just in case
modalStyleguide.close(patternPartial);
// note it's turned on in the viewer
modalStyleguide.active[patternPartial] = true;
// make sure there's no content
div = document.getElementById('sg-pattern-extra-'+patternPartial);
if (div.childNodes.length > 0) {
div.removeChild(div.childNodes[0]);
}
// add the content
document.getElementById('sg-pattern-extra-'+patternPartial).appendChild(content);
// show the modal
document.getElementById('sg-pattern-extra-toggle-'+patternPartial).classList.add('active');
document.getElementById('sg-pattern-extra-'+patternPartial).classList.add('active');
},
/**
* close the modal window for a view-all entry
* @param {String} the patternPartial that identifies what needs to be closed
*/
close: function(patternPartial) {
// not that the modal viewer is no longer active
modalStyleguide.active[patternPartial] = false;
// hide the modal, look at info-panel.js
document.getElementById('sg-pattern-extra-toggle-'+patternPartial).classList.remove('active');
document.getElementById('sg-pattern-extra-'+patternPartial).classList.remove('active');
},
/**
* get the data that needs to be send to the viewer for rendering
* @param {Element} the identifier for the element that needs to be collected
* @param {Boolean} if the refresh is of a view-all view and the content should be sent back
* @param {Boolean} if the text in the dropdown should be switched
*/
collectAndSend: function(el, iframePassback, switchText) {
var patternData = JSON.parse(el.innerHTML);
if (patternData.patternName !== undefined) {
patternMarkupEl = document.querySelector('#'+patternData.patternPartial+' > .sg-pattern-example');
patternData.patternMarkup = (patternMarkupEl !== null) ? patternMarkupEl.innerHTML : document.querySelector('body').innerHTML;
modalStyleguide.patternQueryInfo(patternData, iframePassback, switchText);
}
},
/**
* hide the highlights
*/
highlightsHide: function(patternPartial) {
var patternPartialSelector = (patternPartial !== undefined) ? '#'+patternPartial+" > " : "";
elsToHide = document.querySelectorAll(patternPartialSelector+'.has-annotation');
for (i = 0; i < elsToHide.length; i++) {
elsToHide[i].classList.remove('has-annotation');
}
elsToHide = document.querySelectorAll(patternPartialSelector+'.annotation-tip');
for (i = 0; i < elsToHide.length; i++) {
elsToHide[i].style.display = 'none';
}
},
/**
* return the pattern info to the top level
* @param {Object} the content that will be sent to the viewer for rendering
* @param {Boolean} if the refresh is of a view-all view and the content should be sent back
* @param {Boolean} if the text in the dropdown should be switched
*/
patternQueryInfo: function(patternData, iframePassback, switchText) {
// send a message to the pattern
try {
var obj = JSON.stringify({ 'event': 'patternLab.patternQueryInfo', 'patternData': patternData, 'iframePassback': iframePassback, 'switchText': switchText});
parent.postMessage(obj, modalStyleguide.targetOrigin);
} catch(e) {}
},
/**
* toggle the comment pop-up based on a user clicking on the pattern
* based on the great MDN docs at https://developer.mozilla.org/en-US/docs/Web/API/window.postMessage
* @param {Object} event info
*/
receiveIframeMessage: function(event) {
var i;
// does the origin sending the message match the current host? if not dev/null the request
if ((window.location.protocol !== 'file:') && (event.origin !== window.location.protocol+'//'+window.location.host)) {
return;
}
var data = {};
try {
data = (typeof event.data !== 'string') ? event.data : JSON.parse(event.data);
} catch(e) {}
// see if it got a path to replace
if ((data.event !== undefined) && (data.event == 'patternLab.patternQuery')) {
var els, iframePassback, patternData, patternMarkupEl;
// find all elements related to pattern info
els = document.querySelectorAll('.sg-pattern-data');
iframePassback = (els.length > 1);
// send each up to the parent to be read and compiled into panels
for (i = 0; i < els.length; i++) {
modalStyleguide.collectAndSend(els[i], iframePassback, data.switchText);
}
} else if ((data.event !== undefined) && (data.event == 'patternLab.patternModalInsert')) {
// insert the previously rendered content being passed from the iframe
modalStyleguide.open(data.patternPartial, data.modalContent);
} else if ((data.event !== undefined) && (data.event == 'patternLab.annotationsHighlightShow')) {
var elsToHighlight, j, item, span;
// go over the supplied annotations
for (i = 0; i < data.annotations.length; i++) {
item = data.annotations[i];
elsToHighlight = document.querySelectorAll(item.el);
if (elsToHighlight.length > 0) {
for (j = 0; j < elsToHighlight.length; j++) {
elsToHighlight[j].classList.add('has-annotation');
span = document.createElement('span');
span.innerHTML = item.displayNumber;
span.classList.add('annotation-tip');
if (window.getComputedStyle(elsToHighlight[j],null).getPropertyValue('max-height') == '0px') {
span.style.display = 'none';
}
annotationTip = document.querySelector(item.el+' > span.annotation-tip');
if (annotationTip === null) {
elsToHighlight[j].insertBefore(span,elsToHighlight[j].firstChild);
} else {
annotationTip.style.display = 'inline';
}
elsToHighlight[j].onclick = (function(item) {
return function(e) {
e.preventDefault();
e.stopPropagation();
var obj = JSON.stringify({'event': 'patternLab.annotationNumberClicked', 'displayNumber': item.displayNumber });
parent.postMessage(obj, modalStyleguide.targetOrigin);
};
})(item);
}
}
}
} else if ((data.event !== undefined) && (data.event == 'patternLab.annotationsHighlightHide')) {
modalStyleguide.highlightsHide();
} else if ((data.event !== undefined) && (data.event == 'patternLab.patternModalClose')) {
var keys = [];
for (var k in modalStyleguide.active) {
keys.push(k);
}
for (i = 0; i < keys.length; i++) {
var patternPartial = keys[i];
if (modalStyleguide.active[patternPartial]) {
modalStyleguide.close(patternPartial);
}
}
}
}
};
// when the document is ready make sure the modal is ready
modalStyleguide.onReady();
window.addEventListener('message', modalStyleguide.receiveIframeMessage, false);