flexlayout-rails 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +94 -37
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/flexlayout-rails.gemspec +12 -3
- data/spec/flexlayout/center.html +13 -0
- data/spec/flexlayout/form.html +18 -0
- data/vendor/assets/javascripts/FT-colum-flow.min.js +22 -0
- data/vendor/assets/javascripts/FT-column-flow.js +1409 -0
- data/vendor/assets/javascripts/jquery.wookmark.js +169 -0
- data/vendor/assets/javascripts/jquery.wookmark.min.js +11 -0
- data/vendor/assets/javascripts/multi-column.js +403 -0
- data/vendor/assets/stylesheets/flexlayout.css +105 -0
- data/vendor/assets/stylesheets/multi-column.css +13 -0
- metadata +13 -3
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
|
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
|
-
|
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
|
-
|
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
|
-
##
|
140
|
+
## Multi column
|
118
141
|
|
119
|
-
|
142
|
+
See [Multi-column](https://github.com/stadtwerk/MultiColumn)
|
120
143
|
|
121
|
-
|
144
|
+
Example:
|
122
145
|
|
123
|
-
|
124
|
-
|
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
|
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
|
+
0.1.2
|
data/flexlayout-rails.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "flexlayout-rails"
|
8
|
-
s.version = "0.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,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
|
+
}());
|