Function
| Static Public Summary | ||
| public |
clearCanvas(ctx: context2d, canvas: canvas) Clear the canvas |
|
| public |
fillRectangle(ctx: *, x: *, y: *, width: *, height: *) |
|
| public |
fillSelected(ctx: context2d, x: int, y: int, width: int, height: int, length: int) Draw selected rectangle |
|
| public |
Draw text on the canvas |
|
| public |
line(ctx: context2d, sx: int, sy: int, ex: int, ey: int) Draw line from starting coordinate to ending coordinate |
|
| public |
resetFillStyle(ctx: context2d) Sets fill style to default |
|
| public |
runEngine(ctx: context2d, canvas: canvas, content: [DeductionRule]) Run engine with given objects |
|
| public |
setFillStyle(ctx: context2d, fillStyle: string) Sets fill style for the context |
|
| public |
setupCanvas(canvasId: string): {"canvas": *, "ctx": *} Returns ctx (context 2D) and canvas (html-element) |
Static Public
public clearCanvas(ctx: context2d, canvas: canvas) source
import {clearCanvas} from 'naturaldeduction/src/core/graphics.js'
Clear the canvas
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| canvas | canvas | canvas (html-element) |
public fillRectangle(ctx: *, x: *, y: *, width: *, height: *) source
import {fillRectangle} from 'naturaldeduction/src/core/graphics.js'
Params:
| Name | Type | Attribute | Description |
| ctx | * | ||
| x | * | ||
| y | * | ||
| width | * | ||
| height | * |
public fillSelected(ctx: context2d, x: int, y: int, width: int, height: int, length: int) source
import {fillSelected} from 'naturaldeduction/src/core/graphics.js'
Draw selected rectangle
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| x | int | X-coordinate |
|
| y | int | Y-coordinate |
|
| width | int | Width |
|
| height | int | Height |
|
| length | int | Length of the lines |
public fillText(ctx: context2d, text: string, x: int, y: int) source
import {fillText} from 'naturaldeduction/src/core/graphics.js'
Draw text on the canvas
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| text | string | Text to be displayed |
|
| x | int | X-coordinate of the text |
|
| y | int | Y-coordinate of the text |
public line(ctx: context2d, sx: int, sy: int, ex: int, ey: int) source
import {line} from 'naturaldeduction/src/core/graphics.js'
Draw line from starting coordinate to ending coordinate
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| sx | int | Starting X-coordinate |
|
| sy | int | Starting Y-coordinate |
|
| ex | int | Ending X-coordinate |
|
| ey | int | Ending Y-coordinate |
public resetFillStyle(ctx: context2d) source
import {resetFillStyle} from 'naturaldeduction/src/core/graphics.js'
Sets fill style to default
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
public runEngine(ctx: context2d, canvas: canvas, content: [DeductionRule]) source
import {runEngine} from 'naturaldeduction/src/core/engine.js'
Run engine with given objects
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| canvas | canvas | canvas (html-element) |
|
| content | [DeductionRule] | Array of deduction rules |
public setFillStyle(ctx: context2d, fillStyle: string) source
import {setFillStyle} from 'naturaldeduction/src/core/graphics.js'
Sets fill style for the context
Params:
| Name | Type | Attribute | Description |
| ctx | context2d | context2d from canvas |
|
| fillStyle | string | Fill style for the context |
public setupCanvas(canvasId: string): {"canvas": *, "ctx": *} source
import {setupCanvas} from 'naturaldeduction/src/core/engine.js'
Returns ctx (context 2D) and canvas (html-element)
Params:
| Name | Type | Attribute | Description |
| canvasId | string | Id for newly created canvas |
Return:
| {"canvas": *, "ctx": *} |


| # | File | Line | Problem |
|---|---|---|---|
| 1 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/GildedRose.java | 12 | Found non-transient, non-static member. Please mark as transient or provide accessors. |
| 2 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/GildedRose.java | 22 | A switch statement does not contain a break |
| 3 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/GildedRose.java | 57 | Avoid using Literals in Conditional Statements |
| 4 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/GildedRose.java | 61 | Avoid using Literals in Conditional Statements |
| 5 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/Item.java | 5 | Found non-transient, non-static member. Please mark as transient or provide accessors. |
| 6 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/Item.java | 7 | Found non-transient, non-static member. Please mark as transient or provide accessors. |
| 7 | /home/local.isima.fr/mabenitoro/Téléchargements/GildedRose/src/main/java/com/gildedrose/Item.java | 9 | Found non-transient, non-static member. Please mark as transient or provide accessors. |


:EasyAlign : " preset characters (\=:.,&#|)
:EasyAlign |
:EasyAlign \ " \ means space
:EasyAlign /[:;]+/
:EasyAlign | " align by 1st `|`
:EasyAlign 3 | " align by 3rd `|`
:EasyAlign * | " align by all `|`s
:EasyAlign * | l4r1
l4 " lN - left_margin
r1 " rN - right_margin
" spaces to the left/right of `|`
ar " a[lrc] - align
" align left/right/center
dr " d[lrc] - delimiter_align
" alignment of the delimiter itself
:EasyAlign * /[;:]+/ l3
:EasyAlign*/[;:]+/l3
:EasyAlign = dr
(delimiter_align right)apple = 1
banana += apple
cake ||= banana
:EasyAlign :
(for json or yaml)url: jdbc:mysql://localhost/test
database: test
:EasyAlign *|
(markdown tables)| `<Enter>` | right align |
| `1` | on 1st occurrence |
| `2` | on 2nd occurrence (and so on) |
{Visual}
⏎
| activate for selection |ga
{motion}
| activate for motion/text object |{:.greycode}
Then press options (if available), then a delimiter.
⏎
| Set alignment
|<ctrl-l>
4 ⏎
| Set left_margin
(to the left of the delimiter) |<ctrl-r>
4 ⏎
| Set right_margin
|↓
| no margin |{:.greycode}
gaip
<ctrl-l>
8⏎
=
- puts 8 spaces before the equal sign


|
PX4 Firmware
PX4 Autopilot Software http://px4.io
|
#include "position_estimator_inav_params.h"
Go to the source code of this file.
Functions | |
| PARAM_DEFINE_FLOAT (INAV_W_Z_BARO, 0.5f) | |
| Z axis weight for barometer. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_Z_GPS_P, 0.005f) | |
| Z axis weight for GPS. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_Z_GPS_V, 0.0f) | |
| Z velocity weight for GPS. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_Z_VIS_P, 5.0f) | |
| Z axis weight for vision. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_Z_LIDAR, 3.0f) | |
| Z axis weight for lidar. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_GPS_P, 1.0f) | |
| XY axis weight for GPS position. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_GPS_V, 2.0f) | |
| XY axis weight for GPS velocity. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_VIS_P, 7.0f) | |
| XY axis weight for vision position. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_VIS_V, 0.0f) | |
| XY axis weight for vision velocity. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_MOC_P, 10.0f) | |
| Weight for mocap system. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_FLOW, 0.8f) | |
| XY axis weight for optical flow. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_XY_RES_V, 0.5f) | |
| XY axis weight for resetting velocity. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_GPS_FLOW, 0.1f) | |
| XY axis weight factor for GPS when optical flow available. More... | |
| PARAM_DEFINE_FLOAT (INAV_W_ACC_BIAS, 0.05f) | |
| Accelerometer bias estimation weight. More... | |
| PARAM_DEFINE_FLOAT (INAV_FLOW_K, 1.35f) | |
| Optical flow scale factor. More... | |
| PARAM_DEFINE_FLOAT (INAV_FLOW_Q_MIN, 0.3f) | |
| Minimal acceptable optical flow quality. More... | |
| PARAM_DEFINE_FLOAT (INAV_LIDAR_ERR, 0.2f) | |
| Sonar maximal error for new surface. More... | |
| PARAM_DEFINE_FLOAT (INAV_LAND_T, 3.0f) | |
| Land detector time. More... | |
| PARAM_DEFINE_FLOAT (INAV_LAND_DISP, 0.7f) | |
| Land detector altitude dispersion threshold. More... | |
| PARAM_DEFINE_FLOAT (INAV_LAND_THR, 0.2f) | |
| Land detector throttle threshold. More... | |
| PARAM_DEFINE_FLOAT (INAV_DELAY_GPS, 0.2f) | |
| GPS delay. More... | |
| PARAM_DEFINE_FLOAT (INAV_FLOW_DIST_X, 0.0f) | |
| Flow module offset (center of rotation) in X direction. More... | |
| PARAM_DEFINE_FLOAT (INAV_FLOW_DIST_Y, 0.0f) | |
| Flow module offset (center of rotation) in Y direction. More... | |
| PARAM_DEFINE_FLOAT (INAV_DISAB_MOCAP, 0) | |
| Mo-cap. More... | |
| PARAM_DEFINE_FLOAT (INAV_LIDAR_EST, 0) | |
| LIDAR for altitude estimation. More... | |
| PARAM_DEFINE_FLOAT (INAV_LIDAR_OFF, 0.0f) | |
| LIDAR calibration offset. More... | |
| PARAM_DEFINE_INT32 (CBRK_NO_VISION, 0) | |
| PARAM_DEFINE_FLOAT | ( | INAV_W_Z_BARO | , |
| 0. | 5f | ||
| ) |
Z axis weight for barometer.
Weight (cutoff frequency) for barometer altitude measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_Z_GPS_P | , |
| 0. | 005f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_W_Z_GPS_V | , |
| 0. | 0f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_W_Z_VIS_P | , |
| 5. | 0f | ||
| ) |
Z axis weight for vision.
Weight (cutoff frequency) for vision altitude measurements. vision altitude data is very noisy and should be used only as slow correction for baro offset.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_Z_LIDAR | , |
| 3. | 0f | ||
| ) |
Z axis weight for lidar.
Weight (cutoff frequency) for lidar measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_GPS_P | , |
| 1. | 0f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_GPS_V | , |
| 2. | 0f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_VIS_P | , |
| 7. | 0f | ||
| ) |
XY axis weight for vision position.
Weight (cutoff frequency) for vision position measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_VIS_V | , |
| 0. | 0f | ||
| ) |
XY axis weight for vision velocity.
Weight (cutoff frequency) for vision velocity measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_MOC_P | , |
| 10. | 0f | ||
| ) |
Weight for mocap system.
Weight (cutoff frequency) for mocap position measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_FLOW | , |
| 0. | 8f | ||
| ) |
XY axis weight for optical flow.
Weight (cutoff frequency) for optical flow (velocity) measurements.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_XY_RES_V | , |
| 0. | 5f | ||
| ) |
XY axis weight for resetting velocity.
When velocity sources lost slowly decrease estimated horizontal velocity with this weight.
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_W_GPS_FLOW | , |
| 0. | 1f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_W_ACC_BIAS | , |
| 0. | 05f | ||
| ) |
Accelerometer bias estimation weight.
Weight (cutoff frequency) for accelerometer bias estimation. 0 to disable.
0.0 0.1 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_FLOW_K | , |
| 1. | 35f | ||
| ) |
Optical flow scale factor.
Factor to scale optical flow
0.0 10.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_FLOW_Q_MIN | , |
| 0. | 3f | ||
| ) |
Minimal acceptable optical flow quality.
0 - lowest quality, 1 - best quality.
0.0 1.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_LIDAR_ERR | , |
| 0. | 2f | ||
| ) |
Sonar maximal error for new surface.
If sonar measurement error is larger than this value it skiped (spike) or accepted as new surface level (if offset is stable).
0.0 1.0 m Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_LAND_T | , |
| 3. | 0f | ||
| ) |
Land detector time.
Vehicle assumed landed if no altitude changes happened during this time on low throttle.
0.0 10.0 s Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_LAND_DISP | , |
| 0. | 7f | ||
| ) |
Land detector altitude dispersion threshold.
Dispersion threshold for triggering land detector.
0.0 10.0 m Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_LAND_THR | , |
| 0. | 2f | ||
| ) |
Land detector throttle threshold.
Value should be lower than minimal hovering thrust. Half of it is good choice.
0.0 1.0 Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_DELAY_GPS | , |
| 0. | 2f | ||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_FLOW_DIST_X | , |
| 0. | 0f | ||
| ) |
Flow module offset (center of rotation) in X direction.
Yaw X flow compensation
-1.0 1.0 m Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_FLOW_DIST_Y | , |
| 0. | 0f | ||
| ) |
Flow module offset (center of rotation) in Y direction.
Yaw Y flow compensation
-1.0 1.0 m Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_DISAB_MOCAP | , |
| 0 | |||
| ) |
| PARAM_DEFINE_FLOAT | ( | INAV_LIDAR_EST | , |
| 0 | |||
| ) |
LIDAR for altitude estimation.
Position Estimator INAV
| PARAM_DEFINE_FLOAT | ( | INAV_LIDAR_OFF | , |
| 0. | 0f | ||
| ) |
LIDAR calibration offset.
LIDAR calibration offset. Value will be added to the measured distance
-20 20 m Position Estimator INAV
| PARAM_DEFINE_INT32 | ( | CBRK_NO_VISION | , |
| 0 | |||
| ) |


| Package | Description |
|---|---|
| teetime.framework |
Contains the implementation of the TeeTime framework.
|
| teetime.framework.exceptionHandling |
Copyright © 2015 Christian Wulf, Nelson Tavares de Sousa (http://teetime-framework.github.io)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
|
| teetime.framework.performancelogging | |
| teetime.framework.performancelogging.formatstrategy | |
| teetime.framework.pipe |
Contains all pipes related classes, such as factories and pre-defined pipes.
|
| teetime.framework.scheduling.globaltaskpool | |
| teetime.framework.scheduling.pushpullmodel | |
| teetime.framework.signal |
Contains the signals that are used within the framework.
|
| teetime.framework.termination | |
| teetime.framework.test |
Copyright © 2015 Christian Wulf, Nelson Tavares de Sousa (http://teetime-framework.github.io)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
|
| teetime.framework.validation | |
| teetime.stage |
Contains a set of pre-defined stages, which are ready-to-go and can be used for analysis configuration.
|
| teetime.stage.basic | |
| teetime.stage.basic.distributor | |
| teetime.stage.basic.distributor.dynamic | |
| teetime.stage.basic.distributor.strategy | |
| teetime.stage.basic.merger | |
| teetime.stage.basic.merger.dynamic | |
| teetime.stage.basic.merger.strategy | |
| teetime.stage.io | |
| teetime.stage.quicksort | |
| teetime.stage.string | |
| teetime.stage.taskfarm | |
| teetime.stage.xml | |
| teetime.util.framework.port |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractPipe
Represents an abstract implementation of an
IPipe . |
| AbstractPort |
| AbstractProducerStage
The
ProducerStage produces at least one element at each execution. |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| Configuration
Represents a configuration of connected stages.
|
| ConfigurationBuilder.Connection |
| ConfigurationContext
Deprecated.
since 3.0. Scheduled to be remove in 3.1 or above without replacement.
|
| ConfigurationFacade
Used to access the package-private methods of
Configuration . |
| InputPort |
| ITraverserVisitor |
| OutputPort |
| RuntimeServiceFacade |
| StageFacade
Used to access the package-private methods of
AbstractStage and CompositeStage . |
| StageState |
| TeeTimeScheduler
Represents a scheduling strategy for TeeTime-based configurations.
|
| TerminationStrategy
Deprecated.
since 3.0.
We will completely remove framework-backed support for infinite producers since it has never worked correctly in all (corner) cases.
Instead, please use finite producers and implement an appropriate termination condition by your own.
|
| Traverser.EndOfTraverse |
| Traverser.VisitorBehavior |
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Class and Description |
|---|
| AbstractPipe
Represents an abstract implementation of an
IPipe . |
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractPort |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Configuration
Represents a configuration of connected stages.
|
| InputPort |
| ITraverserVisitor |
| TeeTimeScheduler
Represents a scheduling strategy for TeeTime-based configurations.
|
| Traverser.VisitorBehavior |
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Configuration
Represents a configuration of connected stages.
|
| TeeTimeScheduler
Represents a scheduling strategy for TeeTime-based configurations.
|
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractProducerStage
The
ProducerStage produces at least one element at each execution. |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| OutputPort |
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| InputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractProducerStage
The
ProducerStage produces at least one element at each execution. |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| CompositeStage
Represents a minimal stage that composes several other stages.
|
| InputPort |
| OutputPort |
| Class and Description |
|---|
| AbstractConsumerStage |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
| Class and Description |
|---|
| AbstractPort |
| AbstractStage
Represents a minimal Stage, with some pre-defined methods.
|
Copyright © 2015–2019. All rights reserved.


A jQuery plugin to build custom HTML5 video players.
The main features of this plugin include the ability to customize the player controls and the ability to choose different quality versions of the same video.
Video by Topic Simple
This plugin is free and open source. You can download, fork it, add new features and suggest something to me via GitHub.
To get it this plugin, you can use the icons below or you can go to http://github.com/vebersol/jQPlayer/ and have fun!
The plugin comes with css files (also sass source files) and the swd file used for fallback in browsers that doesn't support HTML5 video.
jQPlayer is a jQuery plugin. It has been made in order to provide some advantages never seen on most popular plugins.
jQPlayer is a jQuery plugin. It has been made in order to provide some advantages never seen on most popular plugins.

You can setup custom buttons by adding the following property to your options object:
customButtons: { 'custom_button_1': { url: 'http://jqplayer.vebersol.net', label: 'TEST', className: 'custom-button-1', target: '_blank' }, 'custom_button_2': { label: 'TEST', className: 'custom-button-1', onclick: function() { alert('test'); } } }
You can add multiple qualities of videos that contain the source (mp4, webm or ogg). The property label will be used as option on quality menu. And finally, subtitle will be the path to srt file.
videos: { p720: { source: { mp4: 'media/sample720p.mp4', webm: 'media/sample720p.webm' }, label: '720p', subtitle: 'media/subtitles.srt' }, p360: { source: { mp4: 'media/sample360p.mp4', webm: 'media/sample360p.webm' }, label: '360p', subtitle: 'media/subtitles.srt' } }
jQPlayer supports browsers that can't play HTML5 video. By adding a flash fallback using the same HTML structure generated by jQPlayer's for modern browsers.
This plugin has been developed by Vinícius Ebersol.
Thiago Reis designed the concept and final layout of this player.
If you want to contribute, please feel free, we encourage you to do this.
MIT License: http://www.opensource.org/licenses/MIT
Any bug report, please, see this link: https://github.com/vebersol/jQPlayer/issues


#include <stdlib.h> void exit(int status);
All functions registered with atexit(3) and on_exit(3) are called, in the reverse order of their registration. (It is possible for one of these functions to use atexit(3) or on_exit(3) to register an additional function to be executed during exit processing; the new registration is added to the front of the list of functions that remain to be called.) If one of these functions does not return (e.g., it calls _exit(2), or kills itself with a signal), then none of the remaining functions is called, and further exit processing (in particular, flushing of stdio(3) streams) is abandoned. If a function has been registered multiple times using atexit(3) or on_exit(3), then it is called as many times as it was registered.
All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed.
The C standard specifies two constants, EXIT_SUCCESS and EXIT_FAILURE, that may be passed to exit() to indicate successful or unsuccessful termination, respectively.
It is undefined what happens if one of the functions registered using atexit(3) and on_exit(3) calls either exit() or longjmp(3). Note that a call to execve(2) removes registrations created using atexit(3) and on_exit(3).
The use of EXIT_SUCCESS and EXIT_FAILURE is slightly more portable (to non-UNIX environments) than the use of 0 and some nonzero value like 1 or -1. In particular, VMS uses a different convention.
BSD has attempted to standardize exit codes; see the file <sysexits.h>.
After exit(), the exit status must be transmitted to the parent process. There are three cases. If the parent has set SA_NOCLDWAIT, or has set the SIGCHLD handler to SIG_IGN, the status is discarded. If the parent was waiting on the child, it is notified of the exit status. In both cases the exiting process dies immediately. If the parent has not indicated that it is not interested in the exit status, but is not waiting, the exiting process turns into a "zombie" process (which is nothing but a container for the single byte representing the exit status) so that the parent can learn the exit status when it later calls one of the wait(2) functions.
If the implementation supports the SIGCHLD signal, this signal is sent to the parent. If the parent has set SA_NOCLDWAIT, it is undefined whether a SIGCHLD signal is sent.
If the process is a session leader and its controlling terminal is the controlling terminal of the session, then each process in the foreground process group of this controlling terminal is sent a SIGHUP signal, and the terminal is disassociated from this session, allowing it to be acquired by a new controlling process.
If the exit of the process causes a process group to become orphaned, and if any member of the newly orphaned process group is stopped, then a SIGHUP signal followed by a SIGCONT signal will be sent to each process in this process group. See setpgid(2) for an explanation of orphaned process groups.


Preparation time
over 2 hours
Cooking time
1 to 2 hours
Serves
Makes 48 vol-au-vents
Go over all retro and make a double quantity of puff pastry to serve show-stopping sweet and savoury vol-au-vents at your party.
Equipment and preparation: you will need a free-standing mixer, a 5cm/2in and a 2.5cm/1in round cutter, a piping bag and at least 2 sheets of silicon for lining the baking tray and covering the pastry.
For the puff pastry, in a freestanding mixer, beat the flour and water together until elastic. Remove half of the dough and roll it into a small rectangle 2.5cm/1in thick. Repeat with the second half, then cover both rectangles with cling film and place in the freezer.
Divide the butter into two and roll out between two sheets of greaseproof paper - a wooden rolling pin will help to flatten the cold butter. Return both slabs of butter to the fridge.
Remove one of the rectangles of dough from the freezer and roll out to double the size of the butter. Keep the second dough in the freezer while you are doing this. Put half of the butter on the top of half of the dough and fold up, sealing the butter into the dough. Roll out to form a long rectangle. Fold the top and bottom in like a book to meet in the middle. Turn 90 degrees and then repeat the rolling and folding. Turn again and repeat the action. Wrap in cling film and return to the freezer.
Repeat this process with the second dough rectangle, sieving the cocoa onto the first lamination.
Perform the lamination process a further two times for each dough, allowing to rest in the freezer for at least 20 minutes each time. While waiting for the doughs to rest make the individual fillings.
For the pesto, blend together all the pesto ingredients in a food processor, adding more oil if required. Put into a jar and keep in the fridge until required.
For the praline paste, line a baking tray with silicon. Dry fry the hazelnuts for a few minutes until beginning to colour. Heat the caster sugar in a pan over a medium heat with 2 tablespoons of water until the colour of caramel. Do not stir after the sugar has dissolved.
Add the nuts to the sugar and pour onto the baking tray. Once solid, break into pieces and blend in a food processor until smooth (about 5 minutes). Store in a jar in the fridge until required.
For the crème légère, heat the milk and vanilla bean paste in a pan until boiling. Whisk the egg yolk, caster sugar and cornflour in a bowl until smooth, before pouring the hot milk on top.
Pour the mixture back into the pan, add the butter and whisk on a medium heat until thickened. Pour into a container, press cling film onto the surface of the cream and allow to cool. Once fully chilled, whisk the double cream in a mixing bowl until soft peaks form when the whisk is removed from the bowl. Stir the cream into the crème légère and whisk until smooth. Store in a container in the fridge until required.
For the ganache, bring the cream to the boil in a pan. Place the chopped chocolate and espresso in a bowl. Pour the cream over the chocolate and espresso and mix until smooth and fully melted. Allow to cool slightly before transferring into a piping bag.
To complete the pastry for the vol-au-vents, preheat the oven to 200C/180C Fan/Gas 6.
Roll the doughs out to 5mm/¼in thick. Using a 5cm/2in round cutter, cut out 24 discs from each dough. Then cut out a 2.5cm/1in circle from the centre. Gather the centres and the scraps of pastry into a ball then re-roll again to 5mm//¼in thick and cut another 24 x 5cm/2in rounds from each dough.
Brush the full discs with a little egg and place a ring on top of each. Using a sharp fork, prick the bottom evenly and all over. Brush with a little more egg. Arrange the vol-au-vents on a large baking tray lined with silicon. Cover with another large sheet of silicon (this helps them to rise evenly). Bake for 15 minutes, checking regularly and only removing the top sheet when fully risen (the chocolate ones might need a little longer). Leave to cool completely. Leave the oven on.
Pipe rounds of ganache into the bottom of the chocolate vol-au-vents and allow to set.
For the savoury filling, add the asparagus, peas, mangetout and broad beans to a bowl, cover with boiling water and leave for 4 minutes to cook gently. Drain all the vegetables, then reserve the very tips of the asparagus for decoration and cut the stalks in half. Add the oil and pesto to the bowl of vegetables and mix well.
Lay the Parma ham flat on a baking tray and bake for 5 minutes, or until fully crisp. Remove from the oven and cut into thin strips.
For the cheese, beat the cream cheese in a bowl with a little salt and pepper and the lemon juice until smooth.
To decorate the savoury vol-au-vents, transfer the cream cheese into a piping bag and pipe a round into the bottom of the plain vol-au-vents. Top with a couple of teaspoons of pesto before adding the vegetables on top until almost full. Pipe a little cream cheese on top and add a Parma ham strip and asparagus tips. Decorate with radishes, micro herbs and a little basil oil. Store in the fridge until required.
To decorate the sweet vol-au-vents, dry fry the hazelnuts and chop. Spoon the praline paste on top of the set ganache. Transfer the crème légère into a piping bag and pipe a blob on top. Sprinkle with the chopped nuts.
Create chocolate curls by using a big flat sharp knife and the bottom of the chocolate bar. Drag the knife over the bar towards you and scoop the curls with the knife - as your hands will melt them.
Sprinkle the chocolate curls on top of the vol-au-vents. The vol-au-vents are best served immediately.


Gateway
BOT
31-May-19 12:14 PM
[dscrd-hush] <dukeleto> @ITM firstly, these are very good questions and things I think about as well
[dscrd-hush] <dukeleto> !tip @ITM 5 hush
[dscrd-hush] <Discordtip> @dukeleto sent @ITM 5.00 HUSH (≈ $0.34).
[dscrd-hush] <dukeleto> @ITM HushList is a protocol, and any sapling-enabled coin can implement. So if people "clone" Hush or HushList, then we have won, because they are adopting our tech. HushList protocol is like HTTP versus a single website. It's much larger than one blockchain or coin or network. To fully realize it's potential, thousands of networks will have HushList protocol and some will be private + temporary, coming in and out of existence, while others will be public and longer lasting.
[dscrd-hush] <dukeleto> In that seething chaos, HUSH+ARRR are (arrr) at the forefront of shielded transaction adoption. I don't see a lot of Zcash protocol coins actually focused on using their shielded addresses, outside of HUSH+ARRR
[dscrd-hush] <dukeleto> @ITM your mempool tricks could be seen as a bug or feature
[dscrd-hush] <dukeleto> @ITM and radix42 mentioned those tricks to me, that they were probably possible, about 2 years ago. We considered them a feature back then
[dscrd-hush] <dukeleto> because it allows you to avoid having to wait 2.5 minutes for a confirmation
(edited)
[dscrd-hush] <dukeleto> @ITM i really like that you are thinking of attack scenarios
[dscrd-hush] <dukeleto> @ITM in my original hushlist paper, I described it as a HushList Protocol: Censorship Resistant Metadata-Minimizing Multi-Blockchain Communication
[dscrd-hush] <dukeleto> Multi-Blockchain is very important, it's not just lip service
[dscrd-hush] <dukeleto> If I was a nation state, and I knew that only one blockchain has certain data, then I can block 1 P2P port and that prevents many people from accessing that blockchain in that country
[dscrd-hush] <dukeleto> But if the application can send your data to a random assortment of 1000 blockchains, or however many you want, blocking P2P ports of individual coins is no longer viable. each blockchain is an independent backup of your data
[dscrd-hush] <dukeleto> and different coins are likely to have different node operators, so your data gets spread in different ways geographically, when you spread it across a few chains
[dscrd-hush] <dukeleto> censorship concerns are more important for public data
[dscrd-hush] <dukeleto> for private data, you don't really need to worry about all that stuff
[dscrd-hush] <ComputerGenie - ChickenPool> If I send 10000 messages at 0.1 hush total ....How is this type of attack dealt with?
I make 10 blocks and take your 0.1 HUSH
[dscrd-hush] <dukeleto> fee=0 xtns still worked on the old Hush v2 network, haven't tried them much on our new mainnet
[dscrd-hush] <ComputerGenie - ChickenPool> likely most clients would ignore them due to relay fee default


Deleting this scenario will alter all workshops that utilize it.
Are you sure you want to delete this scenario?


We give you what you want
Make as many orders as you can depending on your pocket
it is type of pizza made from cured pork and beef mixed together and seasoned with paprika
Orderit is type of pizza made from cured pork and beef mixed together and seasoned with paprika
Orderit is type of pizza made from cured pork and beef mixed together and seasoned with paprika
Order


TITANIC (1997)
Starring: You already know the whole damn list
Directed by: James Cameron, Written by: James Cameron
Rated PG-13 by the MPAA for disturbingly realistic violence, nudity, sexual situations, and strong language - in other words, it should've been an R
Reviewed by Doug Skiles
It's hard to say something about this movie that hasn't already been said. Yes, it's brilliant. Yes, the performances are all excellent. It's very well-written, and expertly directed. What else needs to be said?
Well, since you know that this film is an epic of excellence, maybe some of its flaws should be pointed out for a change. I'm not saying that it's not excellent. But somewhere, in the shuffle, we forgot that it's not perfect.
There's little things, like the fact that the special effects, usually excellent, also have clear flaws (obvious stunt doubles with texture-mapped Leo and Kate faces in some scenes, obvious CGI people in some daytime overhead shots). There's big things, like the fact that Billy Zane's Cal Hockley and David Warner's Spicer Lovejoy, who act as the villians, are ridiculously one-note and depicted as having no redeeming qualities. It's even taken so far as to have Lovejoy put Jack in a James Bond-style slow-moving death trap, where Spicer mocks Jack briefly and then leaves him to his fate, just as someone like Goldfinger would have done to Sean Connery. You almost expect the dialogue to go: "Do you expect me to talk? - Jack Dawson "No, Mr. Dawson, I expect you to die!" - Spicer Lovejoy
That's never said, but the dialogue that IS there does sound a little cheesy at times. However, it's usually excellent, the stuff that Cameron's legacy is made of. All of his movies have dialogue that always sticks with us, from the emotional union between Ripley and Newt in ALIENS (1986) to the simple one-liners like "Hasta la vista, baby" in TERMINATOR 2: JUDGEMENT DAY (1991).
But perhaps the problem with the farthest-reaching implications is the destruction of moral standards. Why does Hollywood feel the need to always depict marriage as a trap? Is it because Hollywood marriages so rarely last? Maybe Cameron wanted to justify his own three marriages? And why do we always promote teen sex in Hollywood films?  We tell them to do what feels right, regardless of how they feel about the person they use to get it. When you see Jack and Rose in this film, they're like a latter-day Romeo and Juliet - star-crossed and doomed lovers, who fall in love with their idealized images of each other rather than the truths. Like Romeo and Juliet, they believe that they've found love, but they barely know one another - they've found infatuation, a temporary, yet powerful feeling that leads them to do anything in their power to protect their relationship. Only as the years go own, and Rose grows older, does she realize that, yes, Jack was someone she will always cherish for all that he did for her. But it's pretty doubtful that she could've understood that at her young age on that Titanic voyage, after having only two days with the boy.
Some of these problems sound serious, but Cameron's narrative is such effective storytelling that it doesn't matter very much. It's the thing of legend - an epic in the style of Hollywood's golden age. It can leave you disquieted, or it can leave you uplifted - or, more likely, both at once. Today, parents look at their children and smile, telling them how it felt to see STAR WARS (1977) in it original theatrical release. Years from now, the youth of today will have their own movie stories to share, and surely TITANIC will be one of them.
Rating: ****
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


The task for this week was to mill out a Reserve seal on the CNC milling machine by using V-Carve. We used a 1/4 inch ball nose and a engraving bit to carve this out. We thought that carving the seal out would be successful in one trial, it actually took us several trials.
The first picture is the failed version of the seal, and the second one is the successful one! We also tried using different types of wood to print. The more rough the texture was, the more debris it left. (Using the wood with softer texture prints much more smoothly!)
The first trial did not go well because the pocket depth was too deep, causing the wood to be penetrated. Also, there were several cases when the machine stopped working over night.
Therefore, we changed the pocket depth to be less, and ran the machine again. On our next trial, it came out to be successful!


tobacco California control Accessed legal childhood March Health TALC model {border-style NPLAN obesity state px;} #ffffff;} health movement technical assistance policy use (CA) Control social Tobacco change laws norm local Legal Oakland Policy public smoking CTCP Childhood Public communities prevention school Disease Law Obesity stakeholders Centers Prevention TALC's approach community lawyers ordinance px; research {font-family Department NPLAN's National Network advocates font-size resources Analysis Center Foundation Johnson Policy, Prevent Robert Wood attorneys creating double; law learning needs new ordinances physical provide role Code Model activity border-color border-width epidemic food products retailer solid; support {font-style {font-weight California, Research access assessment available bold case courier; developed development does em; facilities government healthy influence interventions joint-use license limiting national obesity. programs providing pt;} range requiring reverse tools


We are Melbourne’s specialist framers serving the community for over 30 years.
Offering a comprehensive variety of frames and professional consultation for any framing project, including prints, photos, canvas, memorabilia items, and more.
Visit us for a professional consultation on how to best frame and protect your work.
Get a quoteWe provide elegance and protection to your works of art, so that it may be treasured for many years to come.
We specialise in presenting prints, photos, canvas artwork, embroideries, certificates and limited-edition memorabilia.
Choose from a comprehensive variety of traditional timber and aluminium frames, mats and glass.
With over 30 years of experience, we will aid you your choice of frame to ensure it will best complement your artwork.
We also cater to corporate clients and are proud to have catered to many large organisations over the years.
We'd be delighted to give you a free quote and professional advice for any project from restoration and repairs to how to best present and protect your artwork.
2/58 Anderson Street
Yarraville VIC 3013
Mon–Fri: 09:30–17:30
Saturday: 09:30–15:00
(Now until 3pm on Saturdays!)
Closed on public holidays
Quotes and questions?


The cylindrical coordinates is a right-hand system, with the positive direction of pointing vertically up. In GTAW code, the positive direction of is chosen in the anticlockwise direction when observers look along the direction of . Then the definition indicates that (1) is negative if points from the magnetic axis to LCFS; (2) is positive if points from the LCFS to the magnetic axis. This can be used to determine the sign of Jacobian after using the analytical formula to obtain the absolute value of Jacobian.
The radial coordinate can be chosen to be various surface function, e.g., volume, poloidal or toroidal magnetic flux within a magnetic surface.
The frequently used radial coordinates include , and , where is defined by
| (202) |
| (203) |
| (204) |
If , then
| (205) |
yj 2018-03-09


By sending text messages to +46 76 686 8885 you can publish an itinerary for your upcoming hike. Start by creating your map:
create
After you have sent this from your phone you can access your map at
https://fiordland.antarkt.is/map?map=46701740675
, if your phone number is +46 70 174 0675, that is.
Now you can specify your planned path. For each stop along the way you have to specify:
33 7531609 668818
416476 7527469 UTM34
utm33 N7531<PHONE_NUMBER>3
e416476 34 7527469
2021-03-23
There are also optional data you can add:
11:30
"Starting here on my hike to Abisko."

33 N7533 E652
2021-07-10 19:00
"Staying at Keb for the night."
33 N7538 E638
2021-07-11
"Pitching my tent around here."
33 N7540 E637
2021-07-12
"Planning for a short day to get a good
nights sleep in Sälka."
33 N7549 E635
2021-07-13 13:00
"I hope to get a lunch with a view in Tjäkta."
33 N7562 E642
2021-07-13 22:00
"After a long day I hopefully reach Alesjaure."
33 N7579 E648
2021-07-14
"When I come to Abiskojaure, it's really not that
far to Abisko anymore."
33 N7587 E656
2021-07-14 15:00
"In Abisko I hope to get a really nice fika."
There are a couple of things to note:
edit
. Just make sure you
send all the full plan again, not just the part you want to
change.
Things doesn't always go as planned. Therefore it is a good idea to be
able to check in during the hike, to tell people where you are and that
you are alright. To do this, send a checkin
message:
checkin
33 N7533 E652
"Staying at Keb an extra night due to the ongoing snow storm."
Of course you can post messages commenting the view as well
:)
As you can see, a checkin
message looks just like an
edit
message, but with just one point. And here you don't
have to type in the date if you don't want to. You can't edit a checkin.
When you are home, sound and safe, you might want to start to plan your
next hike. Then it is easy to erase your current map with a
complete
message:
complete
This will delete all of your data from the server. After that
you can start a new map using create
again.
This is an experiment, and the site may go down at any time.
Fork me on GitHub


Keith wishes there was a way to find a blend of coffee that he actually likes without spending so much money experimenting. He verbalizes this frustration when he's with his friends, and they tell him about Black Rifle Coffee's online quiz that does just that..
Keith decides to take their advice.


Before you start working on Chapter 7, get the 7-begin
codebase. The 7-begin folder is located at the root of the book
directory inside the builderbook repo.
git clone https://github.com/builderbook/builderbook.git
.7-begin
folder, run yarn
to install all packages. Check out the package.json for Chapter 7.
Remember to include your .env
at the root of your app.
In Chapter 5, you built the ReadChapter
page, which displays the content of one chapter. In this chapter, we will make multiple improvements to this page.
For example, a user should be able to navigate between chapters and between sections within one chapter. To achieve that, we need to introduce a Table of Contents (TOC). The TOC should contain:
Here's an example of what the TOC would look like:
On a high level, we will add the TOC to our ReadChapter
page in two main steps. We will discuss and write:
renderSections()
function that returns a list of hyperlinked sections within one chapterrenderSidebar()
function that returns a list of hyperlinked titles for all chapters and includes renderSections()
under each chapter's titleWe will add the renderSidebar()
function to the ReadChapter
component's render()
function and then test out the TOC.
In this subsection, we define the renderSections()
function. This function returns a list of hyperlinked sections for one chapter.
Recall how we defined the sections
parameter in our Chapter model. Open server/models/Chapter.js
and look at the schema:
sections: [
{
text: String,
level: Number,
escapedText: String,
},
],
Sections is an array of objects, and each object has text
, level
, and escapedText
. Where do these three parameters come from? In the same server/models/Chapter.js
file, find how we generate the sections
array:
const sections = getSections(content);
Definition of getSections()
:
function getSections(content) {
const renderer = new marked.Renderer();
const sections = [];
renderer.heading = (text, level) => {
if (level !== 2) {
return;
}
const escapedText = text
.trim()
.toLowerCase()
.replace(/[^\w]+/g, '-');
sections.push({ text, level, escapedText });
};
marked.setOptions({
renderer,
});
marked(he.decode(content));
return sections;
}
Marked
parses markdown content
and finds headings with level
equal to 2 (any heading that has ##
). For every heading, we push an object to the sections
array:
sections.push({ text, level, escapedText })
In other words, if your markdown content
has:
## Why this book?
Then getSections(content)
will return this array:
[
{
"text": "Why this book?",
"level": 2,
"escapedText": "why-this-book-"
},
]
We will use text
as text inside {text}
and use escapedText
for href
. When a user clicks on a hyperlinked section inside the TOC, we want the page to scroll to the beginning of that section. In fact, when we wrote our markdownToHtml()
function in Chapter 6, we defined the

heading as follows:
if (level === 2) {
return `<h${level} class="chapter-section" style="color: #222; font-weight: 400;">

link

${text}
</h${level}>`;
}
Let's say we have the heading ## Why this book?
in our markdown content. When user a clicks on the link
icon next to that heading, two things will happen:
#why-this-book-
appended to it
has the name attributeWe want to get the exact same behaviour when a user clicks on the hyperlinked section text
inside our TOC. Thus we get:
<a href={`#${s.escapedText}`}>
{s.text}

Keep in mind that sections
is an array. Thus, you should use JavaScript's method .map():

{sections.map(s => (

<a href={`#${s.escapedText}`}>
{s.text}

))}

You already used this method earlier in this book. Check out books.map()
in pages/admin/index.js
.
At this point, the main part of our renderSection()
function is actually done.
Open pages/public/read-chapter.js
. Remember that we send a chapter
object to a page with:
const chapter = await getChapterDetail()
And then we set the initial state of our ReadChapter
page component with:
this.state = {
chapter,
htmlContent,
};
Now let's use ES6 object destructuring to define sections
as this.state.chapter.sections
:
const { sections } = this.state.chapter
Also, let's return null if the section array does not exist or has zero objects:
if (!sections || !sections.length === 0) {
return null;
}
Put together these two code snippets, plus return a list of hyperlinked sections, and you get:
renderSections() {
const { sections } = this.state.chapter;
if (!sections || !sections.length === 0) {
return null;
}
return (

{sections.map(s => (

<a href={`#${s.escapedText}`}>
{s.text}

))}

);
}
Good job if you got the same result. We'll add this to our read-chapter.js
page at the end of the next subsection.
In this subsection, we discuss the renderSidebar()
function that returns a list of hyperlinked titles for all chapters. We will then add our renderSection()
function from above to add a list of all sections under each chapter title.
Similar to how we defined renderSections()
, we will use list, list item, and anchor elements together with the JavaScript method .map()
for our renderSidebar()
function:
renderSidebar() {
return (

{book.name}

{chapters.map((ch, i) => (

<Link
prefetch
as={`/books/${book.slug}/${ch.slug}`}
href={`/public/read-chapter?bookSlug=${book.slug}&chapterSlug=${ch.slug}`}
>
{ch.title}
</Link>

))}

);
}
The only differences are:

, we use an ordered list

with Next.js's <Link>
, so we can take advantage of the prefetch
feature in production
{book.name}

Time to add renderSections()
to renderSidebar()
to our code. In doing so, we should think about creating good UX. Do we want every chapter to have a list of sections on the TOC? That might be an overwhelming amount of information.
It's sufficient to show sections for only the chapter that is currently rendered on the ReadChapter
page. In other words, if the chapter id from the page's state
(chapter._id
) equals the chapter id from the list (ch._id
), then we display the list of sections. Otherwise, we return null:
{chapter._id === ch._id ? this.renderSections() : null}
Add this line of code right after the hyperlinked title of the chapter in the ReadChapter
page:
renderSidebar() {
return (

{book.name}

{chapters.map((ch, i) => (

<Link
prefetch
as={`/books/${book.slug}/${ch.slug}`}
href={`/public/read-chapter?bookSlug=${book.slug}&chapterSlug=${ch.slug}`}
>
{ch.title}
</Link>
{chapter._id === ch._id ? this.renderSections() : null}

))}

);
}
Alright, the main part of our renderSidebar()
function is done. However, if you look at the code above, you may notice that we have not defined the following three variables:
chapter
(we used it in chapter._id
)book
(used in book.name
)chapters
(used in chapters.map()
)Let's discuss in more detail.
Defining chapter
is easy, since we initiate state
with chapter
in it. Thus:
const { chapter } = this.state;
To understand how to define book
, we should look into how we define chapter
. In pages/public/read-chapter.js
, find the line:
const chapter = await getChapterDetail()
The API method getChapterDetail()
sends a request to our Express route router.get('/get-chapter-detail')
. Open server/api/public.js
and find this Express route:
router.get('/get-chapter-detail', async (req, res) => {
try {
const { bookSlug, chapterSlug } = req.query;
const chapter = await Chapter.getBySlug({
bookSlug,
chapterSlug,
});
res.json(chapter);
} catch (err) {
res.json({ error: err.message || err.toString() });
}
});
Ok, now we remember that chapter
gets returned by the Chapter.getBySlug
static method. Let's look back at that method. Open server/models/Chapter.js
and find getBySlug()
static method:
static async getBySlug({ bookSlug, chapterSlug }) {
const book = await Book.getBySlug({ slug: bookSlug, userId });
if (!book) {
throw new Error('Book not found');
}
const chapter = await this.findOne({ bookId: book._id, slug: chapterSlug });
if (!chapter) {
throw new Error('Chapter not found');
}
const chapterObj = chapter.toObject();
chapterObj.book = book;
return chapterObj;
}
Aha! So the book
object is part of the chapter
object! Because chapterObj.book = book;
and const chapter = await getChapterDetail()
, then:
const book = chapter.book
Or with ES6 object destructuring:
const { book } = chapter
To define chapters
, we need to dig a bit deeper. Inside Chapter.getBySlug()
, we defined book
as follows:
const book = await Book.getBySlug()
Open server/models/Book.js
and find the static method getBySlug()
:
static async getBySlug({ slug }) {
const bookDoc = await this.findOne({ slug });
if (!bookDoc) {
throw new Error('Book not found');
}
const book = bookDoc.toObject();
book.chapters = (await Chapter.find({ bookId: book._id }, 'title slug')
.sort({ order: 1 }))
.map(chapter => chapter.toObject());
return book;
}
We can clearly see that the chapters
object is part of the book
object. Thus:
const chapters = chapter.book.chapters
Or with ES6 object destructuring:
const { chapters } = book
Put these definitions of chapter
, book
, and chapters
into renderSidebar()
:
renderSidebar() {
const { chapter } = this.state;
const { book } = chapter;
const { chapters } = book;
return (

{book.name}

{chapters.map((ch, i) => (

<Link
prefetch
as={`/books/${book.slug}/${ch.slug}`}
href={`/public/read-chapter?bookSlug=${book.slug}&chapterSlug=${ch.slug}`}
>
{ch.title}
</Link>
{chapter._id === ch._id ? this.renderSections() : null}

))}

);
}
This book assumes that you have basic knowledge of CSS, so we won't discuss styling in great detail. Below, we simply added inline CSS to style some HTML elements:
renderSidebar() {
const { chapter } = this.state;
const { book } = chapter;
const { chapters } = book;
return (

{book.name}

{chapters.map((ch, i) => (

<Link
prefetch
as={`/books/${book.slug}/${ch.slug}`}
href={`/public/read-chapter?bookSlug=${book.slug}&chapterSlug=${ch.slug}`}
>
{ch.title}
</Link>
{chapter._id === ch._id ? this.renderSections() : null}

))}

);
}
The ternary operator on styles is worth mentioning. We've used the ternary operator many times before, and you can use it inside inline styles:
condition ? value1 : value2
Look at the style of a list item in

:
style={{ listStyle: i === 0 ? 'none' : 'decimal' }}
If a list item has an index of zero, then the list style type is none
.
If a list item has an index other than zero, then the list style type is decimal
.
This will ensure that our rendered TOC looks like:
Introduction
1. App structure...
The Introduction chapter won't have a number as a list item marker, but the rest of the chapters will.
Another use of the ternary operator:
style={{ color: chapter._id === ch._id ? '#1565C0' : '#222' }}
This allows us to highlight the chapter title of only the currently rendered chapter on the ReadChapter
page. For example, if a reader is on Chapter 7, then the TOC will only highlight the title of Chapter 7.
At this point, we are ready to add our renderSections()
and renderSidebar()
functions to the ReadChapter
page.
Add these functions to the pages/public/read-chapter.js
file like this:pages/public/read-chapter.js
:
import React from 'react';
import PropTypes from 'prop-types';
import Error from 'next/error';
import Head from 'next/head';
import Link from 'next/link';
import { getChapterDetail } from '../../lib/api/public';
import withAuth from '../../lib/withAuth';
const styleIcon = {
opacity: '0.75',
fontSize: '24px',
cursor: 'pointer',
};
class ReadChapter extends React.Component {
static propTypes = {
chapter: PropTypes.shape({
_id: PropTypes.string.isRequired,
}),
};
static defaultProps = {
chapter: null,
};
constructor(props) {
super(props);
const { chapter } = props;
let htmlContent = '';
if (chapter) {
htmlContent = chapter.htmlContent;
}
this.state = {
chapter,
htmlContent,
};
}
componentWillReceiveProps(nextProps) {
const { chapter } = nextProps;
if (chapter && chapter._id !== this.props.chapter._id) {
const { htmlContent } = chapter;
this.setState({ chapter, htmlContent });
}
}
static async getInitialProps({ req, query }) {
const { bookSlug, chapterSlug } = query;
const headers = {};
if (req && req.headers && req.headers.cookie) {
headers.cookie = req.headers.cookie;
}
const chapter = await getChapterDetail({ bookSlug, chapterSlug }, { headers });
return { chapter };
}
renderMainContent() {
const { chapter, htmlContent } = this.state;
return (

Chapter: {chapter.title}

);
}
renderSections() {
const { sections } = this.state.chapter;
if (!sections || !sections.length === 0) {
return null;
}
return (

{sections.map(s => (

<a href={`#${s.escapedText}`}>
{s.text}

))}

);
}
renderSidebar() {
const { chapter } = this.state;
const { book } = chapter;
const { chapters } = book;
return (

{book.name}

{chapters.map((ch, i) => (

<Link
prefetch
as={`/books/${book.slug}/${ch.slug}`}
href={`/public/read-chapter?bookSlug=${book.slug}&chapterSlug=${ch.slug}`}
>
{ch.title}
</Link>
{chapter._id === ch._id ? this.renderSections() : null}

))}

);
}
render() {
const { chapter } = this.state;
if (!chapter) {
return <Error statusCode={404} />;
}
return (

<title>
{chapter.title === 'Introduction'
? 'Introduction'
: `Chapter ${chapter.order - 1}. ${chapter.title}`}
</title>
{chapter.seoDescription ? (
<meta name="description" content={chapter.seoDescription} />
) : null}

{this.renderSidebar()}

{this.renderMainContent()}

format_list_bulleted

);
}
}
export default withAuth(ReadChapter, { loginRequired: false });
Notice that we added the Material icon format_list_bulleted
that does nothing at this point.
Time to test, we will test using the book made from the /demo-book
repo (see Chapter 6).
Start your app with yarn dev
and go to http://localhost:8000/books/demo-book/introduction
:
Not bad! Try clicking on sections within the Introduction. Also try clicking "Example", the title of Chapter 1.
Having the TOC always present is not good UX, since it takes attention away from the main content. In the next subsection, let's add a boolean parameter that allows a reader to hide/reveal the TOC.
In this subsection, we want make the TOC closed on the initial load of the ReadChapter
page. The easiest way to achieve this is to introduce a showTOC
boolean parameter and set it to false
when we initiate state. The page's constructor
becomes:
constructor(props, ...args) {
super(props, ...args);
const { chapter } = props;
let htmlContent = '';
if (chapter) {
htmlContent = chapter.htmlContent;
}
this.state = {
showTOC: false,
chapter,
htmlContent,
};
}
Next, modify the renderSidebar()
function. This line:
const { chapter } = this.state;
Becomes:
const { showTOC, chapter } = this.state;
Under this line, add a code snippet that ensures that renderSidebar()
returns null
when showTOC
is false:
if (!showTOC) {
return null;
}
This code snippet above will ensure that the TOC is closed on initial page load.
Next, we want to define a function that gets executed when a user clicks on on format_list_bulleted
icon:
toggleChapterList = () => {
this.setState({ showTOC: !this.state.showTOC });
};
Add this toggleChapterList
function right before the line that has:
renderMainContent()
Clicking on an icon should change showTOC
from its default false value to true
. Modify our icon code for format_list_bulleted
like this:

format_list_bulleted

Start your app with yarn dev
and go to http://localhost:8000/books/demo-book/introduction
.
Initial load:
After clicking the format_list_bulleted
icon:
Nice, this is much better than having an open TOC at all times.
In the next section, we will make another UX improvement for our TOC.
In the previous section, we highlighted a chapter title on the TOC with a blue color if that chapter was rendered on the page. This is a nice feature that helps a user navigate a book:
In this snapshot, you see that the TOC highlights Introduction, since the page shows the Introduction chapter.
However, you'll notice that both sections within the Introduction chapter are highlighted as well. It would be great if our TOC highlights only one section from the chapter - the section that is currently in view. That way, the highlighted section tells a user which particular section he/she is reading.
To highlight a section, we need to somehow detect if a particular section inside our content is in view.
In Chapter 6, when we wrote the markdownToHtml()
function inside our server/models/Chapter.js
file, we specified the following rule for conversion of the

heading:
if (level === 2) {
return `<h${level} class="chapter-section" style="color: #222; font-weight: 400;">

link

${text}

</h${level}>`;
}
Back in Chapter 6, we added class="section-anchor"
to the
element without explaining why. We will use this class to detect all
elements and match them to sections in the TOC. When matched, we will highlight corresponding section in the TOC.
Each chapter has multiple

headings (i.e. sections). To highlight a particular section inside the TOC, we need to know which
element with the class section-anchor
is in view. Once we know which
element is in view, we extract the ${escapedText}
value from the name
attribute of this
element.
After we know the ${escapedText}
value of the
element in view, we can compare it to the ${s.escapedText}
value of the section inside the TOC. If the values match, we highlight that section in the TOC with a blue color.
To detect which
element is in view, we should do the following:
onScroll
functionspan.section-anchor
elements inside this onScroll
function, then create an activeSection
object and add the ${s.escapedText}
value to it as activeSection.hash
activeSection
Below we discuss each step in detail.
To detect a scroll event, we use JavaScript's addEventListener and type scroll
:
document.getElementById('main-content').addEventListener('scroll');
To execute a function (onScroll
) on every scroll event:
document.getElementById('main-content').addEventListener('scroll', this.onScroll);
Important: add id="main-content"
to the

element that is located right below the ...
element inside render()
at pages/public/read-chapter.js
. If you don't add this line, then scrolling won't be detected.
Since we detect a scroll event on the client (browser), we should place code inside the componentDidMount()
lifecycle hook:
componentDidMount() {
document.getElementById('main-content').addEventListener('scroll', this.onScroll);
}
When a user navigates to a new page, the app does not need to listen for scrolling events on this new page. Thus, we should remove the event listener with:
componentWillUnmount() {
document.getElementById('main-content').removeEventListener('scroll', this.onScroll);
}
Add the following code snippet right after constructor
:
componentDidMount() {
document.getElementById('main-content').addEventListener('scroll', this.onScroll);
}
componentWillUnmount() {
document.getElementById('main-content').removeEventListener('scroll', this.onScroll);
}
In this step, let's create a basic version of our onScroll
function:
onScroll = () => {
// some code
};
We need to detect all
elements with class="section-anchor"
:
const sectionElms = document.querySelectorAll('span.section-anchor');
Let's define an activeSection
object as:
let activeSection;
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
Method getNamedItem('name').value gets us the value of the name
attribute.
We can use a for loop to run code for every
element:
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
Put these code snippets together, and you get:
onScroll = () => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
};
This one is easy, simply add this.setState({ activeSection })
at the end of the onScroll
function:
onScroll = () => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
this.setState({ activeSection });
};
Add this function after the componentWillUnmount()
lifecycle hook in pages/public/read-chapter.js
.
Inside the renderSections()
function (pages/public/read-chapter.js
), after line:
const { sections } = this.state.chapter;
Add:
const { activeSection } = this.state;
console.log(activeSection);
Inside the renderSections()
function, add a conditional style to the
element with the ternary operator. If activeSection
exists and if activeSection.hash
equals s.escaptedText
(we take this value from our database with chapter.sections.map(s => ..)
) - then we highlight the section (s.text
) with a blue color:
<a
href={`#${s.escapedText}`}
style={{
color: activeSection && activeSection.hash === s.escapedText ? '#1565C0' : '#222',
}}
>
{s.text}

Before we move to final step (step 5), we should test what we have so far.
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
. Open the browser console (Developer tools
> Console
) and then open the TOC.
Scroll down the chapter and look at the browser console:
You should see dozens of activeSection
objects printed on your browser console:
{hash: "build-tic-tac-toe-game"}
That's the expected behavior - good job if you see it! However we should consider some improvements. For example, even a small scroll results in over two dozen of activeSection
objects. That means a small scroll fires the onScroll
function over two dozen times. This is overkill. A common way to throttle a function in JavaScript applications is to use throttle from lodash. Let's update our onScroll
function like this:
onScroll = throttle(() => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
this.setState({ activeSection });
}, 500);
Remember to add the missing import:
import throttle from 'lodash/throttle';
This is better - with throttle, the function will fire only once per every 500 milliseconds.
Go to http://localhost:8000/books/demo-book/introduction
. Refresh the tab, open the TOC, and open the browser console.
Scroll down the chapter - now with throttle, a small scroll only prints a few activeSection
objects to your browser console. Exactly what we want.
You may have noticed that the activeSection
object printed to the browser console is always:
{hash: "build-tic-tac-toe-game"}
We never see an activeSection
for the first
element (What is Lorem Ipsum?
).
Also you must have noticed that the TOC highlights the Build Tic Tac Toe Game
section only. The TOC never highlights the What is Lorem Ipsum?
section.
That's because our for loop
runs without break. For loop
runs for i=0, then for i=1, and the final result is activeSection
:{hash: "build-tic-tac-toe-game"}
In other words, the way we wrote our onScroll
function will always highlight the last section in a chapter. Obviously, we did not finish writing the onScroll
function. In the next step, let's discuss how to highlight the proper section.
We need to find the position of the
element and compare it to the top and bottom of the window. To get the relative position of an element, we can use the getBoundingClientRect() method. Modify the onScroll
function like this:
onScroll = throttle(() => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
const b = s.getBoundingClientRect();
const anchorTop = b.top;
const anchorBottom = b.bottom;
console.log(i, anchorTop, anchorBottom, window.innerHeight);
}
this.setState({ activeSection });
}, 500);
This new code is self-explanatory, except perhaps window.innerHeight, which outputs the height of the current window. The relative positions of an element from the top and bottom of the window are anchorTop
and anchorBottom
, respectively.
Before we test the onScroll
function above, comment out the following line of code inside the renderSections()
function:
// console.log(activeSection);
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
. Open the browser console (Developer tools
> Console
) and then open the TOC.
Scroll a bit and look at the console:
You see that the browser console outputs the relative positions of the
elements What is Lorem Ipsum?
(i=0
) and Build Tic Tac Toe Game
(i=1
). The first number is the index of the element, second number is the distance to the top of the window (px), third number is the distance to the bottom of the window (px), and last number is the height of the window (px):
0 122.625 152.625 461
1 1011.03125 1041.03125 461
0 100.625 130.625 461
1 989.03125 1019.03125 461
Scroll down and try to note trends in the values for the relative positions. For the sake of discussion, let's only focus on one element, Build Tic Tac Toe Game
(i=1
). For now, modify your onScroll
function to include a line if (i === )
as shown below:
onScroll = throttle(() => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
const b = s.getBoundingClientRect();
const anchorTop = b.top;
const anchorBottom = b.bottom;
if (i === 1) {
console.log(i, anchorTop, anchorBottom, window.innerHeight);
}
}
this.setState({ activeSection });
}, 500);
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
. Open the browser console (Developer tools
> Console
) and then open the TOC.
Scroll a bit and look at the console:
You see that the anchorTop
and anchorBottom
distances of the i=1
element go down as you scroll. As expected, no changes occur in the value of window.innerHeight
(we did not change the height of the window).
As you keep scrolling, you will notice that you begin to see Build Tic Tac Toe Game
when anchorBottom
equals window.innerHeight
. As you keep scrolling, anchorBottom
becomes 0 when the
element disappears for the first time.
This makes perfect sense, because anchorBottom
( or getBoundingClientRect().bottom) is the distance between the bottom border of an element and upper border of the window. The length of the red line in this snapshot is anchorBottom
:
By combining these two conditions we just explained, we conclude that the section element is in view when:
anchorBottom >= 0 && anchorBottom <= window.innerHeight
When a section element is in view, let's simply pass the value of the name
attribute of
to activeSection.hash
:
if (anchorBottom >= 0 && anchorBottom <= window.innerHeight) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
break;
}
When a section element is not in view and is below the lower border of the window, then:
anchorBottom > window.innerHeight
To visualize this, you can see how the length of the red line (anchorBottom
) is longer than the height of window (window.innerHeight
) when our Build Tic Tac Toe Game
element is below the lower border of the window:
When a section element is not in view and below the lower border of the window, we want to highlight the previous section, sectionElms[i - 1]
, instead of sectionElms[i]
:
if (anchorBottom > window.innerHeight && i > 0) {
if (sectionAbove.bottom <= 0) {
activeSection = {
hash: sectionElms[i - 1].attributes.getNamedItem('name').value,
};
break;
}
} else if (i + 1 === sectionElms.length) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
sectionAbove = b;
In other words, when Build Tic Tac Toe Game
is below the lower border of the window, we want the TOC to highlight the previous section ([i-1]
), which is What is Lorem Ipsum?
. Once hash
gets a value, we break the for loop
.
When a section element is below the lower border of the window (anchorBottom > window.innerHeight
) and there is a section element above the upper border of the window(sectionAbove.bottom <= 0
), then we want to highlight the [i-1]
section. We use the i > 0
condition, since the very first section element has no section above it.
Another special case is the very last section element (i + 1 === sectionElms.length
). The very last section element has no section below it, so the code with condition anchorBottom > window.innerHeight
won't work. Thus we added:
else if (i + 1 === sectionElms.length) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
After following steps 1-5, you get the final version of the onScroll
function:
onScroll = throttle(() => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
let sectionAbove;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
const b = s.getBoundingClientRect();
const anchorBottom = b.bottom;
if (anchorBottom >= 0 && anchorBottom <= window.innerHeight) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
break;
}
if (anchorBottom > window.innerHeight && i > 0) {
if (sectionAbove.bottom <= 0) {
activeSection = {
hash: sectionElms[i - 1].attributes.getNamedItem('name').value,
};
break;
}
} else if (i + 1 === sectionElms.length) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
sectionAbove = b;
}
if (!isEqual(this.state.activeSection, activeSection)) {
this.setState({ activeSection });
}
}, 500);
You might have noticed that we made one more improvement with:
if (!isEqual(this.state.activeSection, activeSection)) {
this.setState({ activeSection });
}
With this code, we tell our app that when the current activeSection
object is the same as object inside the state
(i.e. did not change), then the app should not update state
and re-render the page. Our app should only do so when the activeSection
object has actually changed. Unlike hideHeader
and isMobile
boolean parameters, we can't use !==
for comparison of two activeSection
objects. To compare two objects we used isEqual
method from lodash
. Remember to import isEqual
from lodash
:
import isEqual from 'lodash/isEqual';
Time to test. Uncomment this line in renderSections()
function of ReadChapter
page:
console.log(activeSection);
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
. Open the browser console (Developer tools
> Console
) and open the TOC.
Scroll and look at the console:
As you scroll, the console prints the activeSection
object:
{hash: "what-is-lorem-ipsum-"}
{hash: "what-is-lorem-ipsum-"}
{hash: "what-is-lorem-ipsum-"}
When you have Build Tic Tac Toe Game
in view, the console prints out the new activeSection
object:
{hash: "what-is-lorem-ipsum-"}
{hash: "what-is-lorem-ipsum-"}
{hash: "what-is-lorem-ipsum-"}
{hash: "build-tic-tac-toe-game"}
{hash: "build-tic-tac-toe-game"}
{hash: "build-tic-tac-toe-game"}
Good job if you observed this behaviour!
In the last two sections of the chapter 7, we are going to improve our TOC further. We will:
Header
once a user scrolls past a certain distance In the previous section, we did plenty of scrolling. You may have noticed that the Header
component is always present. However, the user only needs to see the Header
component on rare occasions - when navigating to the dashboard or logging out. Thus, our app should hide the Header
component when you a user scrolls past a certain distance. This will allow the user have an unobstructed view of the main content while reading. To try out a similar UX, go to any Medium article, scroll down and observe the Header behavior.
You just implemented dynamic highlights for sections - you know how to detect scrolling with event listener. We may choose to write again:
document.getElementById('main-content').addEventListener('scroll', this.onScrollHideHeader);
However, having two event listeners and two throttle methods is bad design. Similar to how we strive to reuse components, we should strive to reuse functions. We should use a single event listener that executes one function with throttle. This one function should call two functions: onScrollActiveSection
and onScrollHideHeader
.
Open pages/public/read-chapter.js
, find the componentDidMount()
lifecycle hook:
componentDidMount() {
document.getElementById('main-content').addEventListener('scroll', this.onScroll);
}
Let's make re-define onScroll
function, make it call two function and add throttle to it:
onScroll = throttle(() => {
this.onScrollActiveSection();
this.onScrollHideHeader();
}, 500);
Add above function right after the componentWillUnmount()
lifecycle hook.
Update the old onScroll
function that highlights sections - rename it to onScrollActiveSection
and remove throttle:
onScrollActiveSection = () => {
const sectionElms = document.querySelectorAll('span.section-anchor');
let activeSection;
let aboveSection;
for (let i = 0; i < sectionElms.length; i += 1) {
const s = sectionElms[i];
const b = s.getBoundingClientRect();
const anchorBottom = b.bottom;
if (anchorBottom >= 0 && anchorBottom <= window.innerHeight) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
break;
}
if (anchorBottom > window.innerHeight && i > 0) {
if (aboveSection.bottom <= 0) {
activeSection = {
hash: sectionElms[i - 1].attributes.getNamedItem('name').value,
};
break;
}
} else if (i + 1 === sectionElms.length) {
activeSection = {
hash: s.attributes.getNamedItem('name').value,
};
}
aboveSection = b;
}
if (!isEqual(this.state.activeSection, activeSection)) {
this.setState({ activeSection });
}
};
Next, let's define onScrollHideHeader
function and finish other code related to highlighting sections:
hideHeader
parameter inside onScrollHideHeader
functionhideHeader
as a prop to the Header
componentHeader
component with a conditional style that hides the Header
component when a user scrollsReadChapter
page.Let's discuss each step in detail.
Inside onScrollHideHeader
, we need to select the

element with id="main-content"
, then get the distance from this element's top to the topmost visible content, distanceFromTop
. The JavaScript method scrollTop]) does exactly that:
const distanceFromTop = document.getElementById('main-content').scrollTop;
We define a boolean parameter hideHeader
by comparing distanceFromTop
to 500 (Header
gets hidden after ua ser scrolls for 500px or more):
const hideHeader = distanceFromTop > 500;
Finally, we need to set state
. However, we want to set state
only when the hideHeader
value changes (we did the same for activeSection
in the previous section):
if (this.state.hideHeader !== hideHeader) {
this.setState({ hideHeader });
}
Define onScrollHideHeader
based on the above considerations:
onScrollHideHeader = () => {
const distanceFromTop = document.getElementById('main-content').scrollTop;
const hideHeader = distanceFromTop > 500;
if (this.state.hideHeader !== hideHeader) {
this.setState({ hideHeader });
}
};
To set initial state
, remember to add hideHeader: false
to constructor
(we discussed usage of constructor
in Chapter 2 and Chapter 5) in pages/public/read-chapter.js
:
this.state = {
showTOC: false,
chapter,
htmlContent,
hideHeader: false,
};
Passing the hideHeader
prop to the Header
component is tricky, since our ReadChapter
page does not have a Header
component. In fact, our App
HOC (pages/_app.js
) adds Header
to the page it wraps (Next.js automatically handles wrapping of pages with App
HOC).
Open pages/_app.js
and find
line. If we can hide this Header
element on ReadChapter
page only (and not on all other pages) then we can import Header
component directly to ReadChapter
page only and pass hideHeader
prop to it.
One way to solve this problem is to notice that only pageProps
for ReadChapter
page has pageProps.chapter
. No other page in our web application has a chapter
prop. We can use this fact to conditionally hide Header
inside App
HOC for ReadChapter
page only.
Open pages/_app.js
. Replace line:
With:{pageProps.chapter ? null : }
Start your app with yarn dev
and go to http://localhost:8000/books/demo-book/introduction
:
If you see no Header
- great! That was our goal. To pass the hideHeader
prop to the Header
component, we need to add the Header
component directly to our ReadChapter
page. This requires not adding the Header
component to the page via App
HOC.
Import Header
to pages/public/read-chapter.js
with:
import Header from '../../components/Header';
In pages/public/read-chapter.js
, right above the line with {this.renderSidebar()}
, add
line.
After these changes, render()
should look like:
render() {
const { user } = this.props;
const {
chapter, showTOC, hideHeader,
} = this.state;
if (!chapter) {
return <Error statusCode={404} />;
}
return (

<title>
{chapter.title === 'Introduction'
? 'Introduction'
: `Chapter ${chapter.order - 1}. ${chapter.title}`}
</title>
{chapter.seoDescription ? (
<meta name="description" content={chapter.seoDescription} />
) : null}

{this.renderSidebar()}

{this.renderMainContent()}

format_list_bulleted

);
}
Notice that we passed the user
prop and got hideHeader
from state
with:
const { user } = this.props;
const {
chapter, showTOC, hideHeader,
} = this.state;
We are getting close. We passed the hideHeader
prop to the Header
component, but we did not actually use this prop
to hide the Header
yet. Open components/Header.js
, update this line of code:function Header({ user })
With:function Header({ user, hideHeader })
Then add hideHeader
to propTypes and defaultProps:
Header.propTypes = {
user: PropTypes.shape({
avatarUrl: PropTypes.string,
displayName: PropTypes.string,
}),
hideHeader: PropTypes.bool,
};
Header.defaultProps = {
user: null,
hideHeader: false,
};
To the very first

element of the Header
component, which has no styles, add the following styles:

<Toolbar style={styleToolbar}>
// some code
</Toolbar>

Save. We are ready to test!
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
.
Scroll down more than 500px:
It works! Header
indeed slides up and disappears. However, it leaves an empty space after disappearing.
After the Header
disappears, it leaves an empty space, which does not look good. The best UX would be to slide up the main content, TOC, and icons as the Header
slides up to disappear. To do so, we need to add conditional styles to three

elements inside the ReadChapter
page.

element inside the renderSidebar()
function:

// some code

Update the top
CSS property with the ternary operator hideHeader ? 0 : '64px'
. Also, add a transition
property:

// some code

Remember to get hideHeader
from state
. Inside the renderSidebar()
function, make sure that you have:
const {
showTOC, chapter, hideHeader,
} = this.state;
The remaining two

elements are inside the render()
function. Right under line {this.renderSidebar()}
, find the following code snippet:

{this.renderMainContent()}

format_list_bulleted

Update the top
CSS property with the ternary operator hideHeader ? 0 : '64px'
. Also, add a transition
property:

{this.renderMainContent()}

format_list_bulleted

Let's test again.
Start your app with yarn dev
and navigate to http://localhost:8000/books/demo-book/introduction
.
Scroll down more than 500px:
As expected, three

elements slide up at the same time as the Header
. Thus, we solved the UX problem of an empty space that remains after Header
disappears.
In the next and final section of this chapter, we will add conditional styles to the ReadChapter
page to make it look good on both desktop and mobile browsers.
ReadChapter
page looks great on desktop but not on the mobile browser. These are the problems:
padding
- say {{ padding: '20px 20%' }}
- to our

that contains chapter content (htmlContent
), then the content will look good on the desktop browser but be unreadable on the mobile browser{{ width: '100%' }}
, but that would be a disaster on desktopLast two UX issues are problems on both mobile and desktop browsers:
In this chapter, we wrote a bunch of conditional styles. As a result, you probably guessed that to solve the above issues, we are going to introduce a boolean parameter: isMobile
. And we're going to write several conditional styles (with the ternary operator) with this parameter.
To define isMobile
, we should compare window.innerWidth
to 768px, a typical breakpoint for mobile view. Where should we define isMobile
? Actually, we have no choice but to define it in the componentDidMount()
lifecycle hook.
When an initial request comes from browser to server, Next.js renders the page on the server. At this point, the window
object is not available on the server. On the browser, after ReadChapter
component mounts, the window
object becomes available. You can try using window.innerWidth
in the server code or inside the componentWillMount()
lifecycle hook - but you will get an error:
ReferenceError: window is not defined
Defining isMobile
is straightforward after you defined hideHeader
earlier:
const hideHeader = distanceFromTop > 500
isMobile
is true when:
const isMobile = window.innerWidth < 768
We set state
in the same we did for activeSection
and hideHeader
:
if (this.state.isMobile !== isMobile) {
this.setState({ isMobile }); // eslint-disable-line
}
Update the componentDidMount()
lifecycle hook inside the ReadChapter
page:
componentDidMount() {
document.getElementById('main-content').addEventListener('scroll', this.onScroll);
const isMobile = window.innerWidth < 768;
if (this.state.isMobile !== isMobile) {
this.setState({ isMobile }); // eslint-disable-line
}
}
Remember to initiate state with isMobile
and add isMobile
to constructor
. Find and update the following code snippet:
this.state = {
showTOC: false,
chapter,
htmlContent,
hideHeader: false,
isMobile: false,
};
Now we can use isMobile
to write conditional styles (if browser is mobile, app will apply mobile-specific styles). We will discuss and solve 5 problems stated earlier in this section.
Find function renderMainContent()
in pages/public/read-chapter.js
. We will add a conditional style to the padding
property of the very first

element. Remember to define isMobile
inside the renderMainContent()
function:
const {
chapter, htmlContent, showTOC, isMobile,
} = this.state;
Padding will depend on both isMobile
(mobile/desktop view) and showTOC
(when TOC is closed, give larger padding):
let padding = '20px 20%';
if (!isMobile && showTOC) {
padding = '20px 10%';
} else if (isMobile) {
padding = '0px 10px';
}
At this point, we defined padding
and can use as inline style with simple style={{ padding }}
:

{chapter.order > 1 ? `Chapter ${chapter.order - 1}: ` : null}
{chapter.title}

Notice that we added id="chapter-content"
. We will use this to identify an element when solving a problem in step 5.
Find the renderSidebar()
function. In the very first

element, add the ternary operator isMobile ? '100%' : '400px'
to the width
property like this:

// some code

As always, remember to define variables inside the function:
const {
showTOC, chapter, hideHeader, isMobile,
} = this.state;
To solve this issue, we need to write a function closeTocWhenMobile
:
closeTocWhenMobile = () => {
this.setState({ showTOC: !this.state.isMobile });
};
Add this function right above the renderMainContent()
function.
We defined the function, and now we need to make sure that the function gets executed when a user clicks on hyperlinks inside the TOC. We need to add an onClick
DOM event handler that executes closeTocWhenMobile
:
Inside the renderSections()
function, update
as follows:
<a
style={{
color: activeSection && activeSection.hash === s.escapedText ? '#1565C0' : '#222',
}}
href={`#${s.escapedText}`}
onClick={this.closeTocWhenMobile}
>
{s.text}

Inside the renderSidebar()
function, update
as follows:

{ch.title}

To solve the empty space problem to the left of the

with id="main-content"
, we need to add a conditional style to the CSS property left
. Inside render()
at pages/public/read-chapter.js
, remember to define isMobile
:
const {
chapter, showTOC, hideHeader, isMobile,
} = this.state;
Define left
by using both isMobile
and showTOC
:
let left = '20px';
if (showTOC) {
left = isMobile ? '100%' : '400px';
}
Add this snippet right above return ()
inside render()
.
Now that you've defined left
, add it as an inline style with the simple style={{ left }}
:

// some code

Done.
When a user clicks a hyperlinked chapter title inside the TOC, this new chapter loads at the same scroll depth as the previous chapter.
To scroll an element into view, we use the JavaScript method scrollIntoView() on the

element with id="chapter-content
(we added this id in step 1):
document.getElementById('chapter-content').scrollIntoView();
Add this single line of code above to the componentWillReceiveProps(nextProps)
lifecycle hook that we discussed in Chapter 5 as follows:
componentWillReceiveProps(nextProps) {
const { chapter } = nextProps;
if (chapter && chapter._id !== this.props.chapter._id) {
document.getElementById('chapter-content').scrollIntoView();
const { htmlContent } = chapter;
this.setState({ chapter, htmlContent });
}
}
We are done with conditional styles for mobile browser.
Start your app with yarn dev
and inspect the conditional styles with Developer tools > Elements
.
To toggle between devices, go to Developer tools
and click the Toggle device
icon that is located to the left of the Elements
tab. You should see how our styles change between mobile devices and the desktop.
It's important to mention user experience on a mobile browser. Since we placed const isMobile = window.innerWidth < 768
inside page's componentDidMount()
- user will see a flash of style. In other words, on the first page load on a mobile browser, the end user will see desktop browser styles. A moments later, after componentDidMount()
code block is executed and page component rerenders, the end user will see mobile browser styles. Thus the end user will see a brief flash of style.
To avoid the flash of style, our app's server has to know whether the user uses a mobile browser on the very first HTTP request. If the server knows that the browser is mobile, then the server can apply mobile browser styles to the page and render it. In other words, a page will be server-side rendered with styles for a mobile browser. This can be achieved if the server reads so-called User-Agent that contains detailed information about user's browser. We implemented such logic in our open source SaaS boilerplate, check out isMobile.ts file.
In the next chapter (Chapter 8), we will add all code necessary to sell a book.
At the end of Chapter 7, your codebase should look like the codebase in 7-end
. The 7-end folder is located at the root of the book
directory inside the builderbook repo.
Compare your codebase and make edits if needed.
One chapter left! If you're enjoying the book, please share a quick review. You can update your review at any time.


Welcome to our exchange students!
by Agnes
on 2016-09-13
by Agnes
on 2016-09-13
As old and new Philosophoclers alike settle on campus, I would like to take the opportunity to welcome our new exchange students!
The whole campus is excited. The past few days, we have been poring over the meager biographies they have proffered us; Sygoh, who studies Psychology, has been inferring their personality types; and the Housers have been staying up in the evenings trying to fit them into the dorms.
We first thought to put all the exchange students in Modern Grey—the new, air-conditioned dorm—but the Uniformists protested, saying that it made the new students Stand Out, and besides, don’t Philosophocle students deserve the best of our Housing? Furthermore, putting all the exchange students together would reduce opportunities for cultural assimilation and increase the risk of tribalism. Is not an exchange program the best time for personal growth?
Thus the Housers have unanimously agreed to put Brian, who wants the “authentic Philosophocler experience” and is “up for anything,” in the top1 floor of Riversky, the old, run-down dorm farthest from central campus. The Housers traditionally give higher priority for Riversky to those who get a grade of M- in one of their classes (“Needing a moral education”).
Each exchange student has been assigned a Shadow. (We first thought to call them Friends or Buddies, but Lukko pointed out: what if it is not a friend that the students need, but a rival or a critic? Moreover, an assigned friend is unnecessary when there is a healthy economy for friends—be sure to nab your “free friend” card at the Ideologies Fair.) Onto a Shadow, however, one can project both one’s innermost desires and deepest fears.
A reminder to our Shadows: Please resist the natural urge to force your religion onto the exchange student, and give them freedom of religious choice. It is better for them to choose their own religion and be your enemy, then for you to choose it for them and become their friend! Do not forget that the lack of a decision is itself a choice.
We have decided to respect his desire to be “up”.↩


This element has already been linked.
This error occurs when a single element is linked more then once.
For example, if an element is compiled and linked twice without cloning:
var linker = $compile(template);
linker($scope); //=> ok
linker($scope); //=> multilink error
Linking an element as a clone multiple times is ok:
var linker = $compile(template);
linker($scope, function() { ... }); //=> ok
linker($scope, function() { ... }); //=> ok
However once an element has been linked it can not be re-linked as a clone:
var linker = $compile(template);
linker($scope); //=> ok
linker($scope, function() { ... }); //=> multilink error