flexlayout-rails 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,7 +4,8 @@ Polyfills for various HTML 5 "more flexible" layout models.
4
4
 
5
5
  * Flexie.js (Flex box layout)
6
6
  * Regions.js (CSS Regions)
7
- * CSS Template Layout
7
+ * CSS Multi-column
8
+ * CSS Template layout
8
9
 
9
10
  Flexie.js is a polyfill for the [CSS3 Flexbox](http://www.w3.org/TR/css3-flexbox/) module from W3C.
10
11
 
@@ -15,6 +16,13 @@ CSS Template Layout is an implementation of [CSS Template Layout Module](http://
15
16
  These polyfills are all packaged for use with the Rails asset pipeline.
16
17
  This lets you use and experiment with the new HTML5 CSS layout model in order to simplify and improve your HTML grids and layout :)
17
18
 
19
+ In addition a stylesheet called `flexlayout.css` is included, which wraps the Flex layout model in order to provide some nice utility CSS classes. See the examples in `spec/flexlayout` for a usage demo.
20
+
21
+ ## Extras
22
+
23
+ * [Wookmark-jQuery](https://github.com/GBKS/Wookmark-jQuery)
24
+ * [FT-column-flow](https://github.com/ftlabs/ftcolumnflow)
25
+
18
26
  ## Install
19
27
 
20
28
  `gem 'flexlayout-rails'`
@@ -25,35 +33,7 @@ This lets you use and experiment with the new HTML5 CSS layout model in order to
25
33
  //= require flexie
26
34
  ```
27
35
 
28
- ### Regions.js
29
-
30
- If you find [this](http://labs.adobe.com/technologies/cssregions/) interesting, just try this script!
31
-
32
- Regions.js requires [jQuery](http://jquery.com/) and [Lettering.js](https://github.com/davatron5000/Lettering.js) in order to work.
33
-
34
- ```text
35
- //= require jquery
36
- //= require lettering.jquery
37
- //= require regions.jquery.min
38
- ```
39
-
40
- ### Template layout 2011-2012 Pablo Escalada
41
-
42
- ```text
43
- //= require template_layout/templateLayout
44
- ```
45
-
46
- Not sure how/if to use the template generator and compiler!?
47
-
48
- ### Template layout 2010, Alexis Deveria
49
-
50
- ```text
51
- //= require template_layout/jquery/jquery.tpl_layout1.1.16.min
52
- ```
53
-
54
- Enjoy :)
55
-
56
- ## Flexie usage
36
+ *Flexie usage*
57
37
 
58
38
  ```javscript
59
39
  var box = new Flexie.box({
@@ -82,7 +62,36 @@ DEFAULTS = {
82
62
  }
83
63
  ```
84
64
 
85
- ## Regions.js usage
65
+ *Flexbox Articles*
66
+
67
+ * [Flexbox quick guide](http://www.html5rocks.com/en/tutorials/flexbox/quick/)
68
+
69
+ _Critiques:_
70
+
71
+ * [Why Flexboxes Aren't Good for Page Layout](http://www.xanthir.com/blog/b4580)
72
+ * [Flexbox vs Princess Bride](http://oli.jp/2011/css3-flexbox/)
73
+
74
+ ### Regions.js
75
+
76
+ If you find [this](http://labs.adobe.com/technologies/cssregions/) interesting, just try this script!
77
+
78
+ Regions.js requires [jQuery](http://jquery.com/) and [Lettering.js](https://github.com/davatron5000/Lettering.js) in order to work.
79
+
80
+ ```text
81
+ //= require jquery
82
+ //= require lettering.jquery
83
+ //= require regions.jquery.min
84
+ ```
85
+
86
+ The `feature-detects/cssregions` Modernizr detection included is part of Modernizr _2.6+_ and supplied by _Adobe_.
87
+
88
+ ```javascript
89
+ if (!Modernizr.cssregions) {
90
+ // use regions polyfill
91
+ }
92
+ ```
93
+
94
+ *Regions.js API usage*
86
95
 
87
96
  ```javascript
88
97
  $(function(){ // When DOM is Loaded
@@ -108,22 +117,70 @@ $("#content-source-element").regions([$("#article-region-1"), $("#article-region
108
117
  $("#content-source-element").regions([$("#article-region-1"), "#article-region-2", $("#article-region-3")]);
109
118
  ```
110
119
 
120
+ ### Template layout 2011-2012 Pablo Escalada
121
+
122
+ ```text
123
+ //= require template_layout/templateLayout
124
+ ```
125
+
126
+ Not sure how/if to use the template generator and compiler!?
127
+
128
+ ### Template layout 2010, Alexis Deveria
129
+
130
+ ```text
131
+ //= require template_layout/jquery/jquery.tpl_layout1.1.16.min
132
+ ```
133
+
111
134
  ## Template layout usage
112
135
 
113
136
  Alexis Deveria version, see repo [css-template-layout](https://code.google.com/p/css-template-layout/) and these [demos](http://a.deveria.com/csstpl/) :)
114
137
 
115
138
  Pablo Escalada version, see repo at [cssTemplateLayout](https://github.com/diesire/cssTemplateLayout) and test folder for examples.
116
139
 
117
- ## Flexbox Articles
140
+ ## Multi column
118
141
 
119
- * [Flexbox quick guide](http://www.html5rocks.com/en/tutorials/flexbox/quick/)
142
+ See [Multi-column](https://github.com/stadtwerk/MultiColumn)
120
143
 
121
- _Critiques:_
144
+ Example:
122
145
 
123
- * [Why Flexboxes Aren't Good for Page Layout](http://www.xanthir.com/blog/b4580)
124
- * [Flexbox vs Princess Bride](http://oli.jp/2011/css3-flexbox/)
146
+ ```html
147
+ <div class="multi-column">
148
+ <div class="column">hello</div>
149
+ ...
150
+ </div>
151
+ ```
152
+
153
+ Note: Use the `multi-column.css` included as well ;)
154
+
155
+ ## Wookmark
156
+
157
+ ```text
158
+ //= require jquery.wookmark.min
159
+ ```
160
+
161
+ See [Wookmark-jQuery](https://github.com/GBKS/Wookmark-jQuery) for examples and demos.
162
+
163
+ ## FT Column flow
164
+
165
+ ```text
166
+ //= require FT-colum-flow.min
167
+ ```
168
+
169
+ * Configurable column widths, gutters and margins
170
+ * Fixed-position elements
171
+ * Elements spanning columns
172
+ * Keep-with-next to avoid headings at the bottom of a column
173
+ * No-wrap class to avoid splitting elements across columns
174
+ * Grouping of columns into pages
175
+ * Horizontal or vertical alignment of pages
176
+ * Standardised line height to align text baseline to a grid
177
+ * Rapid reflow as required by events such as device orientation or font-size change
178
+
179
+ See [FT-column-flow](https://github.com/ftlabs/ftcolumnflow) for examples, demos and usage guide etc.
180
+
181
+ See fx [BAsic usage example](http://ftlabs.github.com/ftcolumnflow/1.html)
125
182
 
126
- ## Contributing to flexie-rails
183
+ ## Contributing to flexlayout-rails
127
184
 
128
185
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
129
186
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ Jeweler::Tasks.new do |gem|
18
18
  gem.homepage = "http://github.com/kristianmandrup/flexlayout-rails"
19
19
  gem.license = "MIT"
20
20
  gem.summary = %Q{CSS3 layout polyfills pre-packaged for Rails asset pipeline}
21
- gem.description = %Q{Use CSS Flex, Regions and Template layout models in your Rails apps}
21
+ gem.description = %Q{Use CSS Flex, Regions, Multi-column and Template layout models in your Rails apps}
22
22
  gem.email = "kmandrup@gmail.com"
23
23
  gem.authors = ["Kristian Mandrup"]
24
24
  # dependencies defined in Gemfile
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "flexlayout-rails"
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kristian Mandrup"]
12
12
  s.date = "2012-08-28"
13
- s.description = "Use CSS Flex, Regions and Template layout models in your Rails apps"
13
+ s.description = "Use CSS Flex, Regions, Multi-column and Template layout models in your Rails apps"
14
14
  s.email = "kmandrup@gmail.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
@@ -27,24 +27,33 @@ Gem::Specification.new do |s|
27
27
  "VERSION",
28
28
  "flexlayout-rails.gemspec",
29
29
  "lib/flexlayout-rails.rb",
30
+ "spec/flexlayout/center.html",
31
+ "spec/flexlayout/form.html",
30
32
  "spec/regions/img/lorem-ipsum-logo.jpeg",
31
33
  "spec/regions/index.html",
32
34
  "spec/regions/js/detection.js",
33
35
  "spec/regions/js/layout.js",
34
36
  "spec/regions/js/setup.js",
35
37
  "spec/spec_helper.rb",
38
+ "vendor/assets/javascripts/FT-colum-flow.min.js",
39
+ "vendor/assets/javascripts/FT-column-flow.js",
36
40
  "vendor/assets/javascripts/feature-detects/cssregions.js",
37
41
  "vendor/assets/javascripts/flexie.js",
38
42
  "vendor/assets/javascripts/flexie.min.js",
39
43
  "vendor/assets/javascripts/jquery.lettering.js",
40
44
  "vendor/assets/javascripts/jquery.lettering.min.js",
45
+ "vendor/assets/javascripts/jquery.wookmark.js",
46
+ "vendor/assets/javascripts/jquery.wookmark.min.js",
47
+ "vendor/assets/javascripts/multi-column.js",
41
48
  "vendor/assets/javascripts/regions.jquery.js",
42
49
  "vendor/assets/javascripts/regions.jquery.min.js",
43
50
  "vendor/assets/javascripts/template_layout/jquery/jquery.tpl_layout1.1.6.js",
44
51
  "vendor/assets/javascripts/template_layout/jquery/jquery.tpl_layout1.1.6.min.js",
45
52
  "vendor/assets/javascripts/template_layout/templateLayout.compiler.js",
46
53
  "vendor/assets/javascripts/template_layout/templateLayout.generator.js",
47
- "vendor/assets/javascripts/template_layout/templateLayout.js"
54
+ "vendor/assets/javascripts/template_layout/templateLayout.js",
55
+ "vendor/assets/stylesheets/flexlayout.css",
56
+ "vendor/assets/stylesheets/multi-column.css"
48
57
  ]
49
58
  s.homepage = "http://github.com/kristianmandrup/flexlayout-rails"
50
59
  s.licenses = ["MIT"]
@@ -0,0 +1,13 @@
1
+ <html>
2
+ <head>
3
+ <link rel="stylesheet" href="../../vendor/assets/stylesheets/flexlayout.css">
4
+ </head>
5
+ <body>
6
+ <div class="hbox center">
7
+ <div class="vbox center">
8
+ <div>Hello</div>
9
+ <div>There!</div>
10
+ </div>
11
+ </div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,18 @@
1
+ <html>
2
+ <head>
3
+ <link rel="stylesheet" href="../../vendor/assets/stylesheets/flexlayout.css">
4
+ </head>
5
+ <body>
6
+ <form action="handler.cgi" method="POST" class="hbox">
7
+ <div class="vbox">
8
+ <label>First Name (required):</label>
9
+ <label>Last Name:</label>
10
+ </div>
11
+ <div class="vbox">
12
+ <input type="text" name="first"/>
13
+ <input type="text" name="last"/>
14
+ <input type="submit"/>
15
+ </div>
16
+ </form>
17
+ </body>
18
+ </html>
@@ -0,0 +1,22 @@
1
+ /**
2
+ * FTColumnflow is a polyfill that fixes the inadequacies of CSS column layouts.
3
+ *
4
+ * It is developed by FT Labs (http://labs.ft.com), part of the Financial Times.
5
+ * It is extensively used in the FT Web App (http://app.ft.com), where it allows us to
6
+ * publish articles with complex newspaper/magazine style layouts, including features such as:
7
+ * - Configurable column widths, gutters and margins
8
+ * - Fixed-position elements
9
+ * - Elements spanning columns
10
+ * - Keep-with-next to avoid headings at the bottom of a column
11
+ * - No-wrap class to avoid splitting elements across columns
12
+ * - Grouping of columns into pages
13
+ * - Horizontal or vertical alignment of pages
14
+ * - Standardised line height to align text baseline to a grid
15
+ * - Rapid reflow as required by events such as device orientation or font-size change
16
+ * It is designed with the same column dimension specification API as the CSS3 multi-column specification
17
+ * (http://www.w3.org/TR/css3-multicol/), but gives far greater flexibility over element positioning within those columns.
18
+ *
19
+ * @codingstandard ftlabs-jslint
20
+ * @copyright The Financial Times Limited [All Rights Reserved]
21
+ */
22
+ this.FTColumnflow=(function(){"use strict";function FTColumnflowException(name,message){this.name='FTColumnflow'+name||'FTColumnflowException';this.message=message||''}FTColumnflowException.prototype=new Error();FTColumnflowException.constructor=FTColumnflowException;var layoutDimensionList=['pageInnerWidth','pageInnerHeight','colDefaultTop','colDefaultLeft','columnCount','columnWidth','columnGap'],defaultConfig={layoutDimensions:null,columnFragmentMinHeight:0,viewportWidth:null,viewportHeight:null,columnWidth:'auto',columnCount:'auto',columnGap:'normal',pageClass:'cf-page',columnClass:'cf-column',pageArrangement:'horizontal',pagePadding:0,debug:false,showGrid:false,standardiseLineHeight:false,minFixedPadding:1,lineHeight:null,noWrapOnTags:[]},cssStyles='#[targetId] { position: relative; height: 100%; }\n'+'#[targetId] .[preloadAreaClassName].[pageClass] { visibility: hidden; position: absolute; overflow: hidden; }\n'+'#[targetId] .[preloadFixedAreaClassName] { visibility: hidden; position: absolute; }\n'+'#[targetId] .[pageClass] { position: absolute; width: [viewportWidth]px; height: [viewportHeight]px; [pageArrangement] }\n'+'#[targetId] .[columnClass] { position: absolute; width: [columnWidth]px; overflow: hidden; }\n'+'#[targetId] .[pageClass] .[fixedElementClassName] { position: absolute; }\n'+'#[targetId] .[pageClass] .[columnClass] > :first-child { margin-top: 0px; }\n',cssColumnStyles='#[targetId] .[columnClass].[columnClass]-[columnNum] { left: [leftPos]px; }\n',showGridStyles='#[targetId] .[pageClass] { background-image: -webkit-linear-gradient(skyblue 1px, transparent 1px); background-size: 100% [lh]px; background-origin: content-box; }',_outerHTML=(function(){var outerHTMLXmlSerializer,outerHTMLContainer,html;if(typeof document!=="undefined"&&!document.createElementNS("http://www.w3.org/1999/xhtml","_").hasOwnProperty('outerHTML')){if(document.xmlVersion){outerHTMLXmlSerializer=new XMLSerializer();return function _outerHTMLXML(node){return outerHTMLXmlSerializer.serializeToString(node)}}else{outerHTMLContainer=document.createElementNS("http://www.w3.org/1999/xhtml","_");return function _outerHTMLNode(node){var html;outerHTMLContainer.appendChild(node.cloneNode(false));html=outerHTMLContainer.innerHTML.replace("><",">"+node.innerHTML+"<");outerHTMLContainer.innerHTML="";return html}}}else{return function _outerHTMLNative(node){return node.outerHTML}}}());function FTColumnflow(target,viewport,userConfig){var config={},debugMode,showGrid,flowedContent,fixedContent,maxColumnHeight,colDefaultBottom,colMiddle,minFixedPadding,fixedPadding,renderArea,preloadColumn,fixedPreloadArea,headElement,headerStyles,targetIdPrefix='cf-target-',renderAreaClassName='cf-render-area',preloadAreaClassName='cf-preload',preloadFixedAreaClassName='cf-preload-fixed',fixedElementClassName='cf-fixed',nowrapClassName='nowrap',keepwithnextClassName='keepwithnext',lineHeightTestContents,pagedContent=[],borderElementIndex,indexedPageNum,indexedColumnNum,indexedColumnFrag,topElementOverflow,totalColumnHeight,workingPage,workingColumn,workingColumnFrag,that=this;this.target=target;this.viewport=viewport;this._checkInstanceArgs();_setConfig(userConfig);_setLayoutDimensions();function _setConfig(userConfig){var i;that.config=config={};for(i in defaultConfig){if(defaultConfig.hasOwnProperty(i)){config[i]=defaultConfig[i]}}for(i in userConfig){if(userConfig.hasOwnProperty(i)){if(config[i]===undefined){throw new FTColumnflowException('ParameterException','Unknown config parameter ['+i+'].')}config[i]=userConfig[i]}}if(config.debug!==undefined)debugMode=!!config.debug;if(config.showGrid!==undefined)showGrid=!!config.showGrid;if('horizontal'!==config.pageArrangement&&'vertical'!==config.pageArrangement){throw new FTColumnflowException('ArrangementException',config.pageArrangement+' is not a valid Page Arrangement value.')}config.pagePadding=parseInt(config.pagePadding,10);if(isNaN(config.pagePadding)){throw new FTColumnflowException('PaddingException',config.pagePadding+' is not a valid Page Padding value.')}['columnWidth','columnCount','columnGap','columnFragmentMinHeight'].forEach(function _checkColumnSpecification(type){if(defaultConfig[type]===config[type])return true;config[type]=parseInt(config[type],10);if(isNaN(config[type])||config[type]<0){throw new FTColumnflowException('ColumnDimensionException',type+' must be an positive integer or "'+defaultConfig[type]+'".')}});if(typeof config.standardiseLineHeight!=='boolean'){throw new FTColumnflowException('StandardiseLineheightException','standardiseLineHeight must be a boolean value.')}config.minFixedPadding=parseFloat(config.minFixedPadding);if(isNaN(config.minFixedPadding)){throw new FTColumnflowException('MinFixedPaddingException','minFixedPadding must be a float or integer.')}if((config.viewportWidth!==null)&&isNaN(config.viewportWidth=parseInt(config.viewportWidth,10))){throw new FTColumnflowException('ViewportWidthException','viewportWidth must be an integer.')}if((config.viewportHeight!==null)&&isNaN(config.viewportHeight=parseInt(config.viewportHeight,10))){throw new FTColumnflowException('ViewportHeightException','viewportHeight must be an integer.')}if((config.lineHeight!==null)&&isNaN(config.lineHeight=parseInt(config.lineHeight,10))){throw new FTColumnflowException('LineheightException','lineHeight must be an integer.')}config.pageClass=_normaliseClassName('pageClass',config.pageClass);config.columnClass=_normaliseClassName('columnClass',config.columnClass);if(!Array.isArray(config.noWrapOnTags)){throw new FTColumnflowException('NoWrapException','noWrapOnTags must be an Array.')}config.noWrapOnTags=config.noWrapOnTags.map(function _lowercase(item){return item.toLowerCase()})}function _setLayoutDimensions(){var i,l,derivedColumnCount;if(config.layoutDimensions!==null){for(i=0,l=layoutDimensionList.length;i<l;i++){if(isNaN(Number(config.layoutDimensions[layoutDimensionList[i]]))){throw new FTColumnflowException('DimensionCacheException','Must specify an integer value for '+layoutDimensionList[i])}}return}config.layoutDimensions={};if(!config.viewportWidth){config.viewportWidth=parseInt(window.getComputedStyle(that.viewport).getPropertyValue('width'),10)}if(!config.viewportHeight){config.viewportHeight=parseInt(window.getComputedStyle(that.viewport).getPropertyValue('height'),10)}if(!config.viewportWidth||!config.viewportHeight){throw new FTColumnflowException('ViewportException','Viewport element must have width and height.')}config.layoutDimensions.columnGap=('normal'===config.columnGap)?parseInt(window.getComputedStyle(that.viewport).fontSize,10):config.columnGap;if('horizontal'===config.pageArrangement){config.layoutDimensions.pageInnerWidth=config.viewportWidth-(2*config.pagePadding);config.layoutDimensions.pageInnerHeight=config.viewportHeight;config.layoutDimensions.colDefaultTop=0;config.layoutDimensions.colDefaultLeft=config.pagePadding}else{config.layoutDimensions.pageInnerWidth=config.viewportWidth;config.layoutDimensions.pageInnerHeight=config.viewportHeight-(2*config.pagePadding);config.layoutDimensions.colDefaultTop=config.pagePadding;config.layoutDimensions.colDefaultLeft=0}if('auto'===config.columnWidth&&'auto'===config.columnCount){config.layoutDimensions.columnCount=1;config.layoutDimensions.columnWidth=config.layoutDimensions.pageInnerWidth}else if('auto'===config.columnWidth&&'auto'!==config.columnCount){config.layoutDimensions.columnCount=config.columnCount;config.layoutDimensions.columnWidth=(config.layoutDimensions.pageInnerWidth-((config.columnCount-1)*config.layoutDimensions.columnGap))/config.columnCount}else{derivedColumnCount=Math.max(1,Math.floor((config.layoutDimensions.pageInnerWidth+1+config.layoutDimensions.columnGap)/(config.columnWidth+config.layoutDimensions.columnGap)));if('auto'===config.columnCount){config.layoutDimensions.columnCount=derivedColumnCount;config.layoutDimensions.columnWidth=((config.layoutDimensions.pageInnerWidth+config.layoutDimensions.columnGap)/config.layoutDimensions.columnCount)-config.layoutDimensions.columnGap}else{config.layoutDimensions.columnCount=Math.min(config.columnCount,derivedColumnCount);config.layoutDimensions.columnWidth=((config.layoutDimensions.pageInnerWidth+config.layoutDimensions.columnGap)/config.layoutDimensions.columnCount)-config.layoutDimensions.columnGap}}}function _writeTargetStyles(){var styleContents,columnNum;if(!headElement)headElement=document.querySelector('head');if(!that.target.id)that.target.id=targetIdPrefix+Math.floor(Math.random()*(9000000000)+1000000000);styleContents=_replaceStringTokens(cssStyles,{targetId:that.target.id,preloadAreaClassName:preloadAreaClassName,preloadFixedAreaClassName:preloadFixedAreaClassName,fixedElementClassName:fixedElementClassName,pageClass:config.pageClass,columnClass:config.columnClass,columnWidth:config.layoutDimensions.columnWidth,viewportWidth:config.viewportWidth,viewportHeight:config.viewportHeight,pageArrangement:('horizontal'===config.pageArrangement)?'top: 0;':'left: 0;'});for(columnNum=1;columnNum<=config.layoutDimensions.columnCount;columnNum++){styleContents+=_replaceStringTokens(cssColumnStyles,{targetId:that.target.id,columnClass:config.columnClass,columnNum:columnNum,leftPos:config.layoutDimensions.colDefaultLeft+(config.layoutDimensions.columnWidth*(columnNum-1))+(config.layoutDimensions.columnGap*(columnNum-1))})}if(headerStyles){while(headerStyles.hasChildNodes()){headerStyles.removeChild(headerStyles.firstChild)}}else{headerStyles=document.createElement('style');headerStyles.setAttribute('type','text/css')}headerStyles.appendChild(document.createTextNode(styleContents));headElement.appendChild(headerStyles)}function _createTargetElements(){var preloadElement;that.target.innerHTML='<div class="'+preloadAreaClassName+' '+config.pageClass+'">'+'<div class="'+config.columnClass+'"></div>'+'</div>'+'<div class="'+preloadFixedAreaClassName+'"></div>'+'<div class="'+renderAreaClassName+'"></div>';renderArea=that.target.getElementsByClassName(renderAreaClassName)[0];preloadElement=that.target.getElementsByClassName(preloadAreaClassName)[0];preloadColumn=preloadElement.getElementsByClassName(config.columnClass)[0];if('string'===typeof flowedContent){preloadColumn.innerHTML=flowedContent}else if(flowedContent instanceof HTMLElement){preloadColumn.innerHTML=flowedContent.innerHTML}else if(flowedContent){throw new FTColumnflowException('FlowedContentException','flowedContent must be a HTML string or a DOM element.')}fixedPreloadArea=that.target.getElementsByClassName(preloadFixedAreaClassName)[0];if('string'===typeof fixedContent){fixedPreloadArea.innerHTML=fixedContent}else if(fixedContent instanceof HTMLElement){fixedPreloadArea.innerHTML=fixedContent.innerHTML}else if(fixedContent){throw new FTColumnflowException('FixedContentException','fixedContent must be a HTML string or a DOM element.')}}function _findLineHeight(){var lineHeights,i,l,node,testNode,testLineHeight;if(!config.lineHeight){if(!lineHeightTestContents){lineHeightTestContents=new Array(10).join('x<br />')+'x'}lineHeights=[];for(i=0,l=Math.min(10,(preloadColumn.childNodes.length));i<l;i++){node=preloadColumn.childNodes[i];if(Node.ELEMENT_NODE!==node.nodeType)continue;testLineHeight=parseInt(window.getComputedStyle(node).getPropertyValue('line-height'),10);if(!testLineHeight){testNode=node.cloneNode(false);if(node.className)testNode.className=node.className;testNode.style.padding="0px";testNode.style.border="none";testNode.style.height="auto";testNode.innerHTML=lineHeightTestContents;preloadColumn.appendChild(testNode);testLineHeight=testNode.offsetHeight/10;preloadColumn.removeChild(testNode)}lineHeights.push(testLineHeight)}if(lineHeights.length<5){testNode=document.createElement('p');testNode.style.padding="0px";testNode.style.border="none";testNode.style.height="auto";testNode.innerHTML=lineHeightTestContents;preloadColumn.appendChild(testNode);testLineHeight=testNode.offsetHeight/10;preloadColumn.removeChild(testNode);for(i=lineHeights.length;i<5;i++)lineHeights.push(testLineHeight)}config.lineHeight=_mode(lineHeights)}if(showGrid){headerStyles.innerHTML+=_replaceStringTokens(showGridStyles,{targetId:that.target.id,pageClass:config.pageClass,'lh':config.lineHeight})}}function _addFixedElement(element){var indexedColStart,indexedColEnd,anchorY,anchorX,elementTopPos,elementBottomPos,lowestTopPos,highestBottomPos,topSplitPoint,bottomSplitPoint,matches,indexedPageNum,spanDir,colSpan,normalisedElementHeight,columnNum,firstColFragment,lastColFragment,newColumnFragment,newFragmentHeight,fragment,column,fragNum,fragLen;if(Node.TEXT_NODE===element.nodeType||('none'===window.getComputedStyle(element).getPropertyValue('display'))){return}element.classList.add(fixedElementClassName);matches=element.className.match(/(\s|^)attach-page-(\d+)(\s|$)/);if(matches){indexedPageNum=matches[2]-1}else{indexedPageNum=0}_createPageObjects(indexedPageNum);workingPage=pagedContent[indexedPageNum];matches=element.className.match(/(\s|^)anchor-(top|middle|bottom)-(left|right|(?:col-(\d+)))(\s|$)/);if(matches){anchorY=matches[2];if(matches[4]){anchorX=Math.max(0,(Math.min(matches[4],config.layoutDimensions.columnCount)-1))}else{anchorX=matches[3]}}else{anchorY='top';anchorX='left'}matches=element.className.match(/(\s|^)col-span-(\d+|all)(-(left|right))?(\s|$)/);if(matches){spanDir=matches[4]||'right';if(matches[2]==='all'){colSpan=config.layoutDimensions.columnCount}else{colSpan=parseInt(matches[2],10)}if('left'===anchorX){indexedColStart=0;indexedColEnd=Math.min(colSpan,config.layoutDimensions.columnCount)-1}else if('right'===anchorX){indexedColEnd=config.layoutDimensions.columnCount-1;indexedColStart=config.layoutDimensions.columnCount-Math.min(colSpan,config.layoutDimensions.columnCount)}else{if('right'===spanDir){indexedColStart=anchorX;indexedColEnd=Math.min((indexedColStart+colSpan),config.layoutDimensions.columnCount)-1}else{indexedColEnd=anchorX;indexedColStart=Math.max((indexedColEnd-colSpan-1),0)}}}else{if('left'===anchorX){indexedColStart=0}else if('right'===anchorX){indexedColStart=config.layoutDimensions.columnCount-1}else{indexedColStart=anchorX}indexedColEnd=indexedColStart}element.style.width=((indexedColEnd-indexedColStart)*(config.layoutDimensions.columnWidth+config.layoutDimensions.columnGap))+config.layoutDimensions.columnWidth+'px';normalisedElementHeight=element.offsetHeight+parseInt(window.getComputedStyle(element).getPropertyValue('margin-top'),10);switch(anchorY){case'top':elementTopPos=config.layoutDimensions.colDefaultTop;lowestTopPos=colDefaultBottom-normalisedElementHeight;for(columnNum=indexedColStart;columnNum<=indexedColEnd;columnNum++){firstColFragment=workingPage.columns[columnNum].fragments[0];if(!firstColFragment){elementTopPos=lowestTopPos}else{if(firstColFragment.top>elementTopPos){elementTopPos=(firstColFragment.top>lowestTopPos)?lowestTopPos:firstColFragment.top}}}elementBottomPos=elementTopPos+normalisedElementHeight;topSplitPoint=elementTopPos-config.lineHeight;bottomSplitPoint=_roundUpToGrid(elementBottomPos,true);break;case'middle':elementTopPos=colMiddle-(normalisedElementHeight/2);topSplitPoint=_roundDownToGrid(elementTopPos,true);bottomSplitPoint=_roundUpToGrid(elementTopPos+normalisedElementHeight,true);if(topSplitPoint<0)topSplitPoint=0;if(bottomSplitPoint>maxColumnHeight)bottomSplitPoint=maxColumnHeight;break;case'bottom':elementBottomPos=colDefaultBottom;highestBottomPos=normalisedElementHeight;for(columnNum=indexedColStart;columnNum<=indexedColEnd;columnNum++){lastColFragment=workingPage.columns[columnNum].fragments[workingPage.columns[columnNum].fragments.length-1];if(!lastColFragment){elementBottomPos=highestBottomPos}else{if(lastColFragment.bottom<elementBottomPos){elementBottomPos=(lastColFragment.bottom<highestBottomPos)?highestBottomPos:lastColFragment.bottom}}}elementTopPos=elementBottomPos-normalisedElementHeight;topSplitPoint=_roundDownToGrid(elementTopPos,true);bottomSplitPoint=elementBottomPos+config.lineHeight;break}for(columnNum=indexedColStart;columnNum<=indexedColEnd;columnNum++){column=workingPage.columns[columnNum];for(fragNum=0,fragLen=column.fragments.length;fragNum<fragLen;fragNum++){fragment=column.fragments[fragNum];if(topSplitPoint<fragment.top&&bottomSplitPoint>fragment.bottom){column.fragments.splice(fragNum,1);fragLen--;continue}else if(topSplitPoint>fragment.bottom||bottomSplitPoint<fragment.top){continue}newFragmentHeight=fragment.top+fragment.height-bottomSplitPoint;fragment.height=topSplitPoint-fragment.top;fragment.bottom=fragment.top+fragment.height;if(!fragment.height||fragment.height<config.columnFragmentMinHeight){column.fragments.splice(fragNum--,1);fragLen--}if(newFragmentHeight&&newFragmentHeight>=config.columnFragmentMinHeight){newColumnFragment=_createColumnFragment();newColumnFragment.top=bottomSplitPoint;newColumnFragment.height=newFragmentHeight;newColumnFragment.bottom=newColumnFragment.top+newColumnFragment.height;column.fragments.splice(++fragNum,0,newColumnFragment);fragNum++;fragLen++}}}element.style.top=elementTopPos+'px';element.style.left=(config.layoutDimensions.colDefaultLeft+(('left'===anchorX)?0:((config.layoutDimensions.columnWidth+config.layoutDimensions.columnGap)*indexedColStart)))+'px';workingPage.fixed.push({content:_outerHTML(element)})}function _normaliseFlowedElement(element){var p;if(Node.TEXT_NODE!==element.nodeType)return;if(element.nodeValue.match(/^\s*$/)){element.parentNode.removeChild(element)}else{p=document.createElement('p');p.appendChild(document.createTextNode(element.nodeValue));element.parentNode.replaceChild(p,element)}}function _flowContent(){var i,l;pagedContent=[];indexedPageNum=indexedColumnNum=indexedColumnFrag=borderElementIndex=indexedPageNum=indexedColumnNum=indexedColumnFrag=topElementOverflow=totalColumnHeight=0;maxColumnHeight=config.lineHeight?_roundDownToGrid(config.layoutDimensions.pageInnerHeight):config.layoutDimensions.pageInnerHeight;colDefaultBottom=maxColumnHeight+config.layoutDimensions.colDefaultTop;colMiddle=config.layoutDimensions.colDefaultTop+(maxColumnHeight/2);minFixedPadding=config.minFixedPadding*config.lineHeight;fixedPadding=_roundUpToGrid(minFixedPadding);for(i=0,l=fixedPreloadArea.childNodes.length;i<l;i++){_addFixedElement(fixedPreloadArea.childNodes[i])}for(i=preloadColumn.childNodes.length-1;i>=0;i--){_normaliseFlowedElement(preloadColumn.childNodes[i])}if(!preloadColumn.childNodes.length)return;_createPageObjects(indexedPageNum);workingPage=pagedContent[indexedPageNum];workingColumn=workingPage.columns[indexedColumnNum];workingColumnFrag=workingColumn.fragments[indexedColumnFrag];if(!workingColumnFrag){_advanceWorkingColumnFragment()}totalColumnHeight=workingColumnFrag.height;for(i=0,l=preloadColumn.childNodes.length;i<l;i++){_addFlowedElement(preloadColumn.childNodes[i],i)}_wrapColumn(l-1,false)}function _addFlowedElement(element,index){var originalPadding,existingPadding,totalElementHeight,desiredElementHeight,newPadding,overflow,loopCount,nextElement=element.nextSibling;if(config.standardiseLineHeight){originalPadding=parseInt(element.getAttribute('data-cf-original-padding'),10)||null;existingPadding=parseInt(window.getComputedStyle(element).getPropertyValue('padding-bottom'),10);if(null===originalPadding){originalPadding=existingPadding;element.setAttribute('data-cf-original-padding',originalPadding)}else{existingPadding=originalPadding}if(originalPadding!==existingPadding){element.style.paddingBottom=originalPadding+'px'}totalElementHeight=nextElement?(nextElement.offsetTop-element.offsetTop):element.offsetHeight;desiredElementHeight=_roundUpToGrid(totalElementHeight);newPadding=desiredElementHeight-totalElementHeight+existingPadding;if(newPadding!==existingPadding){element.style.paddingBottom=newPadding+'px'}}element.offsetBottom=element.offsetTop+element.offsetHeight;loopCount=0;while((element.offsetBottom>=totalColumnHeight||(nextElement&&nextElement.offsetTop>=totalColumnHeight))&&(loopCount++<30)){overflow=(element.offsetBottom>totalColumnHeight);_wrapColumn(index,overflow)}if(loopCount>=30)console.error('FTColumnflow: Caught and destroyed a loop when wrapping columns for element',element.outerHTML.substr(0,200)+'...')}function _wrapColumn(currentElementIndex,overflow){var nowrap,keepwithnext,prevElementKeepwithnext,firstInColumn,finalColumnElementIndex,cropCurrentElement,pushElement,pushFromElementIndex,i,lastElement,element,currentElement=preloadColumn.childNodes[currentElementIndex],nextElement=currentElement.nextSibling,prevElement=currentElement.previousSibling;nowrap=currentElement.classList.contains(nowrapClassName);keepwithnext=currentElement.classList.contains(keepwithnextClassName);prevElementKeepwithnext=(prevElement&&prevElement.classList.contains(keepwithnextClassName));if(-1!==config.noWrapOnTags.indexOf(currentElement.tagName.toLowerCase())){nowrap=true}if(!nextElement){lastElement=true}if(currentElementIndex===borderElementIndex){firstInColumn=true}if(currentElement.offsetBottom===totalColumnHeight){overflow=false;if(nextElement)totalColumnHeight=nextElement.offsetTop}if((nowrap||(keepwithnext&&nextElement))&&overflow&&(firstInColumn)){cropCurrentElement=true}if(!cropCurrentElement&&!firstInColumn&&((nowrap&&overflow)||(keepwithnext&&nextElement))){pushElement=true;pushFromElementIndex=currentElementIndex}if((currentElementIndex-1)>borderElementIndex&&prevElementKeepwithnext&&overflow&&nowrap){pushElement=true;pushFromElementIndex=currentElementIndex-1}finalColumnElementIndex=pushElement?pushFromElementIndex-1:currentElementIndex;if(finalColumnElementIndex<borderElementIndex){return}workingColumnFrag.overflow=topElementOverflow;for(i=borderElementIndex;i<=finalColumnElementIndex;i++){element=preloadColumn.childNodes[i];workingColumnFrag.elements.push({content:_outerHTML(element)})}if(pushElement){borderElementIndex=pushFromElementIndex}else if(overflow&&!cropCurrentElement){borderElementIndex=currentElementIndex}else{borderElementIndex=currentElementIndex+1}if(pushElement){totalColumnHeight=preloadColumn.childNodes[pushFromElementIndex].offsetTop}else if(cropCurrentElement&&nextElement){totalColumnHeight=nextElement.offsetTop}if(!overflow||(nowrap||(keepwithnext&&nextElement))){topElementOverflow=0}else{topElementOverflow=totalColumnHeight-currentElement.offsetTop}_advanceWorkingColumnFragment();totalColumnHeight+=workingColumnFrag.height}function _renderFlowedContent(){var outputHTML='',indexedPageNum,page_len,pageHTML,page,i,l,element,indexedColumnNum,column_len,column,indexedColumnFrag,fragLen,el,fragment;for(indexedPageNum=0,page_len=pagedContent.length;indexedPageNum<page_len;indexedPageNum++){pageHTML='';page=pagedContent[indexedPageNum];for(i=0,l=page.fixed.length;i<l;i++){element=page.fixed[i];pageHTML+=element.content}for(indexedColumnNum=0,column_len=page.columns.length;indexedColumnNum<column_len;indexedColumnNum++){column=page.columns[indexedColumnNum];for(indexedColumnFrag=0,fragLen=column.fragments.length;indexedColumnFrag<fragLen;indexedColumnFrag++){fragment=column.fragments[indexedColumnFrag];if(0===fragment.elements.length){continue}pageHTML+=_openColumn(fragment,indexedColumnNum);for(el=0,l=fragment.elements.length;el<l;el++){element=fragment.elements[el];if(el===0){element.content=_addTopMargin(element.content,-fragment.overflow)}pageHTML+=element.content}pageHTML+='</div>'}}if(''===pageHTML){pagedContent.splice(indexedPageNum,1);indexedPageNum--;page_len--;continue}outputHTML+=_openPage(indexedPageNum)+pageHTML+'</div>'}renderArea.innerHTML=outputHTML;that.target.style.width=(config.viewportWidth*page_len)+'px';that.pagedContentCount=pagedContent.length}function _addTopMargin(element,margin){return element.replace(/<(\w+)([^>]*)>/,function _addTopMarginToTag(string,tag,attributes){if(!string.match(/style\s*=/)){string='<'+tag+' style="" '+attributes+'>'}string=string.replace(/style=(["'])/,'style=$1 margin-top:'+margin+'px;');return string})}function _roundDownToGrid(val,addPadding){var resized=val-(val%config.lineHeight);if(addPadding&&((val-resized)<minFixedPadding)){resized-=fixedPadding}return resized}function _roundUpToGrid(val,addPadding){var delta=val%config.lineHeight,resized=(delta?(val-delta+config.lineHeight):val);if(addPadding&&((resized-val)<minFixedPadding)){resized+=fixedPadding}return resized}function _replaceStringTokens(string,tokens){return string.replace(/\[(\w+)\]/g,function _replace(a,b){var r=tokens[b];return typeof r==='string'||typeof r==='number'?r:a})}function _normaliseClassName(type,value){if(typeof value!=='string'){throw new FTColumnflowException('ClassnameException',type+' must be a string.')}return value.replace(/[^\w]/g,'-')}function _createPageObjects(indexedPageNum){var pageNum,indexedColNum;for(pageNum=pagedContent.length;pageNum<=indexedPageNum;pageNum++){pagedContent.push({'fixed':[],'columns':[]});for(indexedColNum=0;indexedColNum<config.layoutDimensions.columnCount;indexedColNum++){pagedContent[pageNum].columns.push({fragments:[_createColumnFragment()]})}}}function _createColumnFragment(){return{elements:[],overflow:0,height:maxColumnHeight,top:config.layoutDimensions.colDefaultTop,bottom:colDefaultBottom}}function _advanceWorkingColumnFragment(){if(!workingColumn.fragments[++indexedColumnFrag]){indexedColumnFrag=0;if(!workingPage.columns[++indexedColumnNum]){indexedColumnNum=0;indexedPageNum++;_createPageObjects(indexedPageNum)}}workingPage=pagedContent[indexedPageNum];workingColumn=workingPage.columns[indexedColumnNum];workingColumnFrag=workingColumn.fragments[indexedColumnFrag];if(!workingColumnFrag){_advanceWorkingColumnFragment()}}function _openPage(indexedPageNum){var pagePos;if('horizontal'===config.pageArrangement){pagePos='left: '+(indexedPageNum*config.viewportWidth)+'px;'}else{pagePos='top: '+(indexedPageNum*config.viewportHeight)+'px;'}return'<div class="'+config.pageClass+' '+config.pageClass+'-'+(indexedPageNum+1)+'" style="'+pagePos+'">'}function _openColumn(column,indexedColumnNum){return'<div class="'+config.columnClass+' '+config.columnClass+'-'+(indexedColumnNum+1)+'" style="height: '+column.height+'px; top: '+column.top+'px;">'}function _mode(array){var modeMap={},maxEl,maxCount,i,el;if(array.length===0){return null}maxEl=array[0];maxCount=1;for(i=0;i<array.length;i++){el=array[i];if(modeMap[el]===undefined){modeMap[el]=1}else{modeMap[el]++}if(modeMap[el]>maxCount){maxEl=el;maxCount=modeMap[el]}}return maxEl}function _log(){if(!debugMode)return;console.log.apply(console,arguments)}this.flow=function(flowed,fixed){flowedContent=flowed;fixedContent=fixed;_writeTargetStyles();_createTargetElements();_findLineHeight();_flowContent();_renderFlowedContent()};this.reflow=function(newConfig){if(newConfig){_setConfig(newConfig)}_setLayoutDimensions();_writeTargetStyles();_findLineHeight();_flowContent();_renderFlowedContent()};this.destroy=function(){if(headerStyles){headerStyles.parentNode.removeChild(headerStyles);headerStyles=null}if(that.target){that.target.parentNode.removeChild(that.target);that.target=null}}}FTColumnflow.prototype={get layoutDimensions(){return this.config.layoutDimensions},set layoutDimensions(value){throw new FTColumnflowException('SetterException','Setter not defined for layoutDimensions.')},get pageClass(){return this.config.pageClass},set pageClass(value){throw new FTColumnflowException('SetterException','Setter not defined for pageClass.')},get columnClass(){return this.config.columnClass},set columnClass(value){throw new FTColumnflowException('SetterException','Setter not defined for columnClass.')},get pageCount(){return this.pagedContentCount},set pageCount(value){throw new FTColumnflowException('SetterException','Setter not defined for pageCount.')},_checkInstanceArgs:function(){var that=this;['target','viewport'].forEach(function _checkArg(name){var arg=that[name];switch(typeof arg){case'string':arg=document.getElementById(arg);if(!arg)throw new FTColumnflowException('SelectorException',name+' must be a valid DOM element.');break;case'object':if(!(arg instanceof HTMLElement)){throw new FTColumnflowException('ParameterException',name+' must be a string ID or DOM element.')}break;default:throw new FTColumnflowException('ParameterException',name+' must be a string ID or DOM element.')}that[name]=arg});if(!this._isDescendant(this.viewport,this.target)){throw new FTColumnflowException('InheritanceException','Target element must be a child of the viewport.')}this.target.innerHTML=''},_isDescendant:function(parent,child){var node=child.parentNode;while(node!==null){if(node===parent){return true}node=node.parentNode}return false}};return FTColumnflow}());
@@ -0,0 +1,1409 @@
1
+ /**
2
+ * FTColumnflow is a polyfill that fixes the inadequacies of CSS column layouts.
3
+ *
4
+ * It is developed by FT Labs (http://labs.ft.com), part of the Financial Times.
5
+ * It is extensively used in the FT Web App (http://app.ft.com), where it allows us to
6
+ * publish articles with complex newspaper/magazine style layouts, including features such as:
7
+ * - Configurable column widths, gutters and margins
8
+ * - Fixed-position elements
9
+ * - Elements spanning columns
10
+ * - Keep-with-next to avoid headings at the bottom of a column
11
+ * - No-wrap class to avoid splitting elements across columns
12
+ * - Grouping of columns into pages
13
+ * - Horizontal or vertical alignment of pages
14
+ * - Standardised line height to align text baseline to a grid
15
+ * - Rapid reflow as required by events such as device orientation or font-size change
16
+ * It is designed with the same column dimension specification API as the CSS3 multi-column specification
17
+ * (http://www.w3.org/TR/css3-multicol/), but gives far greater flexibility over element positioning within those columns.
18
+ *
19
+ * @codingstandard ftlabs-jslint
20
+ * @copyright The Financial Times Limited [All Rights Reserved]
21
+ */
22
+
23
+ this.FTColumnflow = (function () {
24
+
25
+ "use strict";
26
+
27
+
28
+ // Set up FTColumnflowException class
29
+ function FTColumnflowException(name, message) {
30
+ this.name = 'FTColumnflow' + name || 'FTColumnflowException';
31
+ this.message = message || '';
32
+ }
33
+ FTColumnflowException.prototype = new Error();
34
+ FTColumnflowException.constructor = FTColumnflowException;
35
+
36
+
37
+
38
+ /* Scope vars */
39
+
40
+ var
41
+
42
+ layoutDimensionList = [
43
+ 'pageInnerWidth',
44
+ 'pageInnerHeight',
45
+ 'colDefaultTop',
46
+ 'colDefaultLeft',
47
+ 'columnCount',
48
+ 'columnWidth',
49
+ 'columnGap'
50
+ ],
51
+
52
+ defaultConfig = {
53
+ layoutDimensions: null,
54
+ columnFragmentMinHeight: 0,
55
+ viewportWidth: null,
56
+ viewportHeight: null,
57
+ columnWidth: 'auto',
58
+ columnCount: 'auto',
59
+ columnGap: 'normal',
60
+ pageClass: 'cf-page',
61
+ columnClass: 'cf-column',
62
+ pageArrangement: 'horizontal',
63
+ pagePadding: 0,
64
+ debug: false,
65
+ showGrid: false,
66
+ standardiseLineHeight: false,
67
+ minFixedPadding: 1,
68
+ lineHeight: null,
69
+ noWrapOnTags: []
70
+ },
71
+
72
+ // CSS Style declarations
73
+ cssStyles = '#[targetId] { position: relative; height: 100%; }\n'
74
+ + '#[targetId] .[preloadAreaClassName].[pageClass] { visibility: hidden; position: absolute; overflow: hidden; }\n'
75
+ + '#[targetId] .[preloadFixedAreaClassName] { visibility: hidden; position: absolute; }\n'
76
+ + '#[targetId] .[pageClass] { position: absolute; width: [viewportWidth]px; height: [viewportHeight]px; [pageArrangement] }\n'
77
+ + '#[targetId] .[columnClass] { position: absolute; width: [columnWidth]px; overflow: hidden; }\n'
78
+ + '#[targetId] .[pageClass] .[fixedElementClassName] { position: absolute; }\n'
79
+ + '#[targetId] .[pageClass] .[columnClass] > :first-child { margin-top: 0px; }\n',
80
+
81
+ cssColumnStyles = '#[targetId] .[columnClass].[columnClass]-[columnNum] { left: [leftPos]px; }\n',
82
+
83
+ showGridStyles = '#[targetId] .[pageClass] { background-image: -webkit-linear-gradient(skyblue 1px, transparent 1px); background-size: 100% [lh]px; background-origin: content-box; }',
84
+
85
+
86
+ // Implement outerHTML in browsers which don't support it
87
+ // Adapted from Modernizr's outerHTML polyfill: bit.ly/polyfills
88
+ _outerHTML = (function() {
89
+ var outerHTMLXmlSerializer, outerHTMLContainer, html;
90
+ if (typeof document !== "undefined" && !document.createElementNS("http://www.w3.org/1999/xhtml", "_").hasOwnProperty('outerHTML')) {
91
+ if (document.xmlVersion) {
92
+ outerHTMLXmlSerializer = new XMLSerializer();
93
+ return function _outerHTMLXML(node) {
94
+ return outerHTMLXmlSerializer.serializeToString(node);
95
+ };
96
+ } else {
97
+ outerHTMLContainer = document.createElementNS("http://www.w3.org/1999/xhtml", "_");
98
+ return function _outerHTMLNode(node) {
99
+ var html;
100
+ outerHTMLContainer.appendChild(node.cloneNode(false));
101
+ html = outerHTMLContainer.innerHTML.replace("><", ">" + node.innerHTML + "<");
102
+ outerHTMLContainer.innerHTML = "";
103
+ return html;
104
+ };
105
+ }
106
+ } else {
107
+ return function _outerHTMLNative(node) {
108
+ return node.outerHTML;
109
+ };
110
+ }
111
+ }());
112
+
113
+
114
+
115
+ function FTColumnflow(target, viewport, userConfig) {
116
+
117
+ var
118
+
119
+ // Instance configuration
120
+ config = {},
121
+
122
+ // Debugging
123
+ debugMode,
124
+ showGrid,
125
+
126
+ // String or DOM node
127
+ flowedContent,
128
+ fixedContent,
129
+
130
+ // Dimensions
131
+ maxColumnHeight,
132
+ colDefaultBottom,
133
+ colMiddle,
134
+ minFixedPadding,
135
+ fixedPadding,
136
+
137
+ // DOM elements
138
+ renderArea,
139
+ preloadColumn,
140
+ fixedPreloadArea,
141
+ headElement,
142
+ headerStyles,
143
+
144
+ // Class names
145
+ targetIdPrefix = 'cf-target-',
146
+ renderAreaClassName = 'cf-render-area',
147
+ preloadAreaClassName = 'cf-preload',
148
+ preloadFixedAreaClassName = 'cf-preload-fixed',
149
+ fixedElementClassName = 'cf-fixed',
150
+ nowrapClassName = 'nowrap',
151
+ keepwithnextClassName = 'keepwithnext',
152
+
153
+ // HTML fragments
154
+ lineHeightTestContents,
155
+
156
+ // Collections
157
+ pagedContent = [],
158
+
159
+ // Counters
160
+ borderElementIndex,
161
+ indexedPageNum,
162
+ indexedColumnNum,
163
+ indexedColumnFrag,
164
+ topElementOverflow,
165
+ totalColumnHeight,
166
+
167
+ // Object references
168
+ workingPage,
169
+ workingColumn,
170
+ workingColumnFrag,
171
+
172
+ // Copy of parent scope
173
+ that = this;
174
+
175
+
176
+ /* Constructor */
177
+
178
+ this.target = target;
179
+ this.viewport = viewport;
180
+
181
+ this._checkInstanceArgs();
182
+
183
+ _setConfig(userConfig);
184
+
185
+ _setLayoutDimensions();
186
+
187
+
188
+
189
+ function _setConfig(userConfig) {
190
+ var i;
191
+
192
+ // Reference local config as public property
193
+ that.config = config = {};
194
+
195
+ // Copy defaultConfig settings into config
196
+ for (i in defaultConfig) {
197
+ if (defaultConfig.hasOwnProperty(i)) {
198
+ config[i] = defaultConfig[i];
199
+ }
200
+ }
201
+
202
+ // Merge userConfig settings into config
203
+ for (i in userConfig) {
204
+ if (userConfig.hasOwnProperty(i)) {
205
+ if (config[i] === undefined) {
206
+ throw new FTColumnflowException('ParameterException', 'Unknown config parameter [' + i + '].');
207
+ }
208
+ config[i] = userConfig[i];
209
+ }
210
+ }
211
+
212
+ // Enable logging?
213
+ if (config.debug !== undefined) debugMode = !!config.debug;
214
+
215
+ // Enable showGrid?
216
+ if (config.showGrid !== undefined) showGrid = !!config.showGrid;
217
+
218
+ // Check page params
219
+ if ('horizontal' !== config.pageArrangement && 'vertical' !== config.pageArrangement) {
220
+ throw new FTColumnflowException('ArrangementException', config.pageArrangement + ' is not a valid Page Arrangement value.');
221
+ }
222
+
223
+ config.pagePadding = parseInt(config.pagePadding, 10);
224
+ if (isNaN(config.pagePadding)) {
225
+ throw new FTColumnflowException('PaddingException', config.pagePadding + ' is not a valid Page Padding value.');
226
+ }
227
+
228
+ // Check column dimension specifications are valid
229
+ ['columnWidth', 'columnCount', 'columnGap', 'columnFragmentMinHeight'].forEach(function _checkColumnSpecification(type) {
230
+
231
+ if (defaultConfig[type] === config[type]) return true;
232
+ config[type] = parseInt(config[type], 10);
233
+
234
+ if (isNaN(config[type]) || config[type] < 0) {
235
+ throw new FTColumnflowException('ColumnDimensionException', type + ' must be an positive integer or "' + defaultConfig[type] + '".');
236
+ }
237
+ });
238
+
239
+ // Check standardiseLineHeight
240
+ if (typeof config.standardiseLineHeight !== 'boolean') {
241
+ throw new FTColumnflowException('StandardiseLineheightException', 'standardiseLineHeight must be a boolean value.');
242
+ }
243
+
244
+ // Check minFixedPadding
245
+ config.minFixedPadding = parseFloat(config.minFixedPadding);
246
+ if (isNaN(config.minFixedPadding)) {
247
+ throw new FTColumnflowException('MinFixedPaddingException', 'minFixedPadding must be a float or integer.');
248
+ }
249
+
250
+ // Check viewportWidth, if specified
251
+ if ((config.viewportWidth !== null) && isNaN(config.viewportWidth = parseInt(config.viewportWidth, 10))) {
252
+ throw new FTColumnflowException('ViewportWidthException', 'viewportWidth must be an integer.');
253
+ }
254
+
255
+ // Check viewportHeight, if specified
256
+ if ((config.viewportHeight !== null) && isNaN(config.viewportHeight = parseInt(config.viewportHeight, 10))) {
257
+ throw new FTColumnflowException('ViewportHeightException', 'viewportHeight must be an integer.');
258
+ }
259
+
260
+ // Check lineHeight, if specified
261
+ if ((config.lineHeight !== null) && isNaN(config.lineHeight = parseInt(config.lineHeight, 10))) {
262
+ throw new FTColumnflowException('LineheightException', 'lineHeight must be an integer.');
263
+ }
264
+
265
+ // Check class names are valid
266
+ config.pageClass = _normaliseClassName('pageClass', config.pageClass);
267
+ config.columnClass = _normaliseClassName('columnClass', config.columnClass);
268
+
269
+ // Check noWrapOnTags
270
+ if (!Array.isArray(config.noWrapOnTags)) {
271
+ throw new FTColumnflowException('NoWrapException', 'noWrapOnTags must be an Array.');
272
+ }
273
+
274
+ // Ensure tags are lowercase
275
+ config.noWrapOnTags = config.noWrapOnTags.map(function _lowercase(item) {
276
+ return item.toLowerCase();
277
+ });
278
+ }
279
+
280
+
281
+ function _setLayoutDimensions() {
282
+
283
+ var i, l, derivedColumnCount;
284
+
285
+ // If the layoutDimensions parameter was passed in
286
+ if (config.layoutDimensions !== null) {
287
+
288
+ // Check each one
289
+ for (i = 0, l = layoutDimensionList.length; i < l; i++) {
290
+ if (isNaN(Number(config.layoutDimensions[layoutDimensionList[i]]))) {
291
+ throw new FTColumnflowException('DimensionCacheException', 'Must specify an integer value for ' + layoutDimensionList[i]);
292
+ }
293
+ }
294
+
295
+ // Done
296
+ return;
297
+ }
298
+
299
+ config.layoutDimensions = {};
300
+
301
+ // Determine viewport dimensions if they have not been specified
302
+ if (!config.viewportWidth) {
303
+ config.viewportWidth = parseInt(window.getComputedStyle(that.viewport).getPropertyValue('width'), 10);
304
+ }
305
+
306
+ if (!config.viewportHeight) {
307
+ config.viewportHeight = parseInt(window.getComputedStyle(that.viewport).getPropertyValue('height'), 10);
308
+ }
309
+
310
+ if (!config.viewportWidth || !config.viewportHeight) {
311
+ throw new FTColumnflowException('ViewportException', 'Viewport element must have width and height.');
312
+ }
313
+
314
+ // Determine column gap - 'normal' defaults to 1em
315
+ config.layoutDimensions.columnGap = ('normal' === config.columnGap) ? parseInt(window.getComputedStyle(that.viewport).fontSize, 10) : config.columnGap;
316
+
317
+ // Determine page dimensions
318
+ if ('horizontal' === config.pageArrangement) {
319
+ config.layoutDimensions.pageInnerWidth = config.viewportWidth - (2 * config.pagePadding);
320
+ config.layoutDimensions.pageInnerHeight = config.viewportHeight;
321
+ config.layoutDimensions.colDefaultTop = 0;
322
+ config.layoutDimensions.colDefaultLeft = config.pagePadding;
323
+ } else {
324
+ config.layoutDimensions.pageInnerWidth = config.viewportWidth;
325
+ config.layoutDimensions.pageInnerHeight = config.viewportHeight - (2 * config.pagePadding);
326
+ config.layoutDimensions.colDefaultTop = config.pagePadding;
327
+ config.layoutDimensions.colDefaultLeft = 0;
328
+ }
329
+
330
+ // Determine columns per page and column dimensions.
331
+ // For logic, see pseudo-code at http://www.w3.org/TR/css3-multicol/#pseudo-algorithm
332
+ if ('auto' === config.columnWidth && 'auto' === config.columnCount) {
333
+
334
+ // Auto - default to 1 column
335
+ config.layoutDimensions.columnCount = 1;
336
+ config.layoutDimensions.columnWidth = config.layoutDimensions.pageInnerWidth;
337
+ } else if ('auto' === config.columnWidth && 'auto' !== config.columnCount) {
338
+
339
+ // Determine column width from specified column count and page width
340
+ config.layoutDimensions.columnCount = config.columnCount;
341
+ config.layoutDimensions.columnWidth = (config.layoutDimensions.pageInnerWidth - ((config.columnCount - 1) * config.layoutDimensions.columnGap)) / config.columnCount;
342
+
343
+ // Column width is specified.
344
+ } else {
345
+
346
+ // Derive the optimal column count
347
+ // COMPLEX:GC:20120312: Add 1px to the page width to avoid precision errors in the case that the config values
348
+ // result in a column count very near to a whole number
349
+ derivedColumnCount = Math.max(1, Math.floor((config.layoutDimensions.pageInnerWidth + 1 + config.layoutDimensions.columnGap) / (config.columnWidth + config.layoutDimensions.columnGap)));
350
+
351
+ if ('auto' === config.columnCount) {
352
+
353
+ // Use the derived count
354
+ config.layoutDimensions.columnCount = derivedColumnCount;
355
+ config.layoutDimensions.columnWidth = ((config.layoutDimensions.pageInnerWidth + config.layoutDimensions.columnGap) / config.layoutDimensions.columnCount) - config.layoutDimensions.columnGap;
356
+ } else {
357
+
358
+ // Count is specified, but we may be able to fit more
359
+ config.layoutDimensions.columnCount = Math.min(config.columnCount, derivedColumnCount);
360
+ config.layoutDimensions.columnWidth = ((config.layoutDimensions.pageInnerWidth + config.layoutDimensions.columnGap) / config.layoutDimensions.columnCount) - config.layoutDimensions.columnGap;
361
+ }
362
+ }
363
+ }
364
+
365
+
366
+ /* Add the necessary CSS to <head> */
367
+
368
+ function _writeTargetStyles() {
369
+
370
+ var styleContents, columnNum;
371
+
372
+ if (!headElement) headElement = document.querySelector('head');
373
+
374
+ // Set a random ID on the target
375
+ if (!that.target.id) that.target.id = targetIdPrefix + Math.floor(Math.random() * (9000000000) + 1000000000);
376
+
377
+ // Main styles
378
+ styleContents = _replaceStringTokens(cssStyles, {
379
+ targetId: that.target.id,
380
+ preloadAreaClassName: preloadAreaClassName,
381
+ preloadFixedAreaClassName: preloadFixedAreaClassName,
382
+ fixedElementClassName: fixedElementClassName,
383
+ pageClass: config.pageClass,
384
+ columnClass: config.columnClass,
385
+ columnWidth: config.layoutDimensions.columnWidth,
386
+ viewportWidth: config.viewportWidth,
387
+ viewportHeight: config.viewportHeight,
388
+ pageArrangement: ('horizontal' === config.pageArrangement) ? 'top: 0;' : 'left: 0;'
389
+ });
390
+
391
+ // Column-specific styles
392
+ for (columnNum = 1; columnNum <= config.layoutDimensions.columnCount; columnNum++) {
393
+
394
+ styleContents += _replaceStringTokens(cssColumnStyles, {
395
+ targetId: that.target.id,
396
+ columnClass: config.columnClass,
397
+ columnNum: columnNum,
398
+ leftPos: config.layoutDimensions.colDefaultLeft + (config.layoutDimensions.columnWidth * (columnNum - 1)) + (config.layoutDimensions.columnGap * (columnNum - 1))
399
+ });
400
+ }
401
+
402
+ if (headerStyles) {
403
+
404
+ // Remove existing CSS
405
+ while (headerStyles.hasChildNodes()) {
406
+ headerStyles.removeChild(headerStyles.firstChild);
407
+ }
408
+
409
+ } else {
410
+ headerStyles = document.createElement('style');
411
+ headerStyles.setAttribute('type', 'text/css');
412
+ }
413
+
414
+ headerStyles.appendChild(document.createTextNode(styleContents));
415
+ headElement.appendChild(headerStyles);
416
+ }
417
+
418
+ function _createTargetElements() {
419
+
420
+ var preloadElement;
421
+
422
+ // Create the preload and render areas
423
+ that.target.innerHTML = '<div class="' + preloadAreaClassName + ' ' + config.pageClass + '">'
424
+ + '<div class="' + config.columnClass + '"></div>'
425
+ + '</div>'
426
+ + '<div class="' + preloadFixedAreaClassName + '"></div>'
427
+ + '<div class="' + renderAreaClassName + '"></div>';
428
+
429
+ renderArea = that.target.getElementsByClassName(renderAreaClassName)[0];
430
+
431
+ // Add the flowed content to the preload area
432
+ preloadElement = that.target.getElementsByClassName(preloadAreaClassName)[0];
433
+ preloadColumn = preloadElement.getElementsByClassName(config.columnClass)[0];
434
+
435
+ if ('string' === typeof flowedContent) {
436
+ preloadColumn.innerHTML = flowedContent;
437
+ } else if (flowedContent instanceof HTMLElement) {
438
+ preloadColumn.innerHTML = flowedContent.innerHTML;
439
+ } else if (flowedContent) {
440
+ throw new FTColumnflowException('FlowedContentException', 'flowedContent must be a HTML string or a DOM element.');
441
+ }
442
+
443
+ // Add the fixed content to the preload area
444
+ fixedPreloadArea = that.target.getElementsByClassName(preloadFixedAreaClassName)[0];
445
+
446
+ if ('string' === typeof fixedContent) {
447
+ fixedPreloadArea.innerHTML = fixedContent;
448
+ } else if (fixedContent instanceof HTMLElement) {
449
+ fixedPreloadArea.innerHTML = fixedContent.innerHTML;
450
+ } else if (fixedContent) {
451
+ throw new FTColumnflowException('FixedContentException', 'fixedContent must be a HTML string or a DOM element.');
452
+ }
453
+ }
454
+
455
+
456
+ /* Determine the baseline grid for the flowed content from the line-height */
457
+
458
+ function _findLineHeight() {
459
+
460
+ var lineHeights, i, l, node, testNode, testLineHeight;
461
+
462
+ // If the grid height is not pre-set, we need to determine it
463
+ if (!config.lineHeight) {
464
+
465
+ if (!lineHeightTestContents) {
466
+
467
+ // 10 lines of text
468
+ lineHeightTestContents = new Array(10).join('x<br />') + 'x';
469
+ }
470
+
471
+ // Here we take the mode (most common) line-height value from the first few
472
+ // elements (maximum 10), and assume that that is our desired baseline grid value
473
+ lineHeights = [];
474
+ for (i = 0, l = Math.min(10, (preloadColumn.childNodes.length)); i < l; i++) {
475
+
476
+ node = preloadColumn.childNodes[i];
477
+ if (Node.ELEMENT_NODE !== node.nodeType) continue;
478
+
479
+ testLineHeight = parseInt(window.getComputedStyle(node).getPropertyValue('line-height'), 10);
480
+
481
+ // We haven't found a pixel lineheight, so it must be 'normal' or 'inherit'. Unless there's
482
+ // a better way of doing this, for now we just create a (similar) element at the end of the
483
+ // column with 10 lines of text, and measure its height
484
+ if (!testLineHeight) {
485
+
486
+ testNode = node.cloneNode(false);
487
+
488
+ if (node.className) testNode.className = node.className;
489
+ testNode.style.padding = "0px";
490
+ testNode.style.border = "none";
491
+ testNode.style.height = "auto";
492
+ testNode.innerHTML = lineHeightTestContents;
493
+
494
+ preloadColumn.appendChild(testNode);
495
+ testLineHeight = testNode.offsetHeight / 10;
496
+ preloadColumn.removeChild(testNode);
497
+ }
498
+
499
+ lineHeights.push(testLineHeight);
500
+ }
501
+
502
+ if (lineHeights.length < 5) {
503
+
504
+ // If we haven't yet build up a large enough sample, add some simple paragraphs
505
+ testNode = document.createElement('p');
506
+
507
+ testNode.style.padding = "0px";
508
+ testNode.style.border = "none";
509
+ testNode.style.height = "auto";
510
+ testNode.innerHTML = lineHeightTestContents;
511
+
512
+ preloadColumn.appendChild(testNode);
513
+ testLineHeight = testNode.offsetHeight / 10;
514
+ preloadColumn.removeChild(testNode);
515
+
516
+ for (i = lineHeights.length; i < 5; i++) lineHeights.push(testLineHeight);
517
+ }
518
+
519
+ config.lineHeight = _mode(lineHeights);
520
+ }
521
+
522
+ // For debugging, show the grid lines with CSS
523
+ if (showGrid) {
524
+
525
+ headerStyles.innerHTML += _replaceStringTokens(showGridStyles, {
526
+ targetId: that.target.id,
527
+ pageClass: config.pageClass,
528
+ 'lh': config.lineHeight
529
+ });
530
+ }
531
+ }
532
+
533
+
534
+ function _addFixedElement(element) {
535
+
536
+ var indexedColStart,
537
+ indexedColEnd,
538
+ anchorY,
539
+ anchorX,
540
+ elementTopPos,
541
+ elementBottomPos,
542
+ lowestTopPos,
543
+ highestBottomPos,
544
+ topSplitPoint,
545
+ bottomSplitPoint,
546
+ matches,
547
+ indexedPageNum,
548
+ spanDir,
549
+ colSpan,
550
+ normalisedElementHeight,
551
+ columnNum,
552
+ firstColFragment,
553
+ lastColFragment,
554
+ newColumnFragment,
555
+ newFragmentHeight,
556
+ fragment,
557
+ column,
558
+ fragNum,
559
+ fragLen;
560
+
561
+
562
+ // Don't do any manipulation on text nodes, or nodes which are hidden
563
+ // TODO: getComputedStyle() will trigger a render, so do all these calls at once if possible.
564
+ if (Node.TEXT_NODE === element.nodeType || ('none' === window.getComputedStyle(element).getPropertyValue('display'))) {
565
+ return;
566
+ }
567
+
568
+ element.classList.add(fixedElementClassName);
569
+
570
+ // Determine the page
571
+ matches = element.className.match(/(\s|^)attach-page-(\d+)(\s|$)/);
572
+ if (matches) {
573
+ indexedPageNum = matches[2] - 1;
574
+ } else {
575
+ indexedPageNum = 0;
576
+ }
577
+
578
+ // Create any necessary page objects
579
+ _createPageObjects(indexedPageNum);
580
+ workingPage = pagedContent[indexedPageNum];
581
+
582
+ // Determine the anchor point
583
+ matches = element.className.match(/(\s|^)anchor-(top|middle|bottom)-(left|right|(?:col-(\d+)))(\s|$)/);
584
+ if (matches) {
585
+
586
+ anchorY = matches[2];
587
+
588
+ if (matches[4]) {
589
+
590
+ // A numeric column anchor
591
+ anchorX = Math.max(0, (Math.min(matches[4], config.layoutDimensions.columnCount) - 1));
592
+ } else {
593
+
594
+ // Left or right
595
+ anchorX = matches[3];
596
+ }
597
+
598
+ } else {
599
+ anchorY = 'top';
600
+ anchorX = 'left';
601
+ }
602
+
603
+ // Determine the affected columns
604
+ matches = element.className.match(/(\s|^)col-span-(\d+|all)(-(left|right))?(\s|$)/);
605
+ if (matches) {
606
+
607
+ spanDir = matches[4] || 'right';
608
+
609
+ if (matches[2] === 'all') {
610
+ colSpan = config.layoutDimensions.columnCount;
611
+ } else {
612
+ colSpan = parseInt(matches[2], 10);
613
+ }
614
+
615
+ if ('left' === anchorX) {
616
+ indexedColStart = 0;
617
+ indexedColEnd = Math.min(colSpan, config.layoutDimensions.columnCount) - 1;
618
+ } else if ('right' === anchorX) {
619
+ indexedColEnd = config.layoutDimensions.columnCount - 1;
620
+ indexedColStart = config.layoutDimensions.columnCount - Math.min(colSpan, config.layoutDimensions.columnCount);
621
+ } else {
622
+ if ('right' === spanDir) {
623
+ indexedColStart = anchorX;
624
+ indexedColEnd = Math.min((indexedColStart + colSpan), config.layoutDimensions.columnCount) - 1;
625
+ } else {
626
+ indexedColEnd = anchorX;
627
+ indexedColStart = Math.max((indexedColEnd - colSpan - 1), 0);
628
+ }
629
+ }
630
+ } else {
631
+
632
+ if ('left' === anchorX) {
633
+ indexedColStart = 0;
634
+ } else if ('right' === anchorX) {
635
+ indexedColStart = config.layoutDimensions.columnCount - 1;
636
+ } else {
637
+ indexedColStart = anchorX;
638
+ }
639
+
640
+ indexedColEnd = indexedColStart;
641
+ }
642
+
643
+
644
+ // Set an explicit width to that of the columnspan attribute
645
+ element.style.width = ((indexedColEnd - indexedColStart) * (config.layoutDimensions.columnWidth + config.layoutDimensions.columnGap)) + config.layoutDimensions.columnWidth + 'px';
646
+
647
+ // Determine the height of the element, taking into account any vertical shift applied to it using margin-top
648
+ // TODO: getComputedStyle() will trigger a render, so do all these calls at once if possible.
649
+ normalisedElementHeight = element.offsetHeight + parseInt(window.getComputedStyle(element).getPropertyValue('margin-top'), 10);
650
+
651
+ // Find the most appropriate available space for the element on the page
652
+ switch (anchorY) {
653
+
654
+ case 'top':
655
+ elementTopPos = config.layoutDimensions.colDefaultTop;
656
+ lowestTopPos = colDefaultBottom - normalisedElementHeight;
657
+
658
+ for (columnNum = indexedColStart; columnNum <= indexedColEnd; columnNum++) {
659
+
660
+ // Find the topmost column fragment
661
+ firstColFragment = workingPage.columns[columnNum].fragments[0];
662
+
663
+ if (!firstColFragment) {
664
+
665
+ // Column is full, so place element at the bottom
666
+ elementTopPos = lowestTopPos;
667
+ } else {
668
+
669
+ // If the fragment starts below the element top position, move the element down
670
+ if (firstColFragment.top > elementTopPos) {
671
+ elementTopPos = (firstColFragment.top > lowestTopPos) ? lowestTopPos : firstColFragment.top;
672
+ }
673
+ }
674
+ }
675
+ elementBottomPos = elementTopPos + normalisedElementHeight;
676
+ topSplitPoint = elementTopPos - config.lineHeight;
677
+ bottomSplitPoint = _roundUpToGrid(elementBottomPos, true);
678
+ break;
679
+
680
+ case 'middle':
681
+ elementTopPos = colMiddle - (normalisedElementHeight / 2);
682
+ topSplitPoint = _roundDownToGrid(elementTopPos, true);
683
+ bottomSplitPoint = _roundUpToGrid(elementTopPos + normalisedElementHeight, true);
684
+
685
+ if (topSplitPoint < 0) topSplitPoint = 0;
686
+ if (bottomSplitPoint > maxColumnHeight) bottomSplitPoint = maxColumnHeight;
687
+ break;
688
+
689
+ case 'bottom':
690
+ elementBottomPos = colDefaultBottom;
691
+ highestBottomPos = normalisedElementHeight;
692
+
693
+ for (columnNum = indexedColStart; columnNum <= indexedColEnd; columnNum++) {
694
+
695
+ // Find the bottommost column fragment
696
+ lastColFragment = workingPage.columns[columnNum].fragments[workingPage.columns[columnNum].fragments.length - 1];
697
+
698
+ if (!lastColFragment) {
699
+
700
+ // Column is full, so place element at the top
701
+ elementBottomPos = highestBottomPos;
702
+ } else {
703
+
704
+ // If the fragment ends above the element bottom position, move the element up
705
+ if (lastColFragment.bottom < elementBottomPos) {
706
+ elementBottomPos = (lastColFragment.bottom < highestBottomPos) ? highestBottomPos : lastColFragment.bottom;
707
+ }
708
+ }
709
+ }
710
+
711
+ elementTopPos = elementBottomPos - normalisedElementHeight;
712
+ topSplitPoint = _roundDownToGrid(elementTopPos, true);
713
+ bottomSplitPoint = elementBottomPos + config.lineHeight;
714
+ break;
715
+ }
716
+
717
+
718
+ /* Alter dimensions and placing of any affected column fragments. */
719
+
720
+ // Loop the columns spanned by the element
721
+ for (columnNum = indexedColStart; columnNum <= indexedColEnd; columnNum++) {
722
+
723
+ column = workingPage.columns[columnNum];
724
+
725
+ // Loop the fragments
726
+ for (fragNum = 0, fragLen = column.fragments.length; fragNum < fragLen; fragNum++) {
727
+
728
+ fragment = column.fragments[fragNum];
729
+
730
+ // The fragment is entirely overlapped by the fixed element, so delete it and continue the loop
731
+ if (topSplitPoint < fragment.top && bottomSplitPoint > fragment.bottom) {
732
+ column.fragments.splice(fragNum, 1);
733
+ fragLen--;
734
+ continue;
735
+ } else if (topSplitPoint > fragment.bottom || bottomSplitPoint < fragment.top) {
736
+
737
+ // The fragment is not disturbed by the element at all
738
+ continue;
739
+ }
740
+
741
+ // Determine the height of the new fragment
742
+ newFragmentHeight = fragment.top + fragment.height - bottomSplitPoint;
743
+
744
+ // Modify the original column fragment
745
+ fragment.height = topSplitPoint - fragment.top;
746
+ fragment.bottom = fragment.top + fragment.height;
747
+
748
+
749
+ if (!fragment.height || fragment.height < config.columnFragmentMinHeight) {
750
+
751
+ // The fragment is now too small, so delete it and decrement the iteration counter
752
+ column.fragments.splice(fragNum--, 1);
753
+ fragLen--;
754
+ }
755
+
756
+ // Only create the new fragment if it has enough height
757
+ if (newFragmentHeight && newFragmentHeight >= config.columnFragmentMinHeight) {
758
+
759
+ // Create a new column fragment
760
+ newColumnFragment = _createColumnFragment();
761
+
762
+ newColumnFragment.top = bottomSplitPoint;
763
+ newColumnFragment.height = newFragmentHeight;
764
+ newColumnFragment.bottom = newColumnFragment.top + newColumnFragment.height;
765
+
766
+ // Insert it into the collection, and increment the iteration counter
767
+ column.fragments.splice(++fragNum, 0, newColumnFragment);
768
+ fragNum++;
769
+ fragLen++;
770
+ }
771
+ }
772
+ }
773
+
774
+ // Position the element correctly
775
+ element.style.top = elementTopPos + 'px';
776
+ element.style.left = (config.layoutDimensions.colDefaultLeft + (('left' === anchorX) ? 0 : ((config.layoutDimensions.columnWidth + config.layoutDimensions.columnGap) * indexedColStart))) + 'px';
777
+
778
+ // Save the fixed content
779
+ workingPage.fixed.push({
780
+ content: _outerHTML(element)
781
+ });
782
+ }
783
+
784
+ function _normaliseFlowedElement(element) {
785
+
786
+ var p;
787
+
788
+ if (Node.TEXT_NODE !== element.nodeType) return;
789
+
790
+ if (element.nodeValue.match(/^\s*$/)) {
791
+
792
+ // A plain text node, containing only white space
793
+ element.parentNode.removeChild(element);
794
+
795
+ } else {
796
+
797
+ // A plain text node, containing more than just white space.
798
+ // Wrap it in a <p> tag
799
+ p = document.createElement('p');
800
+
801
+ p.appendChild(document.createTextNode(element.nodeValue));
802
+ element.parentNode.replaceChild(p, element);
803
+ }
804
+ }
805
+
806
+
807
+ function _flowContent() {
808
+
809
+ var i, l;
810
+
811
+ // Initialise some variables
812
+ pagedContent = [];
813
+
814
+ indexedPageNum =
815
+ indexedColumnNum =
816
+ indexedColumnFrag =
817
+ borderElementIndex =
818
+ indexedPageNum =
819
+ indexedColumnNum =
820
+ indexedColumnFrag =
821
+ topElementOverflow =
822
+ totalColumnHeight = 0;
823
+
824
+ // Set the maximum column height to a multiple of the lineHeight
825
+ maxColumnHeight = config.lineHeight ? _roundDownToGrid(config.layoutDimensions.pageInnerHeight) : config.layoutDimensions.pageInnerHeight;
826
+ colDefaultBottom = maxColumnHeight + config.layoutDimensions.colDefaultTop;
827
+ colMiddle = config.layoutDimensions.colDefaultTop + (maxColumnHeight / 2);
828
+ minFixedPadding = config.minFixedPadding * config.lineHeight;
829
+ fixedPadding = _roundUpToGrid(minFixedPadding);
830
+
831
+ // Add each fixed element to a page in the correct position, and determine the remaining free space for columns
832
+ for (i = 0, l = fixedPreloadArea.childNodes.length; i < l; i++) {
833
+ _addFixedElement(fixedPreloadArea.childNodes[i]);
834
+ }
835
+
836
+ /* Loop through the preload elements, and determine which column to put them in */
837
+
838
+ // Preliminary loop to remove whitespace, and wrap plain text nodes
839
+ for (i = preloadColumn.childNodes.length - 1; i >= 0; i--) {
840
+ _normaliseFlowedElement(preloadColumn.childNodes[i]);
841
+ }
842
+
843
+ if (!preloadColumn.childNodes.length) return;
844
+
845
+ // Select the first available column for content
846
+ _createPageObjects(indexedPageNum);
847
+
848
+ workingPage = pagedContent[indexedPageNum];
849
+ workingColumn = workingPage.columns[indexedColumnNum];
850
+ workingColumnFrag = workingColumn.fragments[indexedColumnFrag];
851
+
852
+ if (!workingColumnFrag) {
853
+ _advanceWorkingColumnFragment();
854
+ }
855
+
856
+ // Start with the free space in the first available column
857
+ totalColumnHeight = workingColumnFrag.height;
858
+
859
+ // TODO:GC: Save these measurements, so there's no need to re-measure
860
+ // when we return to an orientation we've already rendered.
861
+ for (i = 0, l = preloadColumn.childNodes.length; i < l; i++) {
862
+ _addFlowedElement(preloadColumn.childNodes[i], i);
863
+ }
864
+
865
+ // Wrap one more time, to add everything from borderElementIndex to the the final element
866
+ _wrapColumn(l - 1, false);
867
+ }
868
+
869
+ function _addFlowedElement(element, index) {
870
+
871
+ var originalPadding, existingPadding, totalElementHeight,
872
+ desiredElementHeight, newPadding, overflow, loopCount,
873
+
874
+ nextElement = element.nextSibling;
875
+
876
+ // Check if it's necessary to sanitize elements to conform to the baseline grid
877
+ if (config.standardiseLineHeight) {
878
+
879
+ originalPadding = parseInt(element.getAttribute('data-cf-original-padding'), 10) || null;
880
+
881
+ // TODO: getComputedStyle() will trigger a render, so do all these calls at once if possible.
882
+ existingPadding = parseInt(window.getComputedStyle(element).getPropertyValue('padding-bottom'), 10);
883
+
884
+ if (null === originalPadding) {
885
+ originalPadding = existingPadding;
886
+ element.setAttribute('data-cf-original-padding', originalPadding);
887
+ } else {
888
+ existingPadding = originalPadding;
889
+ }
890
+
891
+ // Return the element to its original padding
892
+ if (originalPadding !== existingPadding) {
893
+ element.style.paddingBottom = originalPadding + 'px';
894
+ }
895
+
896
+ totalElementHeight = nextElement ? (nextElement.offsetTop - element.offsetTop) : element.offsetHeight;
897
+ desiredElementHeight = _roundUpToGrid(totalElementHeight);
898
+
899
+ newPadding = desiredElementHeight - totalElementHeight + existingPadding;
900
+
901
+ if (newPadding !== existingPadding) {
902
+ element.style.paddingBottom = newPadding + 'px';
903
+ }
904
+ }
905
+
906
+ element.offsetBottom = element.offsetTop + element.offsetHeight;
907
+
908
+ // TODO:GC: Remove this loop-protection check
909
+ loopCount = 0;
910
+ while ((element.offsetBottom >= totalColumnHeight || (nextElement && nextElement.offsetTop >= totalColumnHeight)) && (loopCount++ < 30)) {
911
+
912
+ overflow = (element.offsetBottom > totalColumnHeight);
913
+ _wrapColumn(index, overflow);
914
+ }
915
+
916
+ // TODO:GC: Remove this loop-protection check
917
+ if (loopCount >= 30) console.error('FTColumnflow: Caught and destroyed a loop when wrapping columns for element', element.outerHTML.substr(0, 200) + '...');
918
+ }
919
+
920
+ function _wrapColumn(currentElementIndex, overflow) {
921
+
922
+ var nowrap, keepwithnext, prevElementKeepwithnext, firstInColumn, finalColumnElementIndex,
923
+ cropCurrentElement, pushElement, pushFromElementIndex, i, lastElement, element,
924
+
925
+ currentElement = preloadColumn.childNodes[currentElementIndex],
926
+ nextElement = currentElement.nextSibling,
927
+ prevElement = currentElement.previousSibling;
928
+
929
+ // Determine any special classes
930
+ nowrap = currentElement.classList.contains(nowrapClassName);
931
+ keepwithnext = currentElement.classList.contains(keepwithnextClassName);
932
+ prevElementKeepwithnext = (prevElement && prevElement.classList.contains(keepwithnextClassName));
933
+
934
+ // Assume nowrap if element's tag is in the noWrapOnTags list
935
+ if (-1 !== config.noWrapOnTags.indexOf(currentElement.tagName.toLowerCase())) {
936
+ nowrap = true;
937
+ }
938
+
939
+ // Is this the last element of all?
940
+ if (!nextElement) {
941
+ lastElement = true;
942
+ }
943
+
944
+ // Is this the first element of a column?
945
+ if (currentElementIndex === borderElementIndex) {
946
+ firstInColumn = true;
947
+ }
948
+
949
+ // Does the element fit if we collapse the bottom margin?
950
+ if (currentElement.offsetBottom === totalColumnHeight) {
951
+ overflow = false;
952
+ if (nextElement) totalColumnHeight = nextElement.offsetTop;
953
+ }
954
+
955
+ // Do we need to crop the current element?
956
+ if ((nowrap || (keepwithnext && nextElement)) && overflow && (firstInColumn)) {
957
+ cropCurrentElement = true;
958
+ }
959
+
960
+ // Do we need to push the current element to the next column?
961
+ if (!cropCurrentElement && !firstInColumn && ((nowrap && overflow) || (keepwithnext && nextElement))) {
962
+ pushElement = true;
963
+ pushFromElementIndex = currentElementIndex;
964
+ }
965
+
966
+ // Do we need to push the previous element to the next column?
967
+ if ((currentElementIndex - 1) > borderElementIndex && prevElementKeepwithnext && overflow && nowrap) {
968
+ pushElement = true;
969
+ pushFromElementIndex = currentElementIndex - 1;
970
+ }
971
+
972
+ // Determine the final element in this column
973
+ finalColumnElementIndex = pushElement ? pushFromElementIndex - 1 : currentElementIndex;
974
+
975
+ if (finalColumnElementIndex < borderElementIndex) {
976
+
977
+ // We've already added all the elements. We're finished.
978
+ return;
979
+ }
980
+
981
+ // Set the overflow for the current column to the value determined in the last iteration
982
+ workingColumnFrag.overflow = topElementOverflow;
983
+
984
+ // Loop through all elements from the last column border element up to the current element
985
+ for (i = borderElementIndex; i <= finalColumnElementIndex; i++) {
986
+
987
+ element = preloadColumn.childNodes[i];
988
+
989
+ // Add the content of the element to the column
990
+ workingColumnFrag.elements.push({
991
+ content: _outerHTML(element)
992
+ });
993
+ }
994
+
995
+ // Determine the new border element
996
+ if (pushElement) {
997
+ borderElementIndex = pushFromElementIndex;
998
+ } else if (overflow && !cropCurrentElement) {
999
+ borderElementIndex = currentElementIndex;
1000
+ } else {
1001
+ borderElementIndex = currentElementIndex + 1;
1002
+ }
1003
+
1004
+ if (pushElement) {
1005
+
1006
+ // By pushing an element to the next column prematurely, white space has effectively been added to the stream of
1007
+ // column elements. Measurements will therefore be wrong unless the total column height is changed to reflect this. Set
1008
+ // the column height to be the top of the pushed element.
1009
+ totalColumnHeight = preloadColumn.childNodes[pushFromElementIndex].offsetTop;
1010
+
1011
+ } else if (cropCurrentElement && nextElement) {
1012
+
1013
+ // By cropping an element, white space has been removed, so adjust the
1014
+ // column height to be equal to the top of the next element.
1015
+ totalColumnHeight = nextElement.offsetTop;
1016
+ }
1017
+
1018
+ // Set the required negative top margin for the first element in the next column
1019
+ if (!overflow || (nowrap || (keepwithnext && nextElement))) {
1020
+ topElementOverflow = 0;
1021
+ } else {
1022
+ topElementOverflow = totalColumnHeight - currentElement.offsetTop;
1023
+ }
1024
+
1025
+ // Add the height of the next column
1026
+ _advanceWorkingColumnFragment();
1027
+ totalColumnHeight += workingColumnFrag.height;
1028
+ }
1029
+
1030
+
1031
+ /* Add the flowed and fixed content to the target, arranged in pages and columns */
1032
+
1033
+ function _renderFlowedContent() {
1034
+
1035
+ var outputHTML = '', indexedPageNum, page_len, pageHTML, page, i, l, element, indexedColumnNum,
1036
+ column_len, column, indexedColumnFrag, fragLen, el, fragment;
1037
+
1038
+ for (indexedPageNum = 0, page_len = pagedContent.length; indexedPageNum < page_len; indexedPageNum++) {
1039
+
1040
+ pageHTML = '';
1041
+ page = pagedContent[indexedPageNum];
1042
+
1043
+ // Add any fixed elements for this page
1044
+ for (i = 0, l = page.fixed.length; i < l; i++) {
1045
+
1046
+ element = page.fixed[i];
1047
+ pageHTML += element.content;
1048
+ }
1049
+
1050
+ // Add flowed content for this page
1051
+ // First loop the columns
1052
+ for (indexedColumnNum = 0, column_len = page.columns.length; indexedColumnNum < column_len; indexedColumnNum++) {
1053
+
1054
+ column = page.columns[indexedColumnNum];
1055
+
1056
+ // Loop the column fragments
1057
+ for (indexedColumnFrag = 0, fragLen = column.fragments.length; indexedColumnFrag < fragLen; indexedColumnFrag++) {
1058
+
1059
+ fragment = column.fragments[indexedColumnFrag];
1060
+
1061
+ // Don't write empty columns to the page
1062
+ if (0 === fragment.elements.length) {
1063
+ continue;
1064
+ }
1065
+
1066
+ // Open a column div
1067
+ pageHTML += _openColumn(fragment, indexedColumnNum);
1068
+
1069
+ for (el = 0, l = fragment.elements.length; el < l; el++) {
1070
+
1071
+ element = fragment.elements[el];
1072
+
1073
+ // Set the top margin on the first element of the column
1074
+ if (el === 0) {
1075
+
1076
+ // Set a *negative* top margin to shift the element up and hide the content already displayed
1077
+ element.content = _addTopMargin(element.content, -fragment.overflow);
1078
+ }
1079
+
1080
+ pageHTML += element.content;
1081
+ }
1082
+
1083
+ // Close the column
1084
+ pageHTML += '</div>';
1085
+ }
1086
+ }
1087
+
1088
+ // Don't write empty pages
1089
+ if ('' === pageHTML) {
1090
+ pagedContent.splice(indexedPageNum, 1);
1091
+ indexedPageNum--;
1092
+ page_len--;
1093
+ continue;
1094
+ }
1095
+
1096
+ // Add the page contents to the HTML string
1097
+ outputHTML += _openPage(indexedPageNum) + pageHTML + '</div>';
1098
+ }
1099
+
1100
+ renderArea.innerHTML = outputHTML;
1101
+
1102
+ // Set an explicit width on the target - not necessary but will allow adjacent content to flow around the flowed columns normally
1103
+ that.target.style.width = (config.viewportWidth * page_len) + 'px';
1104
+
1105
+ // Update the instance page counter
1106
+ that.pagedContentCount = pagedContent.length;
1107
+ }
1108
+
1109
+
1110
+ /* Private methods */
1111
+
1112
+ function _addTopMargin(element, margin) {
1113
+
1114
+ // Modify the opening tag of the element
1115
+ return element.replace(/<(\w+)([^>]*)>/, function _addTopMarginToTag(string, tag, attributes) {
1116
+
1117
+ // If there's not yet a style attribute, add one
1118
+ if (!string.match(/style\s*=/)) {
1119
+ string = '<' + tag + ' style="" ' + attributes + '>';
1120
+ }
1121
+
1122
+ // Add the top margin declaration
1123
+ string = string.replace(/style=(["'])/, 'style=$1 margin-top:' + margin + 'px;');
1124
+ return string;
1125
+ });
1126
+ }
1127
+
1128
+
1129
+
1130
+
1131
+
1132
+
1133
+ function _roundDownToGrid(val, addPadding) {
1134
+ var resized = val - (val % config.lineHeight);
1135
+
1136
+ // If the difference after rounding down is less than the minimum padding, also subtract one grid line
1137
+ if (addPadding && ((val - resized) < minFixedPadding)) {
1138
+ resized -= fixedPadding;
1139
+ }
1140
+
1141
+ return resized;
1142
+ }
1143
+
1144
+
1145
+ function _roundUpToGrid(val, addPadding) {
1146
+
1147
+ var delta = val % config.lineHeight,
1148
+ resized = (delta ? (val - delta + config.lineHeight) : val);
1149
+
1150
+ // If the difference after rounding up is less than the minimum padding, also add one grid line
1151
+ if (addPadding && ((resized - val) < minFixedPadding)) {
1152
+ resized += fixedPadding;
1153
+ }
1154
+
1155
+ return resized;
1156
+ }
1157
+
1158
+ function _replaceStringTokens(string, tokens) {
1159
+ return string.replace(/\[(\w+)\]/g,
1160
+ function _replace(a, b) {
1161
+ var r = tokens[b];
1162
+ return typeof r === 'string' || typeof r === 'number' ? r : a;
1163
+ });
1164
+ }
1165
+
1166
+
1167
+ function _normaliseClassName(type, value) {
1168
+
1169
+ if (typeof value !== 'string') {
1170
+ throw new FTColumnflowException('ClassnameException', type + ' must be a string.');
1171
+ }
1172
+
1173
+ return value.replace(/[^\w]/g, '-');
1174
+ }
1175
+
1176
+
1177
+ function _createPageObjects(indexedPageNum) {
1178
+ var pageNum, indexedColNum;
1179
+
1180
+ for (pageNum = pagedContent.length; pageNum <= indexedPageNum; pageNum++) {
1181
+
1182
+ pagedContent.push({
1183
+ 'fixed': [],
1184
+ 'columns': []
1185
+ });
1186
+
1187
+ for (indexedColNum = 0; indexedColNum < config.layoutDimensions.columnCount; indexedColNum++) {
1188
+ pagedContent[pageNum].columns.push({
1189
+ fragments: [_createColumnFragment()]
1190
+ });
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+
1196
+ function _createColumnFragment() {
1197
+
1198
+ return {
1199
+ elements: [],
1200
+ overflow: 0,
1201
+ height: maxColumnHeight,
1202
+ top: config.layoutDimensions.colDefaultTop,
1203
+ bottom: colDefaultBottom
1204
+ };
1205
+ }
1206
+
1207
+
1208
+ function _advanceWorkingColumnFragment() {
1209
+
1210
+ // Advance the fragment counter and check for another fragment
1211
+ if (!workingColumn.fragments[++indexedColumnFrag]) {
1212
+ indexedColumnFrag = 0;
1213
+
1214
+ // Advance the column counter and check for another column
1215
+ if (!workingPage.columns[++indexedColumnNum]) {
1216
+ indexedColumnNum = 0;
1217
+
1218
+ // Advance the page counter and create another page if necessary
1219
+ indexedPageNum++;
1220
+ _createPageObjects(indexedPageNum);
1221
+ }
1222
+ }
1223
+
1224
+ workingPage = pagedContent[indexedPageNum];
1225
+ workingColumn = workingPage.columns[indexedColumnNum];
1226
+ workingColumnFrag = workingColumn.fragments[indexedColumnFrag];
1227
+
1228
+ if (!workingColumnFrag) {
1229
+ _advanceWorkingColumnFragment();
1230
+ }
1231
+ }
1232
+
1233
+
1234
+ function _openPage(indexedPageNum) {
1235
+ var pagePos;
1236
+
1237
+ if ('horizontal' === config.pageArrangement) {
1238
+ pagePos = 'left: ' + (indexedPageNum * config.viewportWidth) + 'px;';
1239
+ } else {
1240
+ pagePos = 'top: ' + (indexedPageNum * config.viewportHeight) + 'px;';
1241
+ }
1242
+
1243
+ return '<div class="' + config.pageClass + ' ' + config.pageClass + '-' + (indexedPageNum + 1) + '" style="' + pagePos + '">';
1244
+ }
1245
+
1246
+
1247
+ function _openColumn(column, indexedColumnNum) {
1248
+ return '<div class="' + config.columnClass + ' ' + config.columnClass + '-' + (indexedColumnNum + 1) + '" style="height: ' + column.height + 'px; top: ' + column.top + 'px;">';
1249
+ }
1250
+
1251
+
1252
+ function _mode(array) {
1253
+
1254
+ var modeMap = {},
1255
+ maxEl, maxCount, i, el;
1256
+
1257
+ if (array.length === 0) {
1258
+ return null;
1259
+ }
1260
+
1261
+ maxEl = array[0];
1262
+ maxCount = 1;
1263
+
1264
+ for (i = 0; i < array.length; i++) {
1265
+ el = array[i];
1266
+ if (modeMap[el] === undefined) {
1267
+ modeMap[el] = 1;
1268
+ } else {
1269
+ modeMap[el]++;
1270
+ }
1271
+ if (modeMap[el] > maxCount) {
1272
+ maxEl = el;
1273
+ maxCount = modeMap[el];
1274
+ }
1275
+ }
1276
+ return maxEl;
1277
+ }
1278
+
1279
+
1280
+ function _log() {
1281
+
1282
+ if (!debugMode) return;
1283
+ console.log.apply(console, arguments);
1284
+ }
1285
+
1286
+
1287
+ /* Public methods */
1288
+
1289
+ this.flow = function(flowed, fixed) {
1290
+
1291
+ flowedContent = flowed;
1292
+ fixedContent = fixed;
1293
+
1294
+ _writeTargetStyles();
1295
+ _createTargetElements();
1296
+
1297
+ _findLineHeight();
1298
+ _flowContent();
1299
+ _renderFlowedContent();
1300
+ };
1301
+
1302
+ this.reflow = function(newConfig) {
1303
+
1304
+ if (newConfig) {
1305
+ _setConfig(newConfig);
1306
+ }
1307
+
1308
+ _setLayoutDimensions();
1309
+ _writeTargetStyles();
1310
+
1311
+ _findLineHeight();
1312
+ _flowContent();
1313
+ _renderFlowedContent();
1314
+ };
1315
+
1316
+ this.destroy = function() {
1317
+
1318
+ if (headerStyles) {
1319
+ headerStyles.parentNode.removeChild(headerStyles);
1320
+ headerStyles = null;
1321
+ }
1322
+
1323
+ if (that.target) {
1324
+ that.target.parentNode.removeChild(that.target);
1325
+ that.target = null;
1326
+ }
1327
+ };
1328
+ }
1329
+
1330
+ FTColumnflow.prototype = {
1331
+ get layoutDimensions() {
1332
+ return this.config.layoutDimensions;
1333
+ },
1334
+ set layoutDimensions(value) {
1335
+ throw new FTColumnflowException('SetterException', 'Setter not defined for layoutDimensions.');
1336
+ },
1337
+ get pageClass() {
1338
+ return this.config.pageClass;
1339
+ },
1340
+ set pageClass(value) {
1341
+ throw new FTColumnflowException('SetterException', 'Setter not defined for pageClass.');
1342
+ },
1343
+ get columnClass() {
1344
+ return this.config.columnClass;
1345
+ },
1346
+ set columnClass(value) {
1347
+ throw new FTColumnflowException('SetterException', 'Setter not defined for columnClass.');
1348
+ },
1349
+ get pageCount() {
1350
+ return this.pagedContentCount;
1351
+ },
1352
+ set pageCount(value) {
1353
+ throw new FTColumnflowException('SetterException', 'Setter not defined for pageCount.');
1354
+ },
1355
+
1356
+ _checkInstanceArgs: function () {
1357
+
1358
+ var that = this;
1359
+
1360
+ // Check type of required target and viewport parameters
1361
+ ['target', 'viewport'].forEach(function _checkArg(name) {
1362
+
1363
+ var arg = that[name];
1364
+
1365
+ switch (typeof arg) {
1366
+
1367
+ case 'string':
1368
+
1369
+ arg = document.getElementById(arg);
1370
+ if (!arg) throw new FTColumnflowException('SelectorException', name + ' must be a valid DOM element.');
1371
+ break;
1372
+
1373
+ case 'object':
1374
+ if (!(arg instanceof HTMLElement)) {
1375
+ throw new FTColumnflowException('ParameterException', name + ' must be a string ID or DOM element.');
1376
+ }
1377
+ break;
1378
+
1379
+ default:
1380
+ throw new FTColumnflowException('ParameterException', name + ' must be a string ID or DOM element.');
1381
+ }
1382
+
1383
+ that[name] = arg;
1384
+ });
1385
+
1386
+ // Check target is a child of viewport
1387
+ if (!this._isDescendant(this.viewport, this.target)) {
1388
+ throw new FTColumnflowException('InheritanceException', 'Target element must be a child of the viewport.');
1389
+ }
1390
+
1391
+ // Ensure we have an empty target
1392
+ this.target.innerHTML = '';
1393
+ },
1394
+
1395
+ _isDescendant: function (parent, child) {
1396
+ var node = child.parentNode;
1397
+ while (node !== null) {
1398
+ if (node === parent) {
1399
+ return true;
1400
+ }
1401
+ node = node.parentNode;
1402
+ }
1403
+ return false;
1404
+ }
1405
+ };
1406
+
1407
+ return FTColumnflow;
1408
+
1409
+ }());