Skip to content
Snippets Groups Projects
Commit ee8de523 authored by Will Billingsley's avatar Will Billingsley
Browse files

Added slides on Veautiful for a live session

parent 896363fd
No related branches found
No related tags found
No related merge requests found
Pipeline #358 failed
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="244px" preserveAspectRatio="none" style="width:266px;height:244px;" version="1.1" viewBox="0 0 266 244" width="266px" zoomAndPan="magnify"><defs><filter height="300%" id="finnj3t0mn5kc" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--cluster veautiful--><polygon fill="#FFFFFF" filter="url(#finnj3t0mn5kc)" points="22,24,90,24,97,47.6094,126,47.6094,126,232,22,232,22,24" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="22" x2="97" y1="47.6094" y2="47.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="62" x="26" y="40.5332">veautiful</text><!--cluster dom--><polygon fill="#FFFFFF" filter="url(#finnj3t0mn5kc)" points="150,24,186,24,193,47.6094,244,47.6094,244,124,150,124,150,24" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="150" x2="193" y1="47.6094" y2="47.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="30" x="154" y="40.5332">dom</text><!--class veautiful.VNode--><rect fill="#FEFECE" filter="url(#finnj3t0mn5kc)" height="48" id="veautiful.VNode" style="stroke: #A80036; stroke-width: 1.5;" width="70" x="39" y="60"/><ellipse cx="54" cy="76" fill="#B4A7E5" rx="11" ry="11" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M54.9531,72.6406 L54.9531,79.2969 L56.6719,79.2969 Q57.2813,79.2969 57.5469,79.5313 Q57.8125,79.7656 57.8125,80.1563 Q57.8125,80.5313 57.5469,80.7656 Q57.2813,81 56.6719,81 L51.5313,81 Q50.9219,81 50.6563,80.7656 Q50.3906,80.5313 50.3906,80.1406 Q50.3906,79.7656 50.6563,79.5313 Q50.9219,79.2969 51.5313,79.2969 L53.25,79.2969 L53.25,72.6406 L51.5313,72.6406 Q50.9219,72.6406 50.6563,72.4063 Q50.3906,72.1719 50.3906,71.7813 Q50.3906,71.4063 50.6563,71.1719 Q50.9219,70.9375 51.5313,70.9375 L56.6719,70.9375 Q57.2813,70.9375 57.5469,71.1719 Q57.8125,71.4063 57.8125,71.7813 Q57.8125,72.1719 57.5469,72.4063 Q57.2813,72.6406 56.6719,72.6406 L54.9531,72.6406 Z "/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="38" x="68" y="80.9102">VNode</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="40" x2="108" y1="92" y2="92"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="40" x2="108" y1="100" y2="100"/><!--class veautiful.DNode--><rect fill="#FEFECE" filter="url(#finnj3t0mn5kc)" height="48" id="veautiful.DNode" style="stroke: #A80036; stroke-width: 1.5;" width="71" x="38.5" y="168"/><ellipse cx="53.5" cy="184" fill="#B4A7E5" rx="11" ry="11" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M54.4531,180.6406 L54.4531,187.2969 L56.1719,187.2969 Q56.7813,187.2969 57.0469,187.5313 Q57.3125,187.7656 57.3125,188.1563 Q57.3125,188.5313 57.0469,188.7656 Q56.7813,189 56.1719,189 L51.0313,189 Q50.4219,189 50.1563,188.7656 Q49.8906,188.5313 49.8906,188.1406 Q49.8906,187.7656 50.1563,187.5313 Q50.4219,187.2969 51.0313,187.2969 L52.75,187.2969 L52.75,180.6406 L51.0313,180.6406 Q50.4219,180.6406 50.1563,180.4063 Q49.8906,180.1719 49.8906,179.7813 Q49.8906,179.4063 50.1563,179.1719 Q50.4219,178.9375 51.0313,178.9375 L56.1719,178.9375 Q56.7813,178.9375 57.0469,179.1719 Q57.3125,179.4063 57.3125,179.7813 Q57.3125,180.1719 57.0469,180.4063 Q56.7813,180.6406 56.1719,180.6406 L54.4531,180.6406 Z "/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="39" x="67.5" y="188.9102">DNode</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="39.5" x2="108.5" y1="200" y2="200"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="39.5" x2="108.5" y1="208" y2="208"/><!--class dom.Node--><rect fill="#FEFECE" filter="url(#finnj3t0mn5kc)" height="48" id="dom.Node" style="stroke: #A80036; stroke-width: 1.5;" width="62" x="166" y="60"/><ellipse cx="181" cy="76" fill="#ADD1B2" rx="11" ry="11" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M183.7656,71.875 Q183.9219,71.6563 184.1094,71.5469 Q184.2969,71.4375 184.5156,71.4375 Q184.8906,71.4375 185.125,71.7031 Q185.3594,71.9531 185.3594,72.5625 L185.3594,74.0156 Q185.3594,74.625 185.125,74.8906 Q184.8906,75.1563 184.5156,75.1563 Q184.1719,75.1563 183.9688,74.9531 Q183.7656,74.7656 183.6563,74.25 Q183.6094,73.8906 183.4219,73.7031 Q183.0938,73.3281 182.4844,73.1094 Q181.875,72.8906 181.25,72.8906 Q180.4844,72.8906 179.8438,73.2188 Q179.2188,73.5469 178.7188,74.2969 Q178.2344,75.0469 178.2344,76.0781 L178.2344,77.1719 Q178.2344,78.4063 179.125,79.2344 Q180.0156,80.0469 181.6094,80.0469 Q182.5469,80.0469 183.2031,79.7969 Q183.5938,79.6406 184.0156,79.2031 Q184.2813,78.9375 184.4219,78.8594 Q184.5781,78.7813 184.7813,78.7813 Q185.1094,78.7813 185.3594,79.0469 Q185.625,79.2969 185.625,79.6406 Q185.625,79.9844 185.2813,80.3906 Q184.7813,80.9688 183.9844,81.2969 Q182.9063,81.75 181.6094,81.75 Q180.0938,81.75 178.8906,81.125 Q177.9063,80.625 177.2188,79.5625 Q176.5313,78.4844 176.5313,77.2031 L176.5313,76.0469 Q176.5313,74.7188 177.1406,73.5781 Q177.7656,72.4219 178.8594,71.8125 Q179.9531,71.1875 181.1875,71.1875 Q181.9219,71.1875 182.5625,71.3594 Q183.2188,71.5156 183.7656,71.875 Z "/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="30" x="195" y="80.9102">Node</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="167" x2="227" y1="92" y2="92"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="167" x2="227" y1="100" y2="100"/><!--link veautiful.VNode to veautiful.DNode--><path d="M61.4529,128.024 C60.8955,141.579 61.4864,156.0381 63.2257,167.6784 " fill="none" id="veautiful.VNode-veautiful.DNode" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="none" points="54.4912,127.284,63.2741,108,68.4336,128.552,54.4912,127.284" style="stroke: #A80036; stroke-width: 1.0;"/><!--link veautiful.DNode to veautiful.VNode--><path d="M86.7666,155.7555 C86.7666,155.7555 87.7053,132.126 86.8887,121.338 " fill="none" id="veautiful.DNode-veautiful.VNode" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="85.3568,108,82.4105,117.3978,85.9277,112.9673,90.3582,116.4844,85.3568,108" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="85.9277" x2="86.8403" y1="112.9673" y2="120.9155"/><polygon fill="#FFFFFF" points="85.4081,167.6784,90.0616,162.1698,86.7666,155.7555,82.1131,161.2641,85.4081,167.6784" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="7" x="80.3357" y="157.338">1</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="5" x="82.5156" y="129.338">*</text><!--link veautiful.VNode to dom.Node--><path d="M121.074,84 C121.074,84 142.469,84 152.525,84 " fill="none" id="veautiful.VNode-dom.Node" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="165.745,84,156.745,80,160.745,84,156.745,88,165.745,84" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="160.745" x2="152.745" y1="84" y2="84"/><polygon fill="#FFFFFF" points="109.074,84,115.074,88,121.074,84,115.074,80,109.074,84" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="7" x="117.0299" y="79.8814">1</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="5" x="152.9392" y="79.6858">*</text><!--
@startuml dnode
interface veautiful.VNode
interface veautiful.DNode extends veautiful.VNode
class dom.Node
veautiful.VNode "1" o-> "*" dom.Node
veautiful.DNode "1" o- -> "*" veautiful.VNode
@enduml
PlantUML version 1.2019.06(Sat May 25 03:10:25 AEST 2019)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 11.0.3+7
Operating System: Windows 10
OS Version: 10.0
Default Encoding: Cp1252
Language: en
Country: AU
--></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="233px" preserveAspectRatio="none" style="width:330px;height:233px;" version="1.1" viewBox="0 0 330 233" width="330px" zoomAndPan="magnify"><defs><filter height="300%" id="fu6pum96j95fy" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--cluster veautiful--><polygon fill="#FFFFFF" filter="url(#fu6pum96j95fy)" points="22,24,90,24,97,47.6094,190,47.6094,190,221,22,221,22,24" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="22" x2="97" y1="47.6094" y2="47.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="62" x="26" y="40.5332">veautiful</text><!--cluster dom--><polygon fill="#FFFFFF" filter="url(#fu6pum96j95fy)" points="214,72.5,250,72.5,257,96.1094,308,96.1094,308,172.5,214,172.5,214,72.5" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="214" x2="257" y1="96.1094" y2="96.1094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="30" x="218" y="89.0332">dom</text><!--class veautiful.VNode--><rect fill="#FEFECE" filter="url(#fu6pum96j95fy)" height="144.8516" id="veautiful.VNode" style="stroke: #A80036; stroke-width: 1.5;" width="135" x="38.5" y="60"/><ellipse cx="82.75" cy="76" fill="#B4A7E5" rx="11" ry="11" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M83.7031,72.6406 L83.7031,79.2969 L85.4219,79.2969 Q86.0313,79.2969 86.2969,79.5313 Q86.5625,79.7656 86.5625,80.1563 Q86.5625,80.5313 86.2969,80.7656 Q86.0313,81 85.4219,81 L80.2813,81 Q79.6719,81 79.4063,80.7656 Q79.1406,80.5313 79.1406,80.1406 Q79.1406,79.7656 79.4063,79.5313 Q79.6719,79.2969 80.2813,79.2969 L82,79.2969 L82,72.6406 L80.2813,72.6406 Q79.6719,72.6406 79.4063,72.4063 Q79.1406,72.1719 79.1406,71.7813 Q79.1406,71.4063 79.4063,71.1719 Q79.6719,70.9375 80.2813,70.9375 L85.4219,70.9375 Q86.0313,70.9375 86.2969,71.1719 Q86.5625,71.4063 86.5625,71.7813 Q86.5625,72.1719 86.2969,72.4063 Q86.0313,72.6406 85.4219,72.6406 L83.7031,72.6406 Z "/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacingAndGlyphs" textLength="38" x="103.25" y="80.9102">VNode</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="39.5" x2="172.5" y1="92" y2="92"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="39.5" x2="172.5" y1="100" y2="100"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="103" x="44.5" y="115.4189">public beforeAttach()</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="123" x="44.5" y="129.2549">public attach(): dom.Node</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="94" x="44.5" y="143.0908">public afterAttach()</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="0" x="47.5" y="156.9268"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="105" x="44.5" y="170.7627">public beforeDetach()</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="72" x="44.5" y="184.5986">public detach()</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacingAndGlyphs" textLength="96" x="44.5" y="198.4346">public afterDetach()</text><!--class dom.Node--><rect fill="#FEFECE" filter="url(#fu6pum96j95fy)" height="48" id="dom.Node" style="stroke: #A80036; stroke-width: 1.5;" width="62" x="230" y="108.5"/><ellipse cx="245" cy="124.5" fill="#ADD1B2" rx="11" ry="11" style="stroke: #A80036; stroke-width: 1.0;"/><path d="M247.7656,120.375 Q247.9219,120.1563 248.1094,120.0469 Q248.2969,119.9375 248.5156,119.9375 Q248.8906,119.9375 249.125,120.2031 Q249.3594,120.4531 249.3594,121.0625 L249.3594,122.5156 Q249.3594,123.125 249.125,123.3906 Q248.8906,123.6563 248.5156,123.6563 Q248.1719,123.6563 247.9688,123.4531 Q247.7656,123.2656 247.6563,122.75 Q247.6094,122.3906 247.4219,122.2031 Q247.0938,121.8281 246.4844,121.6094 Q245.875,121.3906 245.25,121.3906 Q244.4844,121.3906 243.8438,121.7188 Q243.2188,122.0469 242.7188,122.7969 Q242.2344,123.5469 242.2344,124.5781 L242.2344,125.6719 Q242.2344,126.9063 243.125,127.7344 Q244.0156,128.5469 245.6094,128.5469 Q246.5469,128.5469 247.2031,128.2969 Q247.5938,128.1406 248.0156,127.7031 Q248.2813,127.4375 248.4219,127.3594 Q248.5781,127.2813 248.7813,127.2813 Q249.1094,127.2813 249.3594,127.5469 Q249.625,127.7969 249.625,128.1406 Q249.625,128.4844 249.2813,128.8906 Q248.7813,129.4688 247.9844,129.7969 Q246.9063,130.25 245.6094,130.25 Q244.0938,130.25 242.8906,129.625 Q241.9063,129.125 241.2188,128.0625 Q240.5313,126.9844 240.5313,125.7031 L240.5313,124.5469 Q240.5313,123.2188 241.1406,122.0781 Q241.7656,120.9219 242.8594,120.3125 Q243.9531,119.6875 245.1875,119.6875 Q245.9219,119.6875 246.5625,119.8594 Q247.2188,120.0156 247.7656,120.375 Z "/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacingAndGlyphs" textLength="30" x="259" y="129.4102">Node</text><line style="stroke: #A80036; stroke-width: 1.5;" x1="231" x2="291" y1="140.5" y2="140.5"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="231" x2="291" y1="148.5" y2="148.5"/><!--link veautiful.VNode to dom.Node--><path d="M185.51,132.5 C185.51,132.5 206.739,132.5 216.745,132.5 " fill="none" id="veautiful.VNode-dom.Node" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="229.9,132.5,220.9,128.5,224.9,132.5,220.9,136.5,229.9,132.5" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="224.9" x2="216.9" y1="132.5" y2="132.4999"/><polygon fill="#FFFFFF" points="173.51,132.5,179.51,136.5,185.51,132.5,179.51,128.5,173.51,132.5" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="7" x="181.7205" y="129.099">1</text><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="5" x="217.13" y="128.1858">*</text><!--
@startuml vnode
interface veautiful.VNode {
public beforeAttach()
public attach(): dom.Node
public afterAttach()
public beforeDetach()
public detach()
public afterDetach()
}
class dom.Node
veautiful.VNode "1" o-> "*" dom.Node
@enduml
PlantUML version 1.2019.06(Sat May 25 03:10:25 AEST 2019)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 11.0.3+7
Operating System: Windows 10
OS Version: 10.0
Default Encoding: Cp1252
Language: en
Country: AU
--></g></svg>
\ No newline at end of file
class: center, middle
# Veautiful
An open source Scala.js SPA framework from first principles
Will Billingsley
---
### Why show us this?
* So you can see how an SPA framework can be *built* not just used
* Conceptually, large frameworks can seem a mystery. This is small enough to be maintained by one person in their spare time!
* Small frameworks have an advantage for spare-time projects: they update *less* often, and being typed the changes from an upgrade are easier to find
---
## Veautiful Nodes
Let's start from first principles.
Suppose all we initially say is
> There is such a thing as a Veautiful Node. When it is "attached" to the DOM, it has a corresponding DOM node which it manages.
![VNode](out/veautiful/vnode.svg)
---
## VNode
VNodes are responsible for managing their own DOM nodes.
```scala
trait VNode {
def domNode:Option[dom.Node]
def beforeDetach():Unit = {}
def detach():Unit
def afterDetach():Unit = {}
def beforeAttach():Unit = {}
def attach():dom.Node
def afterAttach():Unit = {}
def isAttached:Boolean = domNode.nonEmpty
var parent:Option[VNode] = None
}
```
This defines the *attachment lifecycle*.
---
## A simple VNode
```scala
case class Text(text:String) extends VNode {
var domNode:Option[dom.Node] = None
def create() = {
dom.document.createTextNode(text)
}
def attach() = {
val n = create()
domNode = Some(n)
n
}
def detach() = {
domNode = None
}
}
```
---
## VNode
Without much addition, *VNode* comes to represent any leaf node in the *virtual* DOM.
For example:
* Text nodes
* Canvas elements
* Elements that will be managed using other libraries (e.g., d3.js)
As VNodes are completely responsible for managing their nodes (and any sub-nodes), we can let them vary.
---
## Make It So
React.js manages its children by *reconciling* the virtual DOM with the web page's dom, comparing this element-by-element and child-by-child.
But that doesn't tell us what to do with a canvas.
Let's generalise this...
--
```scala
trait MakeItSo {
def makeItSo:PartialFunction[MakeItSo, _]
}
```
We now have a trait that any VNode can implement if you can give it a copy of itself, and it knows how to update itself to match.
---
### Make It So
Unlike JavaScript, Scala has a concept called `case classes`. These provide an easy way do define what makes two examples of a class *equal*.
```scala
case class Text(text:String) extends VNode
```
```scala
Text("The same") == Text("The same") // true
Text("The same") == Text("Not the same") // false
```
Only the state in the constructor is considered for equality. So, whether the VNode is attached is *not* considered part of equality
---
### Canvas and Make It So
We can define a component that can *Make it so* using a Canvas
```scala
case class Spacecraft(var x:Double, var y:Double, var r:Double) extends VNode with MakeItSo {
def create() = dom.document.createElement("canvas")
def redraw() = {
// Code to redraw the spacecraft
}
override def afterAttach() = redraw()
def makeItSo:PartialFunction[MakeItSo, _] = {
case SpaceCraft(xx, yy, dd) =>
x = xx
y = yy
z = zz
redraw()
}
}
```
---
### Daddy Nodes
So far, `VNode` has no children. Let's define a `DNode` that has children.
*Note* - The name `DNode` was an accident. It was called `DNode` because its methods were extracted from `DElement` that connected to a document element. But "daddy node" stuck.
![DNode](/out/veautiful/dnode.svg)
A `DNode`'s chief responsibility is to handle the attachment lifecycle for its children.
---
### DNode
A `DNode`'s chief responsibility is to handle the attachment lifecycle for its children.
```scala
trait DNode extends VNode {
def create():dom.Element
var domNode:Option[dom.Element]
def children:Seq[VNode]
def attach() = {
// ... Call beforeAttach, attach, and afterAttach for all children
}
def detach() = {
// ... Call beforeDetach, detach, and afterDetach for all children
}
}
```
---
### DiffNode
Now let's define React-like nodes. They are `DNode`s that use a reconciliation algorithm so they can *make it so* including their children.
```scala
trait DiffNode extends DNode with MakeItSo {
def makeItSo:PartialFunction[MakeItSo, _] = { case to:DiffNode =>
updateSelf(to)
updateChildren(to.children)
}
def updateChildren(to:Seq[VNode]):Unit = {
// Use a diff algorithm to work out changes to make to children
val diffOps = Differ.seqDiff(children, to)
// implement changes
// Recurse down the DiffNode's (new) children, getting them to "make it so"
updating.zip(to).collect({ case (x:MakeItSo, y:MakeItSo) =>
x.makeItSo(y)
})
}
```
---
### DElement
Finally, we're going to reach the point where can represent a *virtual DOM* node of a single DOM element.
`DElement` is a `DiffNode` that represents one HTML element.
```scala
case class DElement(
name:String,
uniqEl:Any = "",
ns:String = DElement.htmlNS
) extends DiffNode {
// etc
}
```
Something we get for free is the ability to set a unique ID on an element if we want it torn down rather than altered.
Elements will only be preserved if their *name*, *namespace*, and *uniqEl* are the same.
---
### A JSX-like DSL in Scala
Scala is a nice language for writing Domain Specific Languages, because you can create objects whose names are symbols.
```scala
object < {
def apply(n:String, u:String = "", ns:String = DElement.htmlNS) = {
DElement(n, u, ns)
}
def p = apply("div")
def p = apply("p")
}
```
```scala
val para = <.div(
<.p("This is a paragraph"),
<.p("This is another paragraph")
)
```
---
## What this gives us
This gives us
* React-like behaviour, where we have virtual trees of elements that can be reconciled using diff
* But we can insert any other kind of `VNode` wherever we want. E.g., inserting nodes that represent canvases, parts of the page that are managed by d3.js, etc.
---
## Routing in Veautful
As any `DNode` can manage its children however it wishes, a router simply becomes a node whose children change depending on the current state
For convenience, let's first define an `ElementComponent` - a stateful component that uses a `DElement` (a React-like subtree) to update its children.
```scala
class ElementComponent(val el:DElement) extends VNode {
def domNode = el.domNode
def attach() = {
if (isAttached) {
throw new IllegalStateException("Attached twice")
}
el.attach()
}
def detach() = el.detach()
def renderElements(ch:VNode) = el.updateChildren(Seq(ch))
}
```
---
### HistoryRouter
For our router, we're going to need:
* A top-level element to render the view within:
```scala
abstract class HistoryRouter[Route] extends ElementComponent(<.div) {
```
--
* A way of translating a path within the URL to a Route
```scala
def routeFromLocation():Route
```
--
* A way of translating a Route object within the app to a path
```scala
def path(route:Route):String
```
--
* A way of working out what to show in the view, depending on the current state
```scala
def render:VNode
```
---
### HistoryRouter
```scala
abstract class HistoryRouter[Route] extends ElementComponent(<.div) {
var route:Route
def path(route:Route):String
def routeFromLocation():Route
def render:VNode
def registerHistoryListeners():Unit = {
dom.window.onpopstate = {
event =>
route = routeFromLocation()
renderElements(render)
}
}
def routeTo(r:Route):Unit = {
route = r
val p = path(route)
renderElements(render)
}
}
```
---
### Paths and Routes
* *Paths* are strings. E.g.: `"#/class/123"`. We need to turn them into routes
* *Routes* are objects. E.g.: `ClassRoute(123)`. We need to turn them into strings
---
### Converting paths to routes is string matching
Converting paths to routes is just a matter of splitting the string into its components, in such a way that the programming language can match on it
```scala
def pathArray(pathname:String = location.pathname): Array[String] = {
pathname.drop(1).split('/').map(js.URIUtils.decodeURI)
}
```
```scala
pathArray() match {
case Array("class", classId) => ClassRoute(classId.toInt)
}
```
---
### Converting routes to paths is string interpolation
Converting a route object to a path is easier - it's just a `stringify` method. But we can make it easier with a DSL
```scala
route match {
case IntroRoute => (/# / "").stringify
case ToDoRoute => (/# / "todo").stringify
case ReactLikeRoute => (/# / "reactLike").stringify
}
```
---
### Define the Routes
First we define objects to represent our routes
```scala
sealed trait ExampleRoute
case object IntroRoute extends ExampleRoute
case object ToDoRoute extends ExampleRoute
case object ReactLikeRoute extends ExampleRoute
```
---
### Then we declare a Router
And define how to render each route - it's just a reference to another component
```scala
object Router extends HistoryRouter[ExampleRoute] {
var route:ExampleRoute = IntroRoute
def render() = {
route match {
case IntroRoute => Intro.page
case ToDoRoute => ToDoList.page
case ReactLikeRoute => ReactLike.page
}
}
```
---
### Define how to get a path from a route
For the page URL, we want to know what path to show
```scala
override def path(route: ExampleRoute): String = {
import PathDSL._
route match {
case IntroRoute => (/# / "").stringify
case ToDoRoute => (/# / "todo").stringify
case ReactLikeRoute => (/# / "reactLike").stringify
}
}
```
---
### Define how to get a route from a path
To handle forward and back, we then work out how to parse the window location into the routes
```scala
override def routeFromLocation(): ExampleRoute = PathDSL.hashPathArray() match {
case Array("") => IntroRoute
case Array("todo") => ToDoRoute
case Array("reactLike") => ReactLikeRoute
case x =>
println(s"path was ${x}")
IntroRoute
}
}
```
---
class: bottom
Written by Will Billingsley
<small>
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.</small>
\ No newline at end of file
@startuml vnode
interface veautiful.VNode {
public beforeAttach()
public attach(): dom.Node
public afterAttach()
public beforeDetach()
public detach()
public afterDetach()
}
class dom.Node
veautiful.VNode "1" o-> "*" dom.Node
@enduml
@startuml dnode
interface veautiful.VNode
interface veautiful.DNode extends veautiful.VNode
class dom.Node
veautiful.VNode "1" o-> "*" dom.Node
veautiful.DNode "1" o--> "*" veautiful.VNode
@enduml
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment