├── .gitignore ├── elm-package.json ├── README.md ├── LICENSE ├── index.html ├── elm-custom-element.js ├── src └── Main.elm └── CODE_OF_CONDUCT.md /.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff/ 2 | /build/ 3 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "./src" 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 13 | }, 14 | "elm-version": "0.18.0 <= v < 0.19.0" 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elm Custom Element Demo 2 | 3 | http://lukewestby.com/elm-custom-element-demo 4 | 5 | Demonstrates an Elm application running inside of a W3C Custom Element. For 6 | best results, use with a browser known to support the Custom Elements API. I 7 | tested this in Chrome 49. 8 | 9 | ## How it works 10 | 11 | The Elm application defines a `main` exposing a `Signal Html`, like a typical 12 | start-app application. It also exposes a port named `attributes` which allows in 13 | a `Json.Decode.Value` representing the current value of the custom element's 14 | attributes. Lastly, it exposes a port named `events` which maps values onto 15 | event names. Every time a new value is detected on one of the record fields in 16 | `events`, a custom DOM event is triggered with the name of the field and the 17 | associated value is placed on `event.detail`. 18 | 19 | Check out `src/Main.elm` and `index.html` to see related app code, and see 20 | `elm-custom-element.js` for the wire-up Custom Element code that comprises 21 | `ElmCustomElement.register()`. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Luke Westby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Elm Custom Element Demo 6 | 7 | 8 | Fork me on GitHub 9 |

Open Developer Tools!

10 |
Inspect this element! ↓
11 | 12 | 13 | 14 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /elm-custom-element.js: -------------------------------------------------------------------------------- 1 | window.ElmCustomElement = (function () { 2 | 3 | function attributesToObject(attributes) { 4 | return Array.prototype.slice.call(attributes) 5 | .reduce(function (current, next) { 6 | current[next.name] = next.value; 7 | return current; 8 | }, {}); 9 | } 10 | 11 | function register(tagName, elmModule) { 12 | var ElementProto = Object.create(HTMLElement.prototype); 13 | 14 | ElementProto.createdCallback = function createdCallback() { 15 | var self = this; 16 | self._attributesJson = attributesToObject(this.attributes); 17 | self._wrapper = document.createElement('div'); 18 | self._elmApp = elmModule.embed(this._wrapper, { attributes: this._attributesJson }); 19 | self._shadow = this.createShadowRoot(); 20 | self._shadow.appendChild(this._wrapper); 21 | self._previousEventValues = {}; 22 | self._elmApp.ports.events.subscribe(function (nextValues) { 23 | Object.keys(nextValues).forEach(function (key) { 24 | var nextValue = nextValues[key]; 25 | if (self._previousEventValues[key] === nextValue) return; 26 | self._previousEventValues[key] = nextValue; 27 | var event = new CustomEvent(key, { detail: nextValue }); 28 | self.dispatchEvent(event); 29 | }); 30 | }); 31 | }; 32 | 33 | ElementProto.attachedCallback = function attachedCallback() { 34 | var self = this; 35 | self._attributesJson = attributesToObject(self.attributes); 36 | self._elmApp.ports.attributes.send(self._attributesJson); 37 | } 38 | 39 | ElementProto.attributeChangedCallback = function attributeChangedCallback() { 40 | var self = this; 41 | self._attributesJson = attributesToObject(self.attributes); 42 | self._elmApp.ports.attributes.send(self._attributesJson); 43 | }; 44 | 45 | document.registerElement(tagName, { 46 | prototype: ElementProto, 47 | }); 48 | } 49 | 50 | return { 51 | register, 52 | }; 53 | }()); 54 | -------------------------------------------------------------------------------- /src/Main.elm: -------------------------------------------------------------------------------- 1 | port module Main exposing (..) 2 | 3 | import Html as H exposing (Html) 4 | import Html.Attributes as HA 5 | import Json.Decode as Json exposing (Decoder) 6 | import Process 7 | import Task 8 | 9 | 10 | main : Program Json.Value Model Msg 11 | main = 12 | H.programWithFlags 13 | { init = initialState 14 | , update = update 15 | , view = view 16 | , subscriptions = \_ -> Sub.batch [ attributes AttributesChange ] 17 | } 18 | 19 | 20 | type alias Model = 21 | { count : Int 22 | , color : String 23 | } 24 | 25 | 26 | initialState : Json.Value -> ( Model, Cmd Msg ) 27 | initialState attrs = 28 | ( { count = 0 29 | , color = 30 | attrs 31 | |> Json.decodeValue attributeDecoder 32 | |> Result.withDefault defaultAttributes 33 | |> .color 34 | } 35 | , waitAndUpdate 36 | ) 37 | 38 | 39 | type alias Attributes = 40 | { color : String } 41 | 42 | 43 | defaultAttributes : Attributes 44 | defaultAttributes = 45 | { color = "#0F0" } 46 | 47 | 48 | type Msg 49 | = UpdateCounter 50 | | AttributesChange Attributes 51 | 52 | 53 | update : Msg -> Model -> ( Model, Cmd Msg ) 54 | update msg model = 55 | case msg of 56 | UpdateCounter -> 57 | let 58 | newCount = 59 | model.count + 1 60 | in 61 | { model | count = model.count + 1 } 62 | ! [ waitAndUpdate 63 | , events <| Events <| newCount 64 | ] 65 | 66 | AttributesChange attributes -> 67 | { model | color = attributes.color } ! [] 68 | 69 | 70 | waitAndUpdate : Cmd Msg 71 | waitAndUpdate = 72 | Process.sleep 1000 73 | |> Task.perform (always UpdateCounter) 74 | 75 | 76 | type alias Events = 77 | { change : Int 78 | } 79 | 80 | 81 | port events : Events -> Cmd msg 82 | 83 | 84 | port attributes : (Attributes -> msg) -> Sub msg 85 | 86 | 87 | view : Model -> Html Msg 88 | view model = 89 | H.div [ HA.style [ ( "color", model.color ) ] ] 90 | [ H.text <| toString model ] 91 | 92 | 93 | attributeDecoder : Decoder Attributes 94 | attributeDecoder = 95 | Json.string 96 | |> Json.at [ "color" ] 97 | |> Json.map Attributes 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@lukewestby.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | --------------------------------------------------------------------------------