flexlayout-rails 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +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
|
+
}());
|