/** Templates are to be able to re-use layouts without copy/paste. They can be a "main" template for the overall page layout, or just sub-layouts e.g. header/footer that always looks the same and in cases where they are the same we also can re-use static data easily. Templates are created with a single argument, options which is a dict that contain at least this: { layout : layout || this, classPrefix: "templateClassPrefix" } Then content is set by calling setContentData, which takes a JSONLayout array OR title/lead/body formatted data, we call this form "article formatted data". Looks like this: [ { title : "optional paragraph title", lead: "shorter optional intro", body: "main text" }, ... ] Whenever you change data, or before layoutFunction, call setContentData(data) Simply take the json in your layoutFunction by calling templateJSON() and lay out. By always fetching the JSON like this templates can both be inserted into the rootElement or as a sub-layout (below a menu, etc). They can also use each other. So, in short: this.template = new Template({ layout : layout || this, classPrefix: "templateClassPrefix" }) this.template.setContentData(...) this.layout.layoutJSON(rootElement, this.template.templateJSON()) */ /** This is a template fixing the fixed-image bug in iOS safari. It needs to layout each row by itself, wrapping them in several divs. @arg options = { imageColor : "lightblue", backgroundImage : "img/niceBGImage.jpg" } optional keys: @arg footer, to display a footer, supply a template here. @arg firstStyle and secondStyle, the css to discribe content divs, background, text, etc. @arg opaqueFooter, instead of getting a transparant footer @arg lessSpaciousLayout, removes some of the spacing, especially above the header. @arg backgroundPosition, since its so common to want to set this property - you can do it at once. @arg noBackground, just rows with colors, no background and no spaces. Like this: var blueColor = "#428bca" var templateOptions = { backgroundImage: "https://feeds-app.com/img/help_bg.jpg", imageColor : blueColor, footer: footer, firstBGColor : "white", } //secondBGColor : blueColor this.bgTemplate = new FixedBackgroundTemplate(templateOptions) //then when layout: this.layout.layoutJSON(rootElement, this.bgTemplate.templateJSON()) EXTRA: To control a content-items parent class, you can supply "parentClassName". This way you can have per-row settings on padding, background, etc. */ function FixedBackgroundTemplate(options) { this.backgroundImage = options.backgroundImage if (options.backgroundImage) this.backgroundImageURL = "url(" + this.backgroundImage + ")"; else this.backgroundImageURL = "" this.imageColor = options.imageColor this.lessSpaciousLayout = options.lessSpaciousLayout this.header = options.header this.footer = options.footer this.opaqueFooter = options.opaqueFooter this.backgroundPosition = options.backgroundPosition || "left center" this.noBackground = options.noBackground || false this.classPrefix = options.classPrefix || "" this.centerDiv = this.classPrefix + (this.classPrefix ? "CenterDiv" : "centerDiv") var firstSelector = "." + this.classPrefix + "fillFirstColor"; var secondSelector = "." + this.classPrefix + "fillSecondColor"; var firstStyle = options.firstStyle || {}; firstStyle.position = "relative" firstStyle.width = "100%" firstStyle.backgroundColor = firstStyle.backgroundColor || options.firstBGColor || "white" options.layout.setCSSFromSelector(firstSelector, firstStyle ) var secondStyle = options.secondStyle || {}; secondStyle.position = "relative" secondStyle.width = "100%" secondStyle.backgroundColor = secondStyle.backgroundColor || options.secondBGColor || "white" options.layout.setCSSFromSelector(secondSelector, secondStyle); //links must be different - default usually won't work. if (!firstStyle.a) options.layout.setCSSFromSelector(firstSelector + " a", { color : firstStyle.color, fontWeight : "bold" }); if (!secondStyle.a) options.layout.setCSSFromSelector(secondSelector + " a", { color : secondStyle.color, fontWeight : "bold" }); if (options.layout) this.setLayout(options.layout) } FixedBackgroundTemplate.prototype.setLayout = function(layout) { this.layout = layout var selectorPrefix = "." + this.classPrefix //layout.setCSSFromSelector(selectorPrefix + "h1", { textAlign : "center" }) layout.setCSSFromSelector("." + this.centerDiv, { padding: "10px", maxWidth: AUTO_DEVICE_WIDTH.phone + "px", margin: "auto" }) layout.setCSSFromSelector(selectorPrefix + "iconImage", { width : "256px", margin: "auto", display: "block" }) layout.setCSSFromSelector(selectorPrefix + "separator", { position: "relative", width: "70%", borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: "rgb(0, 0, 0), left: 15%", margin: "20px 0px" }) layout.setCSSFromSelector(selectorPrefix + "footer", { position : "relative", }) layout.setCSSFromSelector(selectorPrefix + "spaceSeparator", { height : "20vh", position : "relative", backgroundColor: this.imageColor }) //mobile devices are not allowed cover-backgrounds, so we fix that by doing our own image-translation with cliping! It is weird but works! layout.setCSSFromSelector(selectorPrefix + "backgroundFixedWrap", { clip: "rect(0, auto, auto, 0)", position: "absolute", top: "0", left: "0", width: "100%", height: "100%"}) layout.setCSSFromSelector(selectorPrefix + "backgroundFixedWrapFooter", { clip: "rect(0, auto, auto, 0)", position: "absolute", top: "0", left: "0", width: "100%", height: "auto"}) layout.setCSSFromSelector(selectorPrefix + "backgroundFixed", { position: "fixed", display: "block" , top: "0" , left: "0" , width: "100%" , height: "100%", backgroundPosition: this.backgroundPosition, transform: "translateZ(0)" , willChange: "transform", backgroundImage: this.backgroundImageURL, }) this.mobileBG = { className : this.classPrefix + "backgroundFixedWrap", children : [{ className : this.classPrefix + "backgroundFixed" }]} if (this.footer && this.footer.setupFunction) this.footer.setupFunction({ layout : layout, classPrefix: this.classPrefix }) if (this.header && this.header.setupFunction) this.header.setupFunction({ layout : layout, classPrefix: this.classPrefix }) } FixedBackgroundTemplate.prototype.layoutBefore = function(rootElement) { if (!rootElement) rootElement = this.layout.elements.rootElement; this.setContentData(this.layout.getContentData()) this.layout.layoutJSON(rootElement, this.layoutJSON) } FixedBackgroundTemplate.prototype.setContentData = function(contentData) { //we must use unique objects in our JSON! var spaceSeparatorString = JSON.stringify({ className : this.classPrefix + "spaceSeparator", children : [this.mobileBG] }) //if you don't supply a header template, you need to fill the top title with content yourself var layoutJSON = []; if (this.header) { var headerDiv = this.header.templateJSON(); if (!Array.isArray(headerDiv)) headerDiv = [headerDiv] layoutJSON.push({ className : this.classPrefix + "fillFirstColor", children : [{ className : this.centerDiv, children: [{ var : "mainHeaderDiv", children: headerDiv }] }]}) } if (!contentData) { contentData = [{ title : "Loading..."}] } var startsEven = this.lessSpaciousLayout ? 0 : 1; for (var index = 0; index < contentData.length; index++) { //headers are on top - do you want more space - put the header as the first element. if (!this.noBackground && (!this.lessSpaciousLayout || index != 0)) layoutJSON.push(JSON.parse(spaceSeparatorString)); var post = contentData[index]; var postJSON = this.layout.convertPostDataToJSON(post, this.classPrefix); var json; if (post.templateSkip) { json = { children : postJSON } } else if ((index % 2) == startsEven) json = { className : this.classPrefix + "fillSecondColor", children : [{ className: this.centerDiv, children : postJSON}]} else json = { className : this.classPrefix + "fillFirstColor", children : [{ className : this.centerDiv, children : postJSON}]} var firstChild = postJSON[0]; if (firstChild.parentClassName && firstChild.parentClassName) { json.children[0].className += " " + firstChild.parentClassName } layoutJSON.push(json); } //don't supply footer if you don't want one. It is a template - so if you want to handle it differently let a template do it for you. if (this.footer) { if (this.opaqueFooter) { if (!this.noBackground) layoutJSON.push(JSON.parse(spaceSeparatorString)); var footerDiv = { style : { height: "100%" }, children : [this.footer.templateJSON()] } var parentFooter = { className : this.classPrefix + "footer", children: [footerDiv]} if (((index) % 2) == startsEven) layoutJSON.push({ className : this.classPrefix + "fillSecondColor", children : [{ className: this.centerDiv, children : [parentFooter]}]}) else layoutJSON.push({ className : this.classPrefix + "fillFirstColor", children : [{ className: this.centerDiv, children : [parentFooter]}]}) } else { //footerDiv is the main footer div. //paddingLeft: "0px", paddingRight: "0px", height: "100%", var footerDiv = { style : { paddingTop: "20vh", transform: "translateZ(0)" , willChange: "transform" }} footerDiv.children = [this.footer.templateJSON()]; var parentFooter = { className : this.classPrefix + "spaceSeparator " + this.classPrefix + "footer", children: [{ className : this.classPrefix + "backgroundFixedWrapFooter", children : [{ className : this.classPrefix + "backgroundFixed", children : [] }, footerDiv]}] } layoutJSON.push(parentFooter) } } this.layoutJSON = layoutJSON; } FixedBackgroundTemplate.prototype.templateJSON = function() { if (!this.layoutJSON) this.setContentData() return this.layoutJSON; } FixedBackgroundTemplate.prototype.layoutFunction = function() { } /** The FoldingFooterTemplate holds a single row of items if there is space, otherwise centers them on top of each other. */ function FoldingFooterTemplate(options) { this.classPrefix = options.classPrefix || "" this.setContentData(options.items) } FoldingFooterTemplate.prototype.setupFunction = function(options) { var selectorPrefix = "." + this.classPrefix var layout = options.layout var marginPx = "0.5em" //the tricky thing here is to get the background to also behave the same as the bg-spaces - but we can't use fixed size since the text will have unknown height. layout.setCSSFromSelector(selectorPrefix + "FoldingRow", { margin : "auto", maxWidth : "1800px", textAlign: "center", lineHeight: 1.5, fontWeight: "900", fontSize: "1.15em" }) layout.setCSSFromSelector(selectorPrefix + "FoldingPart", { display: "block", padding: "10px" }) if (!layout.phoneSheet) layout.phoneSheet = layout.createStyleSheet({ mediaType : AUTO_CONST.phone }) layout.setCSSFromSelector(selectorPrefix + "FoldingPart", { display: "block", margin: "auto", paddingBottom: marginPx, paddingTop: marginPx, textAlign : "center" }, layout.phoneSheet); } FoldingFooterTemplate.prototype.setContentData = function(items) { var parts = [] for (var index = 0; index < items.length; index++) { var item = items[index] if (item.className) item.className += " " + this.classPrefix + "FoldingPart" else item.className = this.classPrefix + "FoldingPart" parts.push(item) } this.footerDiv = { className: this.classPrefix + "FoldingRow", children: parts } return this.footerDiv; } FoldingFooterTemplate.prototype.templateJSON = function() { return this.footerDiv; } /* //we do a new template that is just the rows - you should be able to have it inside any div, including the body - and then FixedBackgroundTemplate should just use it for laying out rows. Nope, I don't think I can make a better version - not important at the moment! YES: It can be done. Here is a proof of concept: this.applyCSS ({ ".flexParent" : { margin: "20px", backgroundColor: "green" }, }) if (!this.padSheet) this.padSheet = this.createStyleSheet({ mediaType : AUTO_CONST.pad }) this.applyCSS ({ ".flexParent" : { paddingBottom : "10px", display : "flex", flexDirection: "column", "align-content": "stretch", alignItems: "stretch"}, ".flexParent > *" : { alignSelf: "center", width: "300px", }, ".noCenter" : { alignSelf: "start", width: "unset" }, ".noMaxWidth" : { alignSelf: "start", width: "unset", margin: "auto" }, }, null, this.padSheet) var testJSON = [ { className : "flexParent", text : "#Inside header\n\nits body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long its body text is long\n\nThen short\n\n![image](/img/winning_phone.jpg)"}, { className : "flexParent", children: [ { html: "some new stuff with plain html"}, { className: "noCenter", html: "I want this guy to the left, but still fill up all of space. still fill up all of space. still fill up all of space. still fill up all of space. still fill up all of space. " }, ]}, ] this.layoutJSON(this.elements.rootElement, testJSON) var innerImages = document.querySelectorAll('.flexParent > p > img'); for (var index = 0; index < innerImages.length; index++) { var container = innerImages[index].parentElement; container.className = "noMaxWidth " + (container.className || "") } */