UNPKG

altinn-designsystem

Version:

Altinn Design system based on Pattern Lab.

1,556 lines (1,227 loc) 75 kB
/*! * jQuery Cookie Plugin v1.3 * https://github.com/carhartl/jquery-cookie * * Copyright 2011, Klaus Hartl * Dual licensed under the MIT or GPL Version 2 licenses. * http://www.opensource.org/licenses/mit-license.php * http://www.opensource.org/licenses/GPL-2.0 */ (function ($, document, undefined) { var pluses = /\+/g; function raw(s) { return s; } function decoded(s) { return decodeURIComponent(s.replace(pluses, ' ')); } var config = $.cookie = function (key, value, options) { // write if (value !== undefined) { options = $.extend({}, config.defaults, options); if (value === null) { options.expires = -1; } if (typeof options.expires === 'number') { var days = options.expires, t = options.expires = new Date(); t.setDate(t.getDate() + days); } value = config.json ? JSON.stringify(value) : String(value); return (document.cookie = [ encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value), options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', options.secure ? '; secure' : '' ].join('')); } // read var decode = config.raw ? raw : decoded; var cookies = document.cookie.split('; '); for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); if (decode(parts.shift()) === key) { var cookie = decode(parts.join('=')); return config.json ? JSON.parse(cookie) : cookie; } } return null; }; config.defaults = {}; $.removeCookie = function (key, options) { if ($.cookie(key) !== null) { $.cookie(key, null, options); return true; } return false; }; })(jQuery, document); /*! * Data Saver * * Copyright (c) 2013-2014 Dave Olsen, http://dmolsen.com * Licensed under the MIT license */ var DataSaver = { // the name of the cookie to store the data in cookieName: "patternlab", /** * Add a given value to the cookie * @param {String} the name of the key * @param {String} the value */ addValue: function (name,val) { var cookieVal = $.cookie(this.cookieName); cookieVal = ((cookieVal === null) || (cookieVal === "")) ? name+"~"+val : cookieVal+"|"+name+"~"+val; $.cookie(this.cookieName,cookieVal); }, /** * Update a value found in the cookie. If the key doesn't exist add the value * @param {String} the name of the key * @param {String} the value */ updateValue: function (name,val) { if (this.findValue(name)) { var updateCookieVals = ""; var cookieVals = $.cookie(this.cookieName).split("|"); for (var i = 0; i < cookieVals.length; i++) { var fieldVals = cookieVals[i].split("~"); if (fieldVals[0] == name) { fieldVals[1] = val; } updateCookieVals += (i > 0) ? "|"+fieldVals[0]+"~"+fieldVals[1] : fieldVals[0]+"~"+fieldVals[1]; } $.cookie(this.cookieName,updateCookieVals); } else { this.addValue(name,val); } }, /** * Remove the given key * @param {String} the name of the key */ removeValue: function (name) { var updateCookieVals = ""; var cookieVals = $.cookie(this.cookieName).split("|"); var k = 0; for (var i = 0; i < cookieVals.length; i++) { var fieldVals = cookieVals[i].split("~"); if (fieldVals[0] != name) { updateCookieVals += (k === 0) ? fieldVals[0]+"~"+fieldVals[1] : "|"+fieldVals[0]+"~"+fieldVals[1]; k++; } } $.cookie(this.cookieName,updateCookieVals); }, /** * Find the value using the given key * @param {String} the name of the key * * @return {String} the value of the key or false if the value isn't found */ findValue: function (name) { if ($.cookie(this.cookieName)) { var cookieVals = $.cookie(this.cookieName).split("|"); for (var i = 0; i < cookieVals.length; i++) { var fieldVals = cookieVals[i].split("~"); if (fieldVals[0] == name) { return fieldVals[1]; } } } return false; } }; /*! * Simple Layout Rendering for Pattern Lab * * Copyright (c) 2014 Dave Olsen, http://dmolsen.com * Licensed under the MIT license */ try { /* load pattern nav */ var template = document.getElementById("pl-pattern-nav-template"); var templateCompiled = Hogan.compile(template.innerHTML); var templateRendered = templateCompiled.render(navItems); document.getElementById("pl-pattern-nav-target").innerHTML = templateRendered; /* load ish controls */ var template = document.getElementById("pl-ish-controls-template"); var templateCompiled = Hogan.compile(template.innerHTML); var templateRendered = templateCompiled.render(ishControls); document.getElementById("sg-controls").innerHTML = templateRendered; } catch(e) { var message = "<h1>Nothing Here Yet</h1><p>Please generate your site before trying to view it.</p>"; document.getElementById("pl-pattern-nav-target").innerHTML = message; } /*! * 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); }; /*! * Modal for the Viewer Layer * For both annotations and code/info * * Copyright (c) 2016 Dave Olsen, http://dmolsen.com * Licensed under the MIT license * * @requires url-handler.js * @requires data-saver.js * */ var modalViewer = { // set up some defaults active: false, switchText: true, template: 'info', patternData: {}, targetOrigin: (window.location.protocol === 'file:') ? '*' : window.location.protocol+'//'+window.location.host, /** * initialize the modal window */ onReady: function() { // make sure the listener for checkpanels is set-up Dispatcher.addListener('insertPanels', modalViewer.insert); // watch for resizes and hide the modal container as appropriate when the modal is already hidden $(window).on('resize', function() { if (DataSaver.findValue('modalActive') === 'false') { modalViewer.slide($('#sg-modal-container').outerHeight()); } }); // add the info/code panel onclick handler $('#sg-t-patterninfo').click(function(e) { e.preventDefault(); $('#sg-tools-toggle').removeClass('active'); $(this).parents('ul').removeClass('active'); modalViewer.toggle(); }); // make sure the close button handles the click $('#sg-modal-close-btn').on('click', function(e) { e.preventDefault(); // hide any open annotations obj = JSON.stringify({ 'event': 'patternLab.annotationsHighlightHide' }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, modalViewer.targetOrigin); // hide the viewer modalViewer.close(); }); // see if the modal is already active, if so update attributes as appropriate if (DataSaver.findValue('modalActive') === 'true') { modalViewer.active = true; $('#sg-t-patterninfo').html("Hide Pattern Info"); } // make sure the modal viewer is not viewable, it's alway hidden by default. the pageLoad event determines when it actually opens modalViewer.hide(); // review the query strings in case there is something the modal viewer is supposed to handle by default var queryStringVars = urlHandler.getRequestVars(); // show the modal if code view is called via query string if ((queryStringVars.view !== undefined) && ((queryStringVars.view === 'code') || (queryStringVars.view === 'c'))) { modalViewer.queryPattern(); } // show the modal if the old annotations view is called via query string if ((queryStringVars.view !== undefined) && ((queryStringVars.view === 'annotations') || (queryStringVars.view === 'a'))) { modalViewer.queryPattern(); } }, /** * toggle the modal window open and closed */ toggle: function() { if (modalViewer.active === false) { modalViewer.queryPattern(); } else { obj = JSON.stringify({ 'event': 'patternLab.annotationsHighlightHide' }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, modalViewer.targetOrigin); modalViewer.close(); } }, /** * open the modal window */ open: function() { // make sure the modal viewer and other options are off just in case modalViewer.close(); // note it's turned on in the viewer DataSaver.updateValue('modalActive', 'true'); modalViewer.active = true; // add an active class to the button that matches this template $('#sg-t-'+modalViewer.template+' .sg-checkbox').addClass('active'); //Add active class to modal $('#sg-modal-container').addClass('active'); // show the modal modalViewer.show(); }, /** * close the modal window */ close: function() { var obj; // not that the modal viewer is no longer active DataSaver.updateValue('modalActive', 'false'); modalViewer.active = false; //Add active class to modal $('#sg-modal-container').removeClass('active'); // remove the active class from all of the checkbox items $('.sg-checkbox').removeClass('active'); // hide the modal modalViewer.hide(); // update the wording $('#sg-t-patterninfo').html("Show Pattern Info"); // tell the styleguide to close obj = JSON.stringify({ 'event': 'patternLab.patternModalClose' }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, modalViewer.targetOrigin); }, /** * hide the modal window, add 30px to account for the X box */ hide: function() { modalViewer.slide($('#sg-modal-container').outerHeight()+30); }, /** * insert the copy for the modal window. if it's meant to be sent back to the iframe do do * @param {String} the rendered template that should be inserted * @param {String} the patternPartial that the rendered template is related to * @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 */ insert: function(templateRendered, patternPartial, iframePassback, switchText) { if (iframePassback) { // send a message to the pattern var obj = JSON.stringify({ 'event': 'patternLab.patternModalInsert', 'patternPartial': patternPartial, 'modalContent': templateRendered.outerHTML }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, modalViewer.targetOrigin); } else { // insert the panels and open the viewer $('#sg-modal-content').html(templateRendered); modalViewer.open(); } // update the wording unless this is a default viewall opening if (switchText === true) { $('#sg-t-patterninfo').html("Hide Pattern Info"); } }, /** * refresh the modal if a new pattern is loaded and the modal is active * @param {Object} the patternData sent back from the query * @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 */ refresh: function(patternData, iframePassback, switchText) { // if this is a styleguide view close the modal if (iframePassback) { modalViewer.hide(); } // gather the data that will fill the modal window panelsViewer.gatherPanels(patternData, iframePassback, switchText); }, /** * slides the modal window into or out of view * @param {Integer} where the modal window should be slide to */ slide: function(pos) { pos = (pos === 0) ? 0 : -pos; $('#sg-modal-container').css('bottom',pos); }, /** * slides the modal window to a particular annotation * @param {Integer} the number for the element that should be highlighted */ slideToAnnotation: function(pos) { // remove active class els = document.querySelectorAll('#sg-annotations > .sg-annotations-list > li'); for (i = 0; i < els.length; ++i) { els[i].classList.remove('active'); } // add active class to called element and scroll to it for (i = 0; i < els.length; ++i) { if ((i+1) == pos) { els[i].classList.add('active'); $('.sg-pattern-extra-info').animate({scrollTop: els[i].offsetTop - 10}, 600); } } }, /** * alias for slide */ show: function() { modalViewer.slide(0); }, /** * ask the pattern for info so we can open the modal window and populate it * @param {Boolean} if the dropdown text should be changed */ queryPattern: function(switchText) { // note that the modal is active and set switchText if ((switchText === undefined) || (switchText)) { switchText = true; DataSaver.updateValue('modalActive', 'true'); modalViewer.active = true; } // send a message to the pattern var obj = JSON.stringify({ 'event': 'patternLab.patternQuery', 'switchText': switchText }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, modalViewer.targetOrigin); }, /** * 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 els, 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) {} if ((data.event !== undefined) && (data.event == "patternLab.pageLoad")) { if ((modalViewer.active === false) && (data.patternpartial !== undefined) && (data.patternpartial.indexOf('viewall-') === 0) && (config.defaultShowPatternInfo !== undefined) && (config.defaultShowPatternInfo)) { modalViewer.queryPattern(false); } else if (modalViewer.active === true) { modalViewer.queryPattern(); } } else if ((data.event !== undefined) && (data.event == 'patternLab.patternQueryInfo')) { // refresh the modal if a new pattern is loaded and the modal is active modalViewer.refresh(data.patternData, data.iframePassback, data.switchText); } else if ((data.event !== undefined) && (data.event == 'patternLab.annotationNumberClicked')) { // slide to a given annoation modalViewer.slideToAnnotation(data.displayNumber); } } }; // when the document is ready make sure the modal is ready $(document).ready(function() { modalViewer.onReady(); }); window.addEventListener("message", modalViewer.receiveIframeMessage, false); /*! * 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'; } }; /*! * Default languages for Prism to match rendering capability * * Copyright (c) 2016 Dave Olsen, http://dmolsen.com * Licensed under the MIT license * */ var PrismLanguages = { languages: [], get: function(key) { var language; for (i = 0; i < this.languages.length; ++i) { language = this.languages[i]; if (language[key] !== undefined) { return language[key]; } } return 'markup'; }, add: function(language) { // see if the language already exists, overwrite if it does for (var key in language) { if (language.hasOwnProperty(key)) { for (i = 0; i < this.languages.length; ++i) { if (this.languages[i][key] !== undefined) { this.languages[i][key] = language[key]; return; } } } } this.languages.push(language); } }; // this shouldn't get hardcoded, also need to think about including Prism's real lang libraries (e.g. handlebars & twig) PrismLanguages.add({'twig': 'markup'}); PrismLanguages.add({'mustache': 'markup'}); /*! * Default Panels for Pattern Lab plus Panel related events * * Copyright (c) 2016 Dave Olsen, http://dmolsen.com * Licensed under the MIT license * * config is coming from the default viewer and is passed through from PL's config * * @requires prism-languages.js */ var Panels = { panels: [], count: function() { return this.panels.length; }, get: function() { return JSON.parse(JSON.stringify(this.panels)); }, add: function(panel) { // if ID already exists in panels array ignore the add() for (i = 0; i < this.panels.length; ++i) { if (panel.id === this.panels[i].id) { return; } } // it wasn't found so push the tab onto the tabs this.panels.push(panel); } }; // set-up the base file extensions to fetch var fileSuffixPattern = ((config.outputFileSuffixes !== undefined) && (config.outputFileSuffixes.rawTemplate !== undefined)) ? config.outputFileSuffixes.rawTemplate : ''; var fileSuffixMarkup = ((config.outputFileSuffixes !== undefined) && (config.outputFileSuffixes.markupOnly !== undefined)) ? config.outputFileSuffixes.markupOnly : '.markup-only'; // add the default panels Panels.add({ 'id': 'sg-panel-pattern', 'default': true, 'templateID': 'pl-panel-template-code', 'httpRequest': true, 'httpRequestReplace': fileSuffixPattern, 'httpRequestCompleted': false, 'prismHighlight': true, 'keyCombo': 'ctrl+shift+u' }); Panels.add({ 'id': 'sg-panel-html', 'name': 'HTML', 'default': false, 'templateID': 'pl-panel-template-code', 'httpRequest': true, 'httpRequestReplace': fileSuffixMarkup+'.html', 'httpRequestCompleted': false, 'prismHighlight': true, 'language': 'markup', 'keyCombo': 'ctrl+shift+y' }); // gather panels from plugins Dispatcher.trigger('setupPanels'); /*! * Panel Builder. Supports building the panels to be included in the modal or styleguide * * Copyright (c) 2013-16 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com * Licensed under the MIT license * * @requires panels.js * @requires url-handler.js */ var panelsViewer = { // set up some defaults targetOrigin: (window.location.protocol === 'file:') ? '*' : window.location.protocol+'//'+window.location.host, initCopy: false, initMoveTo: 0, /** * Check to see if all of the panels have been collected before rendering * @param {String} the collected panels * @param {String} the data from the pattern * @param {Boolean} if this is going to be passed back to the styleguide */ checkPanels: function(panels, patternData, iframePassback, switchText) { // count how many panels have rendered content var panelContentCount = 0; for (var i = 0; i < panels.length; ++i) { if (panels[i].content !== undefined) { panelContentCount++; } } // see if the count of panels with content matches number of panels if (panelContentCount === Panels.count()) { panelsViewer.renderPanels(panels, patternData, iframePassback, switchText); } }, /** * Gather the panels related to the modal * @param {String} the data from the pattern * @param {Boolean} if this is going to be passed back to the styleguide */ gatherPanels: function(patternData, iframePassback, switchText) { Dispatcher.addListener('checkPanels', panelsViewer.checkPanels); // set-up defaults var template, templateCompiled, templateRendered, panel; // get the base panels var panels = Panels.get(); // evaluate panels array and create content for (var i = 0; i < panels.length; ++i) { panel = panels[i]; // catch pattern panel since it doesn't have a name defined by default if (panel.name === undefined) { panel.name = patternData.patternEngineName || patternData.patternExtension; panel.httpRequestReplace = panel.httpRequestReplace+'.'+patternData.patternExtension; panel.language = patternData.patternExtension; } if ((panel.templateID !== undefined) && (panel.templateID)) { if ((panel.httpRequest !== undefined) && (panel.httpRequest)) { // need a file and then render var fileBase = urlHandler.getFileName(patternData.patternPartial, false); var e = new XMLHttpRequest(); e.onload = (function(i, panels, patternData, iframeRequest) { return function() { prismedContent = Prism.highlight(this.responseText, Prism.languages['html']); template = document.getElementById(panels[i].templateID); templateCompiled = Hogan.compile(template.innerHTML); templateRendered = templateCompiled.render({ 'language': 'html', 'code': prismedContent }); panels[i].content = templateRendered; Dispatcher.trigger('checkPanels', [panels, patternData, iframePassback, switchText]); }; })(i, panels, patternData, iframePassback); e.open('GET', fileBase+panel.httpRequestReplace+'?'+(new Date()).getTime(), true); e.send(); } else { // vanilla render of pattern data template = document.getElementById(panel.templateID); templateCompiled = Hogan.compile(template.innerHTML); templateRendered = templateCompiled.render(patternData); panels[i].content = templateRendered; Dispatcher.trigger('checkPanels', [panels, patternData, iframePassback, switchText]); } } } }, /** * Render the panels that have been collected * @param {String} the collected panels * @param {String} the data from the pattern * @param {Boolean} if this is going to be passed back to the styleguide */ renderPanels: function(panels, patternData, iframePassback, switchText) { // set-up defaults var template, templateCompiled, templateRendered; var annotation, comment, count, div, els, item, markup, i; var patternPartial = patternData.patternPartial; patternData.panels = panels; // set a default pattern description for modal pop-up if (!iframePassback && (patternData.patternDesc.length === 0)) { patternData.patternDesc = "This pattern doesn't have a description."; } // capitilize the pattern name patternData.patternNameCaps = patternData.patternName.toUpperCase(); // check for annotations in the given mark-up markup = document.createElement('div'); markup.innerHTML = patternData.patternMarkup; count = 1; patternData.annotations = []; delete patternData['patternMarkup']; for (i = 0; i < comments.comments.length; ++i) { item = comments.comments[i]; els = markup.querySelectorAll(item.el); if (els.length > 0) { annotation = { 'displayNumber': count, 'el': item.el, 'title': item.title, 'comment': item.comment }; patternData.annotations.push(annotation); count++; } } // alert the pattern that annotations should be highlighted if (patternData.annotations.length > 0) { var obj = JSON.stringify({ 'event': 'patternLab.annotationsHighlightShow', 'annotations': patternData.annotations }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, panelsViewer.targetOrigin); } // add hasComma property to lineage if (patternData.lineage.length > 0) { for (i = 0; i < patternData.lineage.length; ++i) { if (i < (patternData.lineage.length - 1)) { patternData.lineage[i].hasComma = true; } } } // add hasComma property to lineageR if (patternData.lineageR.length > 0) { for (i = 0; i < patternData.lineageR.length; ++i) { if (i < (patternData.lineageR.length - 1)) { patternData.lineageR[i].hasComma = true; } } } // add *Exists attributes for Hogan templates // figure out if the description exists patternData.patternDescExists = ((patternData.patternDesc.length > 0) || ((patternData.patternDescAdditions !== undefined) && (patternData.patternDescAdditions.length > 0))); // figure out if lineage should be drawn patternData.lineageExists = (patternData.lineage.length !== 0); // figure out if reverse lineage should be drawn patternData.lineageRExists = (patternData.lineageR.length !== 0); // figure out if pattern state should be drawn patternData.patternStateExists = (patternData.patternState.length > 0); // figure if annotations should be drawn patternData.annotationExists = (patternData.annotations.length > 0); // figure if the entire desc block should be drawn patternData.descBlockExists = (patternData.patternDescExists || patternData.lineageExists || patternData.lineageRExists || patternData.patternStateExists || patternData.annotationExists); // set isPatternView based on if we have to pass it back to the styleguide level patternData.isPatternView = (iframePassback === false); // render all of the panels in the base panel template template = document.getElementById('pl-panel-template-base'); templateCompiled = Hogan.compile(template.innerHTML); templateRendered = templateCompiled.render(patternData); // make sure templateRendered is modified to be an HTML element div = document.createElement('div'); div.className = 'sg-modal-content-inner'; div.innerHTML = templateRendered; templateRendered = div; // add click events templateRendered = panelsUtil.addClickEvents(templateRendered, patternPartial); // add onclick events to the tabs in the rendered content for (i = 0; i < panels.length; ++i) { panel = panels[i]; // default IDs panelTab = '#sg-'+patternPartial+'-'+panel.id+'-tab'; panelBlock = '#sg-'+patternPartial+'-'+panel.id+'-panel'; // show default options if ((templateRendered.querySelector(panelTab) !== null) && (panel.default)) { templateRendered.querySelector(panelTab).classList.add('sg-tab-title-active'); templateRendered.querySelector(panelBlock).style.display = 'block'; } } // find lineage links in the rendered content and add postmessage handlers in case it's in the modal $('#sg-code-lineage-fill a, #sg-code-lineager-fill a', templateRendered).on('click', function(e){ e.preventDefault(); var obj = JSON.stringify({ 'event': 'patternLab.updatePath', 'path': urlHandler.getFileName($(this).attr('data-patternpartial')) }); document.getElementById('sg-viewport').contentWindow.postMessage(obj, panelsViewer.targetOrigin); }); // gather panels from plugins Dispatcher.trigger('insertPanels', [templateRendered, patternPartial, iframePassback, switchText]); } }; /*! * Pattern Finder * * Copyright (c) 2014 Dave Olsen, http://dmolsen.com * Licensed under the MIT license * * @requires url-handler.js * */ var patternFinder = { data: [], active: false, init: function() { for (var patternType in patternPaths) { if (patternPaths.hasOwnProperty(patternType)) { for (var pattern in patternPaths[patternType]) { var obj = {}; obj.patternPartial = patternType+"-"+pattern; obj.patternPath = patternPaths[patternType][pattern]; this.data.push(obj); } } } // instantiate the bloodhound suggestion engine var patterns = new Bloodhound({ datumTokenizer: function(d) { return Bloodhound.tokenizers.nonword(d.patternPartial); }, queryTokenizer: Bloodhound.tokenizers.nonword, limit: 10, local: this.data }); // initialize the bloodhound suggestion engine patterns.initialize(); $('#sg-find .typeahead').typeahead({ highlight: true }, { displayKey: 'patternPartial', source: patterns.ttAdapter() }).on('typeahead:selected', patternFinder.onSelected).on('typeahead:autocompleted', patternFinder.onAutocompleted); }, passPath: function(item) { // update the iframe via the history api handler patternFinder.closeFinder(); var obj = JSON.stringify({ "event": "patternLab.updatePath", "path": urlHandler.getFileName(item.patternPartial) }); document.getElementById("sg-viewport").contentWindow.postMessage(obj, urlHandler.targetOrigin); }, onSelected: function(e,item) { patternFinder.passPath(item); }, onAutocompleted: function(e,item) { patternFinder.passPath(item); }, toggleFinder: function() { if (!patternFinder.active) { patternFinder.openFinder(); } else { patternFinder.closeFinder(); } }, openFinder: function() { patternFinder.active = true; $('#sg-find .typeahead').val(""); $("#sg-find").addClass('show-overflow'); }, closeFinder: function() { patternFinder.active = false; document.activeElement.blur(); $("#sg-find").removeClass('show-overflow'); $('#sg-find .typeahead').val(""); }, receiveIframeMessage: function(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 data = {}; try { data = (typeof event.data !== 'string') ? event.data : JSON.parse(event.data); } catch(e) {} if ((data.event !== undefined) && (data.event == "patternLab.keyPress")) { if (data.keyPress == 'ctrl+shift+f') { patternFinder.toggleFinder(); return false; } } } }; patternFinder.init(); window.addEventListener("message", patternFinder.receiveIframeMessage, false); $('#sg-find .typeahead').focus(function() { if (!patternFinder.active) { patternFinder.openFinder(); } }); $('#sg-find .typeahead').blur(function() { patternFinder.closeFinder(); }); /*! * 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); /** * @requires data-saver.js * @requires url-handler.js * @requires postmessage.js */ (function (w) { var sw = document.body.clientWidth, //Viewport Width sh = $(document).height(); //Viewport Height var minViewportWidth = 240; var maxViewportWidth = 2600; //set minimum and maximum viewport based on confg if (config.ishMinimum !== undefined) { minViewportWidth = parseInt(config.ishMinimum); //Minimum Size for Viewport } if (config.ishMaximum !== undefined) { maxViewportWidth = parseInt(config.ishMaximum); //Maxiumum Size for Viewport } //alternatively, use the ishViewportRange object if (config.ishViewportRange !== undefined) { minViewportWidth = config.ishViewportRange.s[0]; maxViewportWidth = config.ishViewportRange.l[1]; } var viewportResizeHandleWidth = 14, //Width of the viewport drag-to-resize handle $sgViewport = $('#sg-viewport'), //Viewport element $sizePx = $('.sg-size-px'), //Px size input element in toolbar $sizeEms = $('.sg-size-em'), //Em size input element in toolbar $bodySize = (config.ishFontSize !== undefined) ? parseInt(config.ishFontSize) : parseInt($('body').css('font-size')), //Body size of the document, $headerHeight = $('.sg-header').height(), discoID = false, discoMode = false, fullMode = true, hayMode = false; //Update dimensions on resize $(w).resize(function() { sw = document.body.clientWidth; sh = $(document).height(); setAccordionHeight(); if(fullMode === true) { sizeiframe(sw, false); } }); // Accordion dropdown $('.sg-acc-handle').on("click", function(e){ e.preventDefault(); var $this = $(this), $panel = $this.next('.sg-acc-panel'), subnav = $this.parent().parent().hasClass('sg-acc-panel'); //Close other panels if link isn't a subnavigation item if (!subnav) { $('.sg-acc-handle').not($this).removeClass('active'); $('.sg-acc-panel').not($panel).removeClass('active'); } //Activate selected panel $this.toggleClass('active'); $panel.toggleClass('active'); setAccordionHeight(); }); //Accordion Height function setAccordionHeight() { var $activeAccordion = $('.sg-acc-panel.active').first(), accordionHeight = $activeAccordion.height(), availableHeight = sh-$headerHeight; //Screen height minus the height of the header $activeAccordion.height(availableHeight); //Set height of accordion to the available height } $('.sg-nav-toggle').on("click", function(e){ e.preventDefault(); $('.sg-nav-container').toggleClass('active'); }); // "View (containing clean, code, raw, etc options) Trigger $('#sg-t-toggle').on("click", function(e){ e.preventDefault(); $(this).parents('ul').toggleClass('active'); }); //Size Trigger $('#sg-size-toggle').on("click", function(e){ e.preventDefault(); $(this).parents('ul').toggleClass('active'); }); //Phase View Events $('.sg-size[data-size]').on("click", function(e){ e.preventDefault(); killDisco(); killHay(); fullMode = false; var val = $(this).attr('data-size'); if (val.indexOf('px') > -1) { $bodySize = 1; } val = val.replace(/[^\d.-]/g,''); sizeiframe(Math.floor(val*$bodySize)); }); //Size View Events // handle small button function goSmall() { killDisco(); killHay(); fullMode = false; sizeiframe(getRandom( minViewportWidth, config.ishViewportRange !== undefined ? parseInt(config.ishViewportRange.s[1]) : 500 )); } $('#sg-size-s').on("click", function(e){ e.preventDefault(); goSmall(); }); jwerty.key('ctrl+shift+s', function(e) { goSmall(); return false; }); // handle medium button function goMedium() { killDisco(); killHay(); fullMode = false; sizeiframe(getRandom( config.ishViewportRange !== undefined ? parseInt(config.ishViewportRange.m[0]) : 500, config.ishViewportRange !== undefined ? parseInt(config.ishViewportRange.m[1]) : 800 )); } $('#sg-size-m').on("click", function(e){ e.preventDefault(); goMedium(); }); jwerty.key('ctrl+shift+m', function(e) { goLarge(); return false; }); // handle large button function goLarge() { killDisco(); killHay(); fullMode = false; sizeiframe(getRandom( config.ishViewportRange !== undefined ? parseInt(config.ishViewportRange.l[0]) : 800, config.ishViewportRange !== undefined ? parseInt(config.ishViewportRange.l[1]) : 1200 )); } $('#sg-size-l').on("click", function(e){ e.preventDefault(); goLarge(); }); jwerty.key('ctrl+shift+l', function(e) { goLarge(); return false; }); //Click Full Width Button $('#sg-size-full').on("click", function(e){ //Resets e.preventDefault(); killDisco(); killHay(); fullMode = true; sizeiframe(sw); }); //Click Random Size Button $('#sg-size-random').on("click", function(e){ e.preventDefault(); killDisco(); killHay(); fullMode = false; sizeiframe(getRandom(minViewportWidth,sw)); }); //Click for Disco Mode, which resizes the viewport randomly $('#sg-size-disco').on("click", function(e){ e.preventDefault(); killHay(); fullMode = false; if (discoMode) { killDisco(); } else { startDisco(); } }); /* Disco Mode */ function disco() { sizeiframe(getRandom(minViewportWidth,sw)); } function killDisco() { discoMode = false; clearInterval(discoID); discoID = false; } function startDisco() { discoMode = true; discoID = setInterval(disco, 800); } jwerty.key('ctrl+shift+d', function(e) { if (!discoMode) { startDisco(); } else { killDisco(); } return false; }); //Stephen Hay Mode - "Start with the small screen first, then expand until it looks like shit. Time for a breakpoint!" $('#sg-size-hay').on("click", function