Last active
July 22, 2020 16:12
-
-
Save OnurGumus/2ee9ee40eb6cb5d5d185ec16ee0becb0 to your computer and use it in GitHub Desktop.
Fable Web components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module App | |
open Fable.Core | |
open Browser | |
open Browser.Types | |
open Fable.Core.JsInterop | |
open Fable.Core.JS | |
open Fable.Core.DynamicExtensions | |
[<AllowNullLiteral>] | |
type HTMLTemplateElement = | |
inherit HTMLElement | |
abstract content: DocumentFragment with get, set | |
[<AllowNullLiteral>] | |
type HTMLTemplateElementType = | |
[<EmitConstructor>] | |
abstract Create: unit -> HTMLTemplateElement | |
[<Global>] | |
type ShadowRoot() = | |
member this.appendChild(el: Browser.Types.Node) = jsNative | |
member this.querySelector(selector: string): Browser.Types.HTMLElement = jsNative | |
let inline attachStatic<'T> (name: string) (f: obj): unit = jsConstructor<'T>?name <- f | |
let inline attachStaticGetter<'T, 'V> (name: string) (f: unit -> 'V): unit = | |
JS.Constructors.Object.defineProperty (jsConstructor<'T>, name, !!{| get = f |}) | |
|> ignore | |
[<Global; AbstractClass>] | |
[<AllowNullLiteral>] | |
type HTMLElement() = | |
member _.getAttribute(attr: string): string = jsNative | |
member _.setAttribute(attr: string, v:obj) = jsNative | |
member _.attachShadow(obj): ShadowRoot = jsNative | |
member _.dispatchEvent(e:CustomEvent): unit = jsNative | |
abstract connectedCallback: unit -> unit | |
default _.connectedCallback () = () | |
abstract attributeChangedCallback: string * obj * obj -> unit | |
default _.attributeChangedCallback (_,_,_) = () | |
module Button = | |
let template: HTMLTemplateElement = | |
downcast document.createElement ("template") | |
template.innerHTML <- | |
""" | |
<style> | |
.container { | |
padding: 8px; | |
} | |
button { | |
display: block; | |
overflow: hidden; | |
position: relative; | |
padding: 0 16px; | |
font-size: 16px; | |
font-weight: bold; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
cursor: pointer; | |
outline: none; | |
width: 100%; | |
height: 40px; | |
box-sizing: border-box; | |
border: 1px solid #a1a1a1; | |
background: #ffffff; | |
box-shadow: 0 2px 4px 0 rgba(0,0,0, 0.05), 0 2px 8px 0 rgba(161,161,161, 0.4); | |
color: #363636; | |
cursor: pointer; | |
} | |
</style> | |
<div class="container"> | |
<button>Label</button> | |
</div> | |
""" | |
[<AllowNullLiteral>] | |
type Button() as this = | |
inherit HTMLElement() | |
let shadowRoot: ShadowRoot = base.attachShadow ({| mode = "open" |}) | |
let button = lazy( shadowRoot.querySelector ("button")) | |
do | |
let clone = template.content.cloneNode (true) | |
shadowRoot.appendChild (clone) | |
button.Force().addEventListener("click", fun _ -> | |
this.dispatchEvent( | |
CustomEvent.Create("onClick", jsOptions<CustomEventInit>(fun o -> o.detail <-"Hello") ))) | |
member this.render() = | |
button.Force().innerHTML <- this.label | |
abstract label : string with get,set | |
default this.label | |
with get() = this.getAttribute("label") | |
and set(value:string) = | |
printf "%A" value | |
this.setAttribute("label", value) | |
override _.connectedCallback() = printf "connected callback" | |
override this.attributeChangedCallback(name, oldVal, newVal) = this.render () | |
attachStaticGetter<Button, _> "observedAttributes" (fun () -> [| "label" |]) | |
window?customElements?define ("my-button", jsConstructor<Button>) | |
module DDL = | |
let template: HTMLTemplateElement = | |
downcast document.createElement ("template") | |
template.innerHTML <- | |
""" | |
<style> | |
:host { | |
font-family: sans-serif; | |
} | |
.dropdown { | |
padding: 3px 8px 8px; | |
} | |
.label { | |
display: block; | |
margin-bottom: 5px; | |
color: #000000; | |
font-size: 16px; | |
font-weight: normal; | |
line-height: 16px; | |
} | |
.dropdown-list-container { | |
position: relative; | |
} | |
.dropdown-list { | |
position: absolute; | |
width: 100%; | |
display: none; | |
max-height: 192px; | |
overflow-y: auto; | |
margin: 4px 0 0; | |
padding: 0; | |
background-color: #ffffff; | |
border: 1px solid #a1a1a1; | |
box-shadow: 0 2px 4px 0 rgba(0,0,0, 0.05), 0 2px 8px 0 rgba(161,161,161, 0.4); | |
list-style: none; | |
} | |
.dropdown-list li { | |
display: flex; | |
align-items: center; | |
margin: 4px 0; | |
padding: 0 7px; | |
font-size: 16px; | |
height: 40px; | |
cursor: pointer; | |
} | |
.dropdown.open .dropdown-list { | |
display: flex; | |
flex-direction: column; | |
} | |
.dropdown-list li.selected { | |
font-weight: 600; | |
} | |
</style> | |
<div class="dropdown"> | |
<span class="label">Label</span> | |
<my-button as-atom>Content</my-button> | |
<div class="dropdown-list-container"> | |
<ul class="dropdown-list"></ul> | |
</div> | |
</div> | |
""" | |
[<AllowNullLiteral>] | |
type Dropdown() as this = | |
inherit HTMLElement() | |
let shadowRoot: ShadowRoot = base.attachShadow ({| mode = "open" |}) | |
let mutable opened = false | |
let button = lazy( shadowRoot.querySelector ("my-button")) | |
let labelv = lazy(shadowRoot.querySelector(".label")) | |
let dropdown = lazy(shadowRoot.querySelector(".dropdown")) | |
let dropdownList = lazy(shadowRoot.querySelector(".dropdown-list")) | |
do | |
let clone = template.content.cloneNode (true) | |
shadowRoot.appendChild (clone) | |
button.Value.addEventListener("onClick", fun e-> this.toggleOpen(e)) | |
member _.toggleOpen(event) = | |
opened <- not opened | |
if opened then dropdown.Value.classList.add "open" | |
else dropdown.Value.classList.remove "open" | |
member this.render() = | |
labelv.Value.innerHTML <- this.label | |
if(this.options <> null) then | |
button.Value.setAttribute("label", this.options.[this.option]?label) | |
dropdownList.Value.innerHTML <- "" | |
printf "%A" this.options | |
JS.Object.keys(if this.options <> null then this.options else createEmpty ) | |
?forEach(fun key -> | |
let option = this.options.[key] | |
let opt = document.createElement("li") | |
opt.innerHTML <- option?label | |
if (this.option <> null && this.option = key) then | |
opt.classList.add "selected" | |
opt.addEventListener("click", fun e -> | |
this.option <- key | |
this.toggleOpen(e) | |
this.dispatchEvent( CustomEvent.Create("onChange", jsOptions<CustomEventInit>(fun o -> o.detail <- key) )) | |
this.render() | |
) | |
dropdownList.Value.appendChild opt | |
|> ignore | |
) | |
abstract label : string with get,set | |
default this.label | |
with get() = this.getAttribute("label") | |
and set(value:string) = this.setAttribute("label", value) | |
abstract option : string with get,set | |
default this.option | |
with get() = this.getAttribute("option") | |
and set(value:string) = this.setAttribute("option", value) | |
abstract options : obj with get,set | |
default this.options | |
with get() = this.getAttribute("options") |> JSON.parse | |
and set(value:obj) = this.setAttribute("options", value |> JSON.stringify) | |
override _.connectedCallback() = printf "connected callback" | |
override this.attributeChangedCallback(name, oldVal, newVal) = this.render () | |
attachStaticGetter<Dropdown, _> "observedAttributes" | |
(fun () -> [| "label"; "option"; "options" |]) | |
window?customElements?define ("my-dropdown", jsConstructor<Dropdown>) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'> | |
<script src="__HOST__/libs/react.production.min.js"></script> | |
<script src="__HOST__/libs/react-dom.production.min.js"></script> | |
<script src="https://unpkg.com/@webcomponents/[email protected]/custom-elements-es5-adapter.js"></script> | |
<script src="https://unpkg.com/@webcomponents/[email protected]/custom-elements.min.js"></script> | |
<script src="https://unpkg.com/@webcomponents/[email protected]/shadydom.min.js"></script> | |
<script src="bundle.js"></script> | |
</head> | |
<body class="app-container"> | |
<my-button></my-button> | |
<script> | |
const element = document.querySelector('my-button'); | |
element.label = 'Click Me'; | |
console.log (element) | |
</script> | |
<my-dropdown | |
label="Dropdown" | |
option="option2" | |
></my-dropdown> | |
<!-- | |
<my-dropdown label="Dropdown" option="option2"></my-dropdown> --> | |
<script> | |
document.querySelector('my-dropdown').options = { | |
option1: { label: 'Option 11' }, | |
option1: { label: 'Option 12' }, | |
option2: { label: 'Option 2' }, | |
}; | |
document | |
.querySelector('my-dropdown') | |
.addEventListener('onChange', event => console.log(event.detail)); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment