UNPKG

granulate

Version:

Did you ever have to divide your pile of content into horizontal containers, slides, panels?

478 lines (376 loc) 20.4 kB
export default class Granulate { //******************************************************* // building the Granulate Object //******************************************************* constructor(options) { // merge settings this.settings = Granulate.mergeSettings(options); // store some data in the class object this.input = {}; this.storeData(this.settings.content); // check the configuration if(!this.input.container){ console.warn('granulate.js: please define your content'); } if(!this.output.container){ console.warn('granulate.js: please define your outputContainer'); } // inititate the class if all features supported //------------------------------------------------------ if (Granulate.featureTest()) { this.settings.beforeGranulated(); this.attachEvents(); this.injectStyles(); this.wrapIntoSpans(function(classObject){ classObject.buildStorePasteLines(classObject.input.container, classObject, function(classObject){ classObject.granulateContent(classObject, function(){ //lets call the callback with some new data classObject.settings.afterGranulated.call(classObject) }); }); }); } else { console.warn('your browser doesnt support Granulate') } } //******************************************************* // Default settings //******************************************************* static mergeSettings(options) { const settings = { content: '.granulate', outputContainerHeight: 400, outputContainerCss: 'granulate-output', listClass: 'granulate-list-item', dontSplit:'granulate-hold', outputInlineCss: '', beforeGranulated: function(){}, afterGranulated: function(){}, beforeResize: function(){}, afterResize: function(){} }; const userSttings = options; for (const attrname in userSttings) { settings[attrname] = userSttings[attrname]; } return settings; } //******************************************************* // helper methods //******************************************************* // Attach events //------------------------------------------------------ attachEvents() { // Resize element on window resize window.addEventListener('resize', this.resizeHandler.bind(this)); } // update //------------------------------------------------------ resizeHandler(){ this.settings.beforeResize(); const container = this.output.container; container.innerHTML = this.input.spanedContent.outerHTML; container.setAttribute('style', ''); this.storeData(container.childNodes[0]); // console.log(this.input.container); this.buildStorePasteLines(this.input.container, this, function(classObject){ classObject.granulateContent(classObject, function(){ //lets call the callback with some new data classObject.settings.afterResize.call(classObject) // console.log(classObject); }); }); } // store data in the class object //------------------------------------------------------ storeData(inputContentObject){ this.input.padding={}; this.input.container = typeof inputContentObject === 'string' ? document.querySelector(inputContentObject) : inputContentObject; this.input.width = Granulate.getStylesValues(this.input.container, 'width'); this.input.padding.top = Granulate.getStylesValues(this.input.container, 'padding-top'); this.input.padding.bottom = Granulate.getStylesValues(this.input.container, 'padding-bottom'); this.input.padding.left = Granulate.getStylesValues(this.input.container, 'padding-left'); this.input.padding.right = Granulate.getStylesValues(this.input.container, 'padding-right'); this.output = {}; this.output.container = this.input.container.parentNode; } // feature test //------------------------------------------------------ static featureTest(){ return 'querySelector' in document && 'addEventListener' in window; } // replaceHtml //------------------------------------------------------ static replaceHtml(object, html, callback){ object.innerHTML = html; if (typeof callback === "function") { callback(); } } // get Computed styles //------------------------------------------------------ static getStyles(obj, attribute){ const object = (typeof window.getComputedStyle === 'undefined') ? obj.currentStyle : window.getComputedStyle(obj); const value = object.getPropertyValue(attribute) return value; } static getStylesValues(obj, attribute){ const object = (typeof window.getComputedStyle === 'undefined') ? obj.currentStyle : window.getComputedStyle(obj); const value = object.getPropertyValue(attribute) const valueNumber = Number(value.slice(0, -2)); return valueNumber; } // after document finished repainting // let's pass in the granulate object to // be able to use callbacks //------------------------------------------------------ afterDocumentComplete(classObject, callback){ let readyStateCheckInterval = setInterval(function(classObject) { if (document.readyState === "complete") { clearInterval(readyStateCheckInterval); callback(); } }, 10); } //******************************************************* // Main Methods //******************************************************* // styles for proper measuring //------------------------------------------------------ injectStyles(){ const css = ` ${this.settings.outputContainer} span { display:inline-block;} `; const head = document.head || document.getElementsByTagName('head')[0]; const styles = document.createElement('style'); styles.type = 'text/css'; if (styles.styleSheet){ styles.styleSheet.cssText = css; } else { styles.appendChild(document.createTextNode(css)); } head.appendChild(styles); } // This function will wrap all the words from the content container into spans except for // already existing spans, lists, or predefined tags (settings.dontSplit) //-------------------------------------------------------------- wrapIntoSpans(callback){ const gContent = this.input.container; const gContentWidth = Granulate.getStyles(gContent, 'width') const gContentPadding = Granulate.getStyles(gContent, 'padding') const gContentChildren = gContent.children; // iterate through all children for (let i = 0; i < gContentChildren.length; i++) { const child = gContentChildren[i]; const childTagname = child.tagName.toLowerCase(); const childHtml = child.innerHTML; const childClasses = child.classList; // we dont wrap words in <li>'s and predefined nodes const dontIgnore = childTagname !== 'ul' && childTagname !=='ol' && !childClasses.contains(this.settings.dontSplit); if(dontIgnore){ const nodes = child.childNodes; let array = []; for (let i = 0; i < nodes.length; i++) { if (nodes[i].nodeName == "#text") { array = array.concat(nodes[i].nodeValue.split(" ")); continue; } array.push(nodes[i].outerHTML); } const nodesArray = array.filter(v => v.length > 0); const newText = nodesArray.map(function(node){ const notString = node.indexOf('<span') > -1; //don't wrap spans with spans if (notString) { return `${node}`; } else { return `<span class="node">${node}</span>`; // make sure the span wont exceed the parent (display:inline-block) } }).join(' '); // let's replace the children with the new "spaned" content child.innerHTML = newText; } } const classObject = this; // lets cache the spaned content as an object for updates (resize etc..) const spanedInput = document.createElement('div'); [].slice.call(gContent.attributes).forEach(function(item) { spanedInput.setAttribute(item.name, item.value) }); spanedInput.innerHTML = gContent.innerHTML // cache it in the Granulate Object this.input.spanedContent = spanedInput console.log(this); // callback after the page finished repainting this.afterDocumentComplete(classObject, function(){ if (typeof callback === "function") { callback(classObject); } }) } // let's build an array of lines objects based on the spans offset. // The line items will be stored as a string built from an array or a string // Granulate.lines = [ // { // tagName: ''; // cssClasses:''; // styling: ''; // items: [].join(' '); //join if items is an array // }, // ] //------------------------------------------------------ buildStorePasteLines(container, classObject, callback){ // VARS const nodesChildren = container.children; const settings = this.settings; classObject.lines = []; let lines = []; let line = {}; let lastTop = 0; let lineItems = []; //Methods function createLineAndStoreInLines(tag, css, styles, items){ // ignored line.items wont be stored as arrays but as strings const itemsString = typeof items === 'object' ? items.join(' ') : items if (items.length > 0) { line = { tagName: tag, cssClasses: css, styling: styles, items: itemsString } lines.push(line); // console.log('createLine from:', tag, '-', items); } } //iterate through all nodes for (let i = 0; i < nodesChildren.length; i++) { const child = nodesChildren[i]; const childTagname = child.tagName.toLowerCase(); const childClassList = child.classList; const childInlineStyles = child.getAttribute('styles'); const childHtml = child.innerHTML; const childMarginBottom = window.getComputedStyle(child, null).getPropertyValue('margin-bottom'); // we wont split li's if( childTagname == 'ul' || childTagname =='ol' ){ const newChildHtml = childHtml.split(/(?=<li>)/); newChildHtml.map(function(li){ const notEmpty = li.indexOf('</li>') > -1 && li.indexOf('<li>') > -1; if (notEmpty) { lineItems = []; lineItems.push(li.replace('<li>', '').replace('</li>', '').replace('\n', '').replace(/ /, '').trim()); createLineAndStoreInLines('p', settings.listClass, '', lineItems) } }); lineItems = []; // console.warn(`granulate.js: Please style your new list markup: .${settings.listClass}`); } else if( (childClassList.value).indexOf(settings.dontSplit) > -1 ){ createLineAndStoreInLines(childTagname, childClassList.value, childInlineStyles, childHtml ) } else { // we will split all other tags const words = child.querySelectorAll('span'); const lastWord = words.length -1; lineItems = []; for (let i = 0; i < words.length; i++) { const word = words[i]; const toBeStriped = word.classList.contains('node'); const html = word.innerHTML; const outerHtml = word.outerHTML; const isLastWord = (i == lastWord); const isFirstWord = (i == 0); if (isFirstWord) { lastTop = word.offsetTop; } const offset = word.offsetTop; const min = lastTop - 2; const max = lastTop + 2; const isOnOneLine = (min <= offset) && (offset <= max); // console.log(`${offset} : ${lastTop}, ${isOnOneLine}, ${childTagname}, ${html} || ${lineItems}`); if (isLastWord && isOnOneLine) { if (toBeStriped) { lineItems.push(html) } else { lineItems.push(outerHtml); } createLineAndStoreInLines(childTagname, childClassList.value, 'margin-bottom:0;', lineItems); lineItems = [] lastTop = word.offsetTop; // console.log('LAST'); } if (isFirstWord && !isOnOneLine) { if (toBeStriped) { lineItems.push(html) } else { lineItems.push(outerHtml); } } // Next line if (!isOnOneLine) { createLineAndStoreInLines(childTagname, childClassList.value, 'margin-bottom:0;', lineItems); lineItems = [] lastTop = word.offsetTop; } if (toBeStriped) { lineItems.push(html) } else { lineItems.push(outerHtml); } if (isLastWord) { // let's add the original margin to the last line const currentLine = lines.length - 1; lines[currentLine].styling = `margin-bottom: ${childMarginBottom}`; lineItems = [] } } } } // let's store the lines in the Object classObject.lines = lines; //lets build the markup classObject.linesMarkup = classObject.lines.map(function(line){ return `<${line.tagName} style="${line.styling}" class="${line.cssClasses}">${line.items}</${line.tagName}>`; }).join(''); // let's paste the markup classObject.input.container.innerHTML = classObject.linesMarkup; this.afterDocumentComplete(classObject, function(){ if (typeof callback === "function") { callback(classObject); } }) } // Lets divide the content into containers with a defined height //------------------------------------------------------ granulateContent(classObject, callback){ const padding = this.input.padding; // console.log(padding); const containerSpace = this.settings.outputContainerHeight - padding.top - padding.bottom; const children = this.input.container.children; let globalHtml = `<div style="${this.settings.outputInlineCss}; width:${this.input.width}px; padding-top:${padding.top}px; padding-bottom:${padding.bottom}px; padding-left:${padding.left}px; padding-right:${padding.right}px; height:${this.settings.outputContainerHeight}px" class="${this.settings.outputContainerCss}">`; let currentHeight = 0; let breakFlag = false; let numberOfPanels = 1; for (let i = 0; i < children.length; i++) { const _this = children[i]; const marginBottom = Granulate.getStylesValues(_this, 'margin-bottom'); const marginTop = Granulate.getStylesValues(_this, 'margin-top'); const height = _this.clientHeight + marginTop + marginBottom; const html = _this.outerHTML; console.log(`${currentHeight} + ${height} = ${currentHeight + height} - ${_this.innerText}`); currentHeight = height + currentHeight; if (currentHeight < containerSpace) { globalHtml += html; } else { //reset the height counter currentHeight = height; numberOfPanels ++; globalHtml += `</div><div style="${this.settings.outputInlineCss}; width:${this.input.width}px; padding-top:${padding.top}px; padding-bottom:${padding.bottom}px; padding-left:${padding.left}px; padding-right:${padding.right}px; height:${this.settings.outputContainerHeight}px" class="${this.settings.outputContainerCss}"> ${html}`; } } globalHtml += `</div>`; this.granulated = globalHtml; this.output.container.innerHTML = this.granulated; this.output.numberOfPanels = numberOfPanels if (typeof callback === "function") { callback(classObject); } } }