alongslide 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/Gemfile.lock +103 -0
- data/alongslide.gemspec +18 -0
- data/app/assets/javascripts/alongslide/alongslide.coffee +107 -0
- data/app/assets/javascripts/alongslide/layout.coffee +504 -0
- data/app/assets/javascripts/alongslide/parser.coffee +125 -0
- data/app/assets/javascripts/alongslide/scrolling.coffee +423 -0
- data/app/assets/javascripts/alongslide.coffee +19 -0
- data/app/assets/stylesheets/alongslide.sass +444 -0
- data/app/views/panel/panel.haml +8 -0
- data/app/views/panel/unpin.haml +4 -0
- data/app/views/section/exit.haml +4 -0
- data/app/views/section/section.haml +4 -0
- data/grammar/alongslide.treetop +162 -0
- data/grammar/panel.treetop +67 -0
- data/vendor/assets/javascripts/jquery.fitvids.js +74 -0
- data/vendor/assets/javascripts/prefix.js +18 -0
- data/vendor/assets/javascripts/regionFlow.coffee +305 -0
- data/vendor/assets/javascripts/skrollr.js +1716 -0
- data/vendor/assets/javascripts/tether.js +1357 -0
- metadata +23 -3
@@ -0,0 +1,504 @@
|
|
1
|
+
#
|
2
|
+
# alongslide.coffee: Re-format HTML into horizontally-scrolling elements
|
3
|
+
# which scroll at different rates.
|
4
|
+
#
|
5
|
+
# Use CSS Regions polyfill for text flowing, and skrollr for scroll positioning.
|
6
|
+
#
|
7
|
+
# Copyright 2013 Canopy Canopy Canopy, Inc.
|
8
|
+
# Authors Adam Florin & Anthony Tran
|
9
|
+
#
|
10
|
+
class Alongslide::Layout
|
11
|
+
|
12
|
+
# For parsing pinned panel directives.
|
13
|
+
#
|
14
|
+
HORIZONTAL_EDGES: ["left", "right"]
|
15
|
+
VERTICAL_EDGES: ["top", "bottom"]
|
16
|
+
EDGES: @::HORIZONTAL_EDGES.concat @::VERTICAL_EDGES
|
17
|
+
SIZES: ["one-third", "half", "two-thirds"]
|
18
|
+
ALIGNMENTS: @::EDGES.concat 'fullscreen'
|
19
|
+
|
20
|
+
# Keys to ALS position data attributes
|
21
|
+
IN_POINT_KEY: 'als-in-position'
|
22
|
+
OUT_POINT_KEY: 'als-out-position'
|
23
|
+
|
24
|
+
# Pixel width of each frame == screen width.
|
25
|
+
#
|
26
|
+
frameWidth: $(window).width()
|
27
|
+
|
28
|
+
# Switch to true for verbose debugging. Plus constants for indent level.
|
29
|
+
#
|
30
|
+
debug: false
|
31
|
+
SUPER_FRAME_LEVEL: 1
|
32
|
+
FRAME_LEVEL: 2
|
33
|
+
SUB_FRAME_LEVEL: 3
|
34
|
+
|
35
|
+
#
|
36
|
+
#
|
37
|
+
constructor: (options = {}) ->
|
38
|
+
{@frames, @flowNames, @backgrounds, @panels, @regionCls, @sourceLength} = options
|
39
|
+
|
40
|
+
# Main entrypoint for asynchronous chain of render calls.
|
41
|
+
#
|
42
|
+
# @param postRenderCallback - to be called when layout is 100% complete
|
43
|
+
#
|
44
|
+
render: (@postRenderCallback) ->
|
45
|
+
@reset()
|
46
|
+
@writeBackgrounds()
|
47
|
+
@layout()
|
48
|
+
|
49
|
+
# Flow text into columns, using regionFlow as a CSS regions polyfill.
|
50
|
+
#
|
51
|
+
# Loop through each flow (= section), then each frame, then each column.
|
52
|
+
#
|
53
|
+
# This is all done asynchronously so that DOM can update itself periodically
|
54
|
+
# (namely for layout progress updates).
|
55
|
+
#
|
56
|
+
layout: =>
|
57
|
+
@startTime = new Date
|
58
|
+
@log "Beginning layout"
|
59
|
+
|
60
|
+
@currentFlowIndex = 0
|
61
|
+
|
62
|
+
# kick off asynchronous render
|
63
|
+
@renderSection()
|
64
|
+
|
65
|
+
# Render one section (a.k.a. one "flow" in CSSRegions parlance),
|
66
|
+
# one frame at a time, asynchronously.
|
67
|
+
#
|
68
|
+
renderSection: ->
|
69
|
+
flowName = @flowNames[@currentFlowIndex]
|
70
|
+
|
71
|
+
@log "Laying out section \"#{flowName}\"", @SUPER_FRAME_LEVEL
|
72
|
+
background = @findBackground(flowName)
|
73
|
+
@setPositionOf background, to: @nextFramePosition() if background.length
|
74
|
+
background.addClass('unstaged')
|
75
|
+
|
76
|
+
@renderFrame(flowName)
|
77
|
+
|
78
|
+
# Render one frame and its containing columns.
|
79
|
+
#
|
80
|
+
# When frame is done, trigger (asynchronously) either next frame or next section.
|
81
|
+
#
|
82
|
+
# Note that normally we check _second-to-last_ column for directives,
|
83
|
+
# as last column contains overflow. Once flow is complete, though,
|
84
|
+
# check the last column--and remove it if it contains nothing but directives.
|
85
|
+
#
|
86
|
+
renderFrame: (flowName, frame, lastColumn) ->
|
87
|
+
frame = @findOrBuildNextFlowFrame frame
|
88
|
+
|
89
|
+
# for each column in frame
|
90
|
+
while frame.find('.'+@regionCls).length < @numFrameColumns(frame)
|
91
|
+
column = @buildRegion frame, flowName
|
92
|
+
|
93
|
+
# Move three-columns class from .section to .frame
|
94
|
+
hasThreeColumns = (column.children('.three-columns').length)
|
95
|
+
if hasThreeColumns
|
96
|
+
column.find('.section').removeClass('three-columns')
|
97
|
+
column.parent().addClass('three-columns')
|
98
|
+
|
99
|
+
# Process N-1 column (as current column still total text overflow of
|
100
|
+
# the entire section).
|
101
|
+
if lastColumn?
|
102
|
+
@checkForOrphans lastColumn
|
103
|
+
@updateProgress lastColumn
|
104
|
+
@checkForDirectives lastColumn
|
105
|
+
|
106
|
+
# Section is complete. Current column is the last column of the
|
107
|
+
# section (no longer the overflow column).
|
108
|
+
if @flowComplete(flowName)
|
109
|
+
@updateProgress column
|
110
|
+
@checkForDirectives column, true
|
111
|
+
@checkForEmpties column
|
112
|
+
|
113
|
+
# render next section, or complete render.
|
114
|
+
@currentFlowIndex++
|
115
|
+
unless @currentFlowIndex is @flowNames.length
|
116
|
+
background = @findBackground(flowName)
|
117
|
+
@setPositionOf background, until: @lastFramePosition() if background.length
|
118
|
+
setTimeout((=> @renderSection()), 1)
|
119
|
+
else
|
120
|
+
@log "Layout complete"
|
121
|
+
@reorder()
|
122
|
+
@index()
|
123
|
+
@frames.children('.flow').find('.frame').addClass('unstaged')
|
124
|
+
@postRenderCallback(@lastFramePosition())
|
125
|
+
return
|
126
|
+
|
127
|
+
lastColumn = column
|
128
|
+
|
129
|
+
# unstage earlier frames
|
130
|
+
frame.prevAll().addClass('unstaged')
|
131
|
+
|
132
|
+
# render next frame
|
133
|
+
setTimeout((=> @renderFrame(flowName, frame, lastColumn)), 1)
|
134
|
+
|
135
|
+
# Check the last "fit" column for any special directives (CSS classes).
|
136
|
+
#
|
137
|
+
# The last "fit" column is the last one that doesn't also contain all the
|
138
|
+
# overflow to be laid out in other columns--typically the second-to-last column.
|
139
|
+
#
|
140
|
+
# Then, having found a directive, parse the classes and act accordingly.
|
141
|
+
#
|
142
|
+
# NOTE that this method also takes responsibility for creating the appropriate
|
143
|
+
# next frame for text to flow in. It doesn't need to return it, however,
|
144
|
+
# as the caller will just use `findOrBuildNextFlowFrame` to check for it.
|
145
|
+
#
|
146
|
+
# @param column - jQuery element to scan for directives
|
147
|
+
# @param layoutComplete - true if this flow has been completely laid out
|
148
|
+
# (and therefore no new flowing regions should be created)
|
149
|
+
#
|
150
|
+
checkForDirectives: (column, layoutComplete) ->
|
151
|
+
# for each directive
|
152
|
+
(column.find ".alongslide").each (index, directiveElement) =>
|
153
|
+
directive = $(directiveElement).detach()
|
154
|
+
id = directive.data('alongslide-id')
|
155
|
+
|
156
|
+
# the column's frame may have changed (if specified by previous directive)
|
157
|
+
flowFrame = column.parent('.frame')
|
158
|
+
|
159
|
+
switch
|
160
|
+
# new panel
|
161
|
+
when directive.hasClass "show"
|
162
|
+
|
163
|
+
# build next flow frame (if there is one)
|
164
|
+
unless layoutComplete
|
165
|
+
nextFlowFrame = @findOrBuildNextFlowFrame flowFrame
|
166
|
+
nextFlowFramePosition = if nextFlowFrame
|
167
|
+
@getPositionOf(nextFlowFrame)
|
168
|
+
else
|
169
|
+
@nextFramePosition()
|
170
|
+
|
171
|
+
# build panel
|
172
|
+
panelPosition = if directive.hasClass("now")
|
173
|
+
@getPositionOf(flowFrame)
|
174
|
+
else
|
175
|
+
nextFlowFramePosition
|
176
|
+
panelFrame = @buildPanel id, panelPosition
|
177
|
+
|
178
|
+
@updateProgress(panelFrame)
|
179
|
+
|
180
|
+
switch
|
181
|
+
|
182
|
+
# pinned panel layout
|
183
|
+
when directive.hasClass "pin"
|
184
|
+
# display forever (until unpinned)
|
185
|
+
@setPositionOf panelFrame, until: -1
|
186
|
+
|
187
|
+
# which frames need to have classes set--next and/or current?
|
188
|
+
framesWithPinnedPanels = _.compact [
|
189
|
+
flowFrame if directive.hasClass("now"),
|
190
|
+
nextFlowFrame]
|
191
|
+
|
192
|
+
# set frame classes--pushing columns into subsequent frames if necessary
|
193
|
+
_.each framesWithPinnedPanels, (frame) =>
|
194
|
+
@log "Applying with-pinned-panel styles to flow frame at " +
|
195
|
+
"#{@getPositionOf frame}", @SUB_FRAME_LEVEL
|
196
|
+
frame.addClass @withPinnedClass(directive)
|
197
|
+
frame.addClass @withSizedClass(directive)
|
198
|
+
while frame.find('.'+@regionCls).length > @numFrameColumns frame
|
199
|
+
pushToFrame = @findOrBuildNextFlowFrame frame
|
200
|
+
@log "Pushing last column of flow frame at #{@getPositionOf frame} " +
|
201
|
+
"to flow frame at #{@getPositionOf pushToFrame}", @SUB_FRAME_LEVEL
|
202
|
+
frame.find('.'+@regionCls).last().detach().prependTo(pushToFrame)
|
203
|
+
|
204
|
+
# If we changed this frame's layout, re-flow this whole section's
|
205
|
+
# regions.
|
206
|
+
if directive.hasClass("now")
|
207
|
+
sectionId = flowFrame.data('als-section-id')
|
208
|
+
@frames.children('.flow').find(".frame[data-als-section-id=#{sectionId}]").removeClass('unstaged')
|
209
|
+
document.namedFlows.get(sectionId).reFlow()
|
210
|
+
|
211
|
+
# fullscreen panel layout
|
212
|
+
when directive.hasClass "fullscreen"
|
213
|
+
if directive.hasClass "now"
|
214
|
+
@setPositionOf flowFrame, to: nextFlowFramePosition
|
215
|
+
|
216
|
+
if nextFlowFrame?
|
217
|
+
@setPositionOf nextFlowFrame, to: nextFlowFramePosition + 1
|
218
|
+
|
219
|
+
# unpin pinned panel
|
220
|
+
when directive.hasClass "unpin"
|
221
|
+
panelFrame = @findPanel(id)
|
222
|
+
|
223
|
+
unless panelFrame.length == 0
|
224
|
+
@setPositionOf panelFrame, until:
|
225
|
+
if layoutComplete
|
226
|
+
@nextFramePosition() - 1
|
227
|
+
else
|
228
|
+
Math.max @getPositionOf(flowFrame), @getPositionOf(panelFrame)
|
229
|
+
|
230
|
+
# unset frame classes for first flow frame after panel (may be next-next frame)
|
231
|
+
unless layoutComplete
|
232
|
+
postPanelFlowFrame = @findOrBuildNextFlowFrame flowFrame
|
233
|
+
if @getPositionOf(postPanelFlowFrame) is @getPositionOf(panelFrame)
|
234
|
+
postPanelFlowFrame = @findOrBuildNextFlowFrame postPanelFlowFrame
|
235
|
+
postPanelFlowFrame.removeClass @withPinnedClass(panelFrame)
|
236
|
+
postPanelFlowFrame.removeClass @withSizedClass(panelFrame)
|
237
|
+
|
238
|
+
# If column contains nothing other than directives, remove it.
|
239
|
+
#
|
240
|
+
# Called only when a section has been fully laid out.
|
241
|
+
#
|
242
|
+
# Do the test on a clone, so we don't strip directives from actual column,
|
243
|
+
# which will still be checked by checkForDirectives.
|
244
|
+
#
|
245
|
+
# If column's frame is empty, remove that, too.
|
246
|
+
#
|
247
|
+
checkForEmpties: (column) ->
|
248
|
+
columnClone = column.clone()
|
249
|
+
columnClone.find(".alongslide").detach()
|
250
|
+
if @isEmpty columnClone
|
251
|
+
columnFrame = column.parent('.frame')
|
252
|
+
@log "Removing empty column from flow frame at " +
|
253
|
+
"#{@getPositionOf columnFrame}", @SUB_FRAME_LEVEL
|
254
|
+
column.detach()
|
255
|
+
|
256
|
+
# Reset entire section layout, as regionFlow has kept a record of this column
|
257
|
+
document.namedFlows.get(columnFrame.data('als-section-id')).resetRegions()
|
258
|
+
|
259
|
+
# destroy column frame if it's empty, too
|
260
|
+
@destroyFlowFrame columnFrame if @isEmpty columnFrame
|
261
|
+
|
262
|
+
# while we're at it, check if any empty frames were created (probably at the end)
|
263
|
+
@frames.children('.flow').find('.frame:empty').each (index, frame) =>
|
264
|
+
@destroyFlowFrame $(frame)
|
265
|
+
|
266
|
+
# Check for orphaned content. This can take many forms, so this method will
|
267
|
+
# grow and evolve as cases emerge.
|
268
|
+
#
|
269
|
+
checkForOrphans: (column) ->
|
270
|
+
# If column ends with a header, push it to the overflow column.
|
271
|
+
column.find(':last:header').detach().prependTo($('.'+@regionCls+':last'))
|
272
|
+
|
273
|
+
# Given a flow frame, find the next in line after it--or create one if none exists.
|
274
|
+
#
|
275
|
+
findOrBuildNextFlowFrame: (lastFrame) ->
|
276
|
+
nextFlowFrame = if lastFrame?.length then lastFrame.next('.frame')
|
277
|
+
unless nextFlowFrame?.length
|
278
|
+
nextFlowFrame = @buildFlowFrame lastFrame
|
279
|
+
return nextFlowFrame
|
280
|
+
|
281
|
+
# Build one frame to hold columns of flowing text.
|
282
|
+
#
|
283
|
+
# Only build new frame if there are none. Otherwise, clone the last one.
|
284
|
+
#
|
285
|
+
buildFlowFrame: (lastFrame) ->
|
286
|
+
position = if lastFrame?.length
|
287
|
+
@getPositionOf(lastFrame) + 1
|
288
|
+
else
|
289
|
+
@nextFramePosition()
|
290
|
+
@log "Building flow frame at #{position}", @FRAME_LEVEL
|
291
|
+
frame = if lastFrame?.length
|
292
|
+
lastFrame?.clone().empty()
|
293
|
+
else
|
294
|
+
$('<div class="frame"/>')
|
295
|
+
frame.appendTo @frames.children('.flow')
|
296
|
+
@setPositionOf frame, to: position
|
297
|
+
|
298
|
+
# Destroy frame, shifting any subsequent panels up by one.
|
299
|
+
#
|
300
|
+
destroyFlowFrame: (frame) ->
|
301
|
+
@log "Destroying flow frame at #{@getPositionOf frame}", @FRAME_LEVEL
|
302
|
+
frame.detach()
|
303
|
+
@frames.children('.panels').find('.panel.frame').each (index, panel) =>
|
304
|
+
panelPosition = $(panel).data(@IN_POINT_KEY)
|
305
|
+
if panelPosition > frame.data(@IN_POINT_KEY)
|
306
|
+
@log "Moving panel \"#{$(panel).data('alongslide-id')}\" at " +
|
307
|
+
"#{panelPosition} to #{panelPosition-1}", @FRAME_LEVEL
|
308
|
+
$(panel).data(@IN_POINT_KEY, panelPosition-1)
|
309
|
+
|
310
|
+
# Create region to receive flowing text (column). Return jQuery object.
|
311
|
+
#
|
312
|
+
buildRegion: (frame, flowName) ->
|
313
|
+
@log "Building column in flow frame at #{@getPositionOf frame}", @SUB_FRAME_LEVEL
|
314
|
+
region = $('<div/>').addClass(@regionCls)
|
315
|
+
frame.attr('data-als-section-id', flowName)
|
316
|
+
region.appendTo frame
|
317
|
+
document.namedFlows.get(flowName).addRegion(region.get(0))
|
318
|
+
return region
|
319
|
+
|
320
|
+
# Pull panel element out of @panels storage, apply its transition, and
|
321
|
+
# append to DOM!
|
322
|
+
#
|
323
|
+
# @param id - Alongslide panel ID
|
324
|
+
#
|
325
|
+
buildPanel: (id, position) ->
|
326
|
+
panel = @panels[id].clone().addClass('unstaged').show()
|
327
|
+
alignment = _.filter @ALIGNMENTS, (alignment) -> panel.hasClass(alignment)
|
328
|
+
@log "Building #{alignment} panel frame \"#{id}\" at position #{position}", @FRAME_LEVEL
|
329
|
+
panel.addClass('frame')
|
330
|
+
panel.appendTo @frames.children('.panels')
|
331
|
+
@setPositionOf panel, to: position
|
332
|
+
return panel
|
333
|
+
|
334
|
+
# Destroy all previously laid out content.
|
335
|
+
#
|
336
|
+
reset: ->
|
337
|
+
@laidOutLength = 0
|
338
|
+
|
339
|
+
@frames.find('.backgrounds').empty()
|
340
|
+
@frames.find('.flow').empty()
|
341
|
+
@frames.find('.panels').empty()
|
342
|
+
|
343
|
+
# remove regionFlows' internal record of regions we just destroyed
|
344
|
+
_.each document.namedFlows.namedFlows, (flow) -> flow.resetRegions()
|
345
|
+
|
346
|
+
# Write the given array of backgrounds to the DOM.
|
347
|
+
#
|
348
|
+
writeBackgrounds: ->
|
349
|
+
for background in @backgrounds
|
350
|
+
@frames.find('.backgrounds').append(background.clone())
|
351
|
+
|
352
|
+
# Set frame start/end position.
|
353
|
+
#
|
354
|
+
# @param options
|
355
|
+
# to: in point (= start position)
|
356
|
+
# until: out point (= end position)
|
357
|
+
#
|
358
|
+
setPositionOf: (frame, options={}) ->
|
359
|
+
frameType = frame.parent().get(0).className
|
360
|
+
if options.to?
|
361
|
+
if (currentFramePosition = @getPositionOf frame)?
|
362
|
+
@log "Moving #{frameType} frame at #{currentFramePosition} to " +
|
363
|
+
"#{options.to}", @SUB_FRAME_LEVEL
|
364
|
+
frame.data @IN_POINT_KEY, options.to
|
365
|
+
if options.until?
|
366
|
+
@log "Dismissing #{frameType} frame \"#{frame.data('alongslide-id')}\" " +
|
367
|
+
"at #{options.until}", @SUB_FRAME_LEVEL
|
368
|
+
frame.data @OUT_POINT_KEY, options.until
|
369
|
+
return frame
|
370
|
+
|
371
|
+
# Return start position.
|
372
|
+
#
|
373
|
+
getPositionOf: (frame) ->
|
374
|
+
frame.data(@IN_POINT_KEY)
|
375
|
+
|
376
|
+
# What position the _next_ (as yet unrendered) frame should be at.
|
377
|
+
#
|
378
|
+
nextFramePosition: ->
|
379
|
+
framePosition = @lastFramePosition()
|
380
|
+
if framePosition? then framePosition + 1 else 0
|
381
|
+
|
382
|
+
# Return the largest frame number of all frames.
|
383
|
+
#
|
384
|
+
lastFramePosition: ->
|
385
|
+
flowsAndPanels = @frames.children('.flow, .panels').find('.frame')
|
386
|
+
allFramePositions = _(flowsAndPanels).map (frame) => @getPositionOf $(frame)
|
387
|
+
return if allFramePositions.length then Math.max(allFramePositions...)
|
388
|
+
|
389
|
+
# Certain frame CSS classes limit the number of columns allowed
|
390
|
+
# per flow frame.
|
391
|
+
#
|
392
|
+
numFrameColumns: (frame) ->
|
393
|
+
numColumns = if frame.hasClass('three-columns') then 3 else 2
|
394
|
+
numColumns -= 1 if @isWithHorizontalPanel(frame)
|
395
|
+
numColumns -= 1 if frame.hasClass('three-columns') and frame.hasClass("with-panel-sized-two-thirds")
|
396
|
+
return numColumns
|
397
|
+
|
398
|
+
# Once render is done, build relevant indices for later lookup.
|
399
|
+
#
|
400
|
+
index: ->
|
401
|
+
@panelIndex = {}
|
402
|
+
@frames.children('.panels').find('.panel.frame').each (index, panel) =>
|
403
|
+
outPosition = $(panel).data(@OUT_POINT_KEY) || $(panel).data(@IN_POINT_KEY)
|
404
|
+
outPosition = @lastFramePosition() if outPosition is -1
|
405
|
+
for position in [$(panel).data(@IN_POINT_KEY)..outPosition]
|
406
|
+
@panelIndex[position] ?= []
|
407
|
+
@panelIndex[position].push panel
|
408
|
+
|
409
|
+
# Re-order elements in DOM if specified.
|
410
|
+
#
|
411
|
+
# This is a more reliable method of forcing a higher z-index for certain panels.
|
412
|
+
#
|
413
|
+
reorder: ->
|
414
|
+
frontPanels = @frames.children('.panels').find('.panel.front').detach()
|
415
|
+
@frames.children('.panels').append(frontPanels)
|
416
|
+
|
417
|
+
# DOM utility.
|
418
|
+
#
|
419
|
+
# Find background for a given section (flow).
|
420
|
+
#
|
421
|
+
findBackground: (flowName) ->
|
422
|
+
@frames.children('.backgrounds').find(".background.frame[data-alongslide-id=#{flowName}]")
|
423
|
+
|
424
|
+
# DOM utility.
|
425
|
+
#
|
426
|
+
findPanel: (id) ->
|
427
|
+
@frames.children('.panels').find(".panel.frame[data-alongslide-id=#{id}]")
|
428
|
+
|
429
|
+
# Check panel index for panel at given position, and check if it's horizontal.
|
430
|
+
#
|
431
|
+
horizontalPanelAt: (position, edge) ->
|
432
|
+
edges = if edge? then [edge] else @HORIZONTAL_EDGES
|
433
|
+
_(@panelIndex[position] || []).any (panel) ->
|
434
|
+
_(edges).any (edge) -> $(panel).hasClass(edge)
|
435
|
+
|
436
|
+
# Check if flow frame shares space with horizontally-pinned panel.
|
437
|
+
#
|
438
|
+
isWithHorizontalPanel: (frame) ->
|
439
|
+
for cssClass in _.map(@HORIZONTAL_EDGES, (edge) -> "with-panel-pinned-#{edge}")
|
440
|
+
return true if frame.hasClass(cssClass)
|
441
|
+
|
442
|
+
# Given a directive for a pinned panel, retun the class to be applied to
|
443
|
+
# flow frames.
|
444
|
+
#
|
445
|
+
withPinnedClass: (directive) ->
|
446
|
+
edge = _.first _.filter @EDGES, (edge) -> directive.hasClass(edge)
|
447
|
+
"with-panel-pinned-#{edge}"
|
448
|
+
|
449
|
+
# Given a directive for a sized panel, retun the class to be applied to
|
450
|
+
# flow frames.
|
451
|
+
# TODO: maybe combine withPinnedClass and withSizedClass
|
452
|
+
withSizedClass: (directive) ->
|
453
|
+
size = _.first _.filter @SIZES, (size) -> directive.hasClass(size)
|
454
|
+
size ?= "half"
|
455
|
+
"with-panel-sized-#{size}"
|
456
|
+
|
457
|
+
# If panel is partial width (i.e. is with left/right pinned panel),
|
458
|
+
# then return its scrolling scale (0.0-1.0).
|
459
|
+
#
|
460
|
+
# Called by Scrolling to determine scoll distance.
|
461
|
+
#
|
462
|
+
# @return partialWidth - a percentage width (0.0-1.0), or undefined.
|
463
|
+
# Note: This number doesn't have to be exact--just has to "feel" right for
|
464
|
+
# pinned panels of different widths.
|
465
|
+
#
|
466
|
+
framePartialWidth: (frame) ->
|
467
|
+
if @isWithHorizontalPanel(frame)
|
468
|
+
$column = frame.find('.'+@regionCls)
|
469
|
+
return ($column.width() + $column.position().left) * @numFrameColumns(frame) / frame.width()
|
470
|
+
|
471
|
+
# Return panel alignment.
|
472
|
+
#
|
473
|
+
panelAlignment: (directive) ->
|
474
|
+
_.first _.filter @ALIGNMENTS, (alignment) -> directive.hasClass(alignment)
|
475
|
+
|
476
|
+
# Returns true when all flowing text has been laid out (i.e. last column
|
477
|
+
# region no longer contains overflow.)
|
478
|
+
#
|
479
|
+
flowComplete: (flowName) ->
|
480
|
+
not document.namedFlows.get(flowName).updateOverset()
|
481
|
+
|
482
|
+
# Utility: Is column empty after we remove directives from it?
|
483
|
+
#
|
484
|
+
isEmpty: (el) ->
|
485
|
+
$.trim(el.children().html()) == ''
|
486
|
+
|
487
|
+
# Compute length of total laid out content so far and broadcast it to our
|
488
|
+
# listeners.
|
489
|
+
#
|
490
|
+
# To listen:
|
491
|
+
#
|
492
|
+
# $(document).on 'alongslide.progress', (e, progress) ->
|
493
|
+
# #
|
494
|
+
#
|
495
|
+
updateProgress: (newElement) ->
|
496
|
+
@laidOutLength += newElement.text().length
|
497
|
+
$(document).triggerHandler 'alongslide.progress', (@laidOutLength / @sourceLength)
|
498
|
+
|
499
|
+
# Write debug log to console if available/desired.
|
500
|
+
#
|
501
|
+
log: (message, indentLevel = 0) ->
|
502
|
+
indent = (_(indentLevel).times -> ". ").join('')
|
503
|
+
if console? and @debug
|
504
|
+
console.log "#{indent}#{message} (elapsed: #{(new Date - @startTime).valueOf()}ms)"
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#
|
2
|
+
# parser.coffee: parse raw HTML into Alongslide types: sections, panels, etc.
|
3
|
+
#
|
4
|
+
# Copyright 2013 Canopy Canopy Canopy, Inc.
|
5
|
+
# Author Adam Florin
|
6
|
+
|
7
|
+
class Alongslide::Parser
|
8
|
+
|
9
|
+
# Store names of flows here as we create them.
|
10
|
+
#
|
11
|
+
# sections: []
|
12
|
+
backgrounds: []
|
13
|
+
flowNames: []
|
14
|
+
|
15
|
+
constructor: (options) ->
|
16
|
+
{@source} = options
|
17
|
+
@preprocessSource()
|
18
|
+
|
19
|
+
#
|
20
|
+
#
|
21
|
+
preprocessSource: ->
|
22
|
+
# Put dummy content inside empty directives as CSSRegions trims any empty
|
23
|
+
# elements found near the boundaries of a region.
|
24
|
+
(@source.find ".alongslide:empty").text("[ALS]")
|
25
|
+
|
26
|
+
# Parser entrypoint.
|
27
|
+
#
|
28
|
+
# Build sections and store them directly as CSSRegions named flows.
|
29
|
+
#
|
30
|
+
# Retun panels and footnotes, which will be needed by other components.
|
31
|
+
#
|
32
|
+
# Note! Parse order matters! Sections should go last, once all non-section
|
33
|
+
# material has been scraped out of @source.
|
34
|
+
#
|
35
|
+
parse: ->
|
36
|
+
@sourceLength = 0
|
37
|
+
|
38
|
+
panels = @collectPanels()
|
39
|
+
footnotes = @collectFootnotes()
|
40
|
+
@collectSections()
|
41
|
+
|
42
|
+
flowNames: @flowNames
|
43
|
+
backgrounds: @backgrounds
|
44
|
+
panels: panels
|
45
|
+
footnotes: footnotes
|
46
|
+
sourceLength: @sourceLength
|
47
|
+
|
48
|
+
#
|
49
|
+
#
|
50
|
+
collectPanels: ->
|
51
|
+
rawPanels = @source.find('.alongslide.show.panel')
|
52
|
+
|
53
|
+
_.object _.map rawPanels, (el) ->
|
54
|
+
$el = $(el)
|
55
|
+
panelId = $el.data('alongslide-id')
|
56
|
+
panelEl = $el.clone().removeClass('show')
|
57
|
+
|
58
|
+
# Cleanup
|
59
|
+
$el.empty().removeClass('panel')
|
60
|
+
|
61
|
+
return [ panelId, panelEl ]
|
62
|
+
|
63
|
+
# Sift through passed-in sections, delineating them based on `enter` and `exit`
|
64
|
+
# directives, then assigning each to a flow.
|
65
|
+
#
|
66
|
+
collectSections: ->
|
67
|
+
@source.find('.alongslide.enter.section').each (index, directiveElement) =>
|
68
|
+
directive = $(directiveElement)
|
69
|
+
id = directive.data('alongslide-id')
|
70
|
+
exitSelector = '.alongslide.exit.section[data-alongslide-id='+id+']'
|
71
|
+
|
72
|
+
# build section for content BEFORE section enter
|
73
|
+
lastSectionContents = directive.prevAll().detach().get().reverse()
|
74
|
+
@buildSection(lastSectionContents, directive) if lastSectionContents.length
|
75
|
+
|
76
|
+
# build section for content AFTER section enter
|
77
|
+
sectionContents = directive.nextUntil(exitSelector).detach()
|
78
|
+
@buildSection(sectionContents, directive, id)
|
79
|
+
|
80
|
+
# cleanup section build process
|
81
|
+
directive.remove()
|
82
|
+
(@source.find exitSelector).remove()
|
83
|
+
|
84
|
+
@buildSection @source.children() unless @source.is(':empty')
|
85
|
+
|
86
|
+
# Build section, given content.
|
87
|
+
#
|
88
|
+
# Create new NamedFlow for it, and log the name.
|
89
|
+
#
|
90
|
+
# Create section background.
|
91
|
+
#
|
92
|
+
# @param content - jQuery object of section contents
|
93
|
+
# @param directive (optional) - directive which specified the section
|
94
|
+
# @param id (optional) - Alongslide section ID
|
95
|
+
#
|
96
|
+
buildSection: (content, directive, id) ->
|
97
|
+
flowName = id || "sectionFlow#{@flowNames.length}"
|
98
|
+
|
99
|
+
idElement = directive?.removeClass('alongslide').empty().removeClass('enter') || $("<div/>")
|
100
|
+
idElement.attr('data-alongslide-id', flowName)
|
101
|
+
|
102
|
+
# create section
|
103
|
+
section = idElement.clone().append(content)
|
104
|
+
|
105
|
+
# tally up length
|
106
|
+
@sourceLength += section.text().length
|
107
|
+
|
108
|
+
# create NamedFlow
|
109
|
+
document.namedFlows.get(flowName).addContent(section.get(0))
|
110
|
+
@flowNames.push flowName
|
111
|
+
|
112
|
+
# create background if ID is specified
|
113
|
+
if id?
|
114
|
+
background = idElement.clone().addClass('background frame').html(" ")
|
115
|
+
@backgrounds.push(background)
|
116
|
+
|
117
|
+
# Search for footntes as formatted by Redcarpet.
|
118
|
+
#
|
119
|
+
# Each has an ID of the form `fn1`, which corresponds to the links in the
|
120
|
+
# footnote references.
|
121
|
+
#
|
122
|
+
# Returns a jQuery list of li elements and removes the generated footnotes from DOM
|
123
|
+
collectFootnotes: ->
|
124
|
+
@source.find('.footnotes:last-child')
|
125
|
+
.remove()
|