Current section
14 Versions
Jump to
Current section
14 Versions
Compare versions
5
files changed
+340
additions
-7
deletions
| @@ -13,7 +13,7 @@ If [available in Hex](https://hex.pm/docs/publish), the package can be installed | |
| 13 13 | 1. Add elmer to your list of dependencies in `mix.exs`: |
| 14 14 | |
| 15 15 | def deps do |
| 16 | - [{:elmer, "~> 0.0.10", only: :dev}] |
| 16 | + [{:elmer, "~> 0.0.12", only: :dev}] |
| 17 17 | end |
| 18 18 | |
| 19 19 | # V1 Roadmap |
| @@ -26,4 +26,5 @@ If [available in Hex](https://hex.pm/docs/publish), the package can be installed | |
| 26 26 | - ~~Cmd generator~~ |
| 27 27 | - ~~Port generator~~ |
| 28 28 | - ~~Resource generator (Msg, View, Model, Update)~~ |
| 29 | + - ~~Hop generator~~ |
| 29 30 | - Phoenix resource generator (Resource + phoenix gen.json for API) |
| @@ -10,12 +10,12 @@ | |
| 10 10 | <<"lib/mix/tasks/gen_port.ex">>,<<"lib/mix/tasks/gen_resource.ex">>, |
| 11 11 | <<"lib/mix/tasks/gen_update.ex">>,<<"lib/mix/tasks/gen_view.ex">>, |
| 12 12 | <<"lib/mix/tasks/new.ex">>,<<"lib/templates.ex">>, |
| 13 | - <<"lib/templates/beginner.ex">>,<<"lib/templates/html.ex">>, |
| 14 | - <<"lib/templates/navigation.ex">>,<<"mix.exs">>,<<"README.md">>, |
| 15 | - <<"LICENSE.md">>]}. |
| 13 | + <<"lib/templates/beginner.ex">>,<<"lib/templates/hop.ex">>, |
| 14 | + <<"lib/templates/html.ex">>,<<"lib/templates/navigation.ex">>,<<"mix.exs">>, |
| 15 | + <<"README.md">>,<<"LICENSE.md">>]}. |
| 16 16 | {<<"licenses">>,[<<"MIT">>]}. |
| 17 17 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/nathanjohnson320/elmer">>}]}. |
| 18 18 | {<<"maintainers">>,[<<"Nathan Johnson">>]}. |
| 19 19 | {<<"name">>,<<"elmer">>}. |
| 20 20 | {<<"requirements">>,[]}. |
| 21 | - {<<"version">>,<<"0.0.11">>}. |
| 21 | + {<<"version">>,<<"0.0.12">>}. |
| @@ -33,6 +33,16 @@ defmodule Mix.Tasks.Elmer.New do | |
| 33 33 | * Update.elm |
| 34 34 | * View.elm |
| 35 35 | * elm-package.json |
| 36 | + * Hop (https://github.com/sporto/hop) which generates: |
| 37 | + * Main.elm |
| 38 | + * Models.elm |
| 39 | + * Msgs.elm |
| 40 | + * Ports.elm |
| 41 | + * RouteMsgs.elm |
| 42 | + * Routing.Elm |
| 43 | + * Update.elm |
| 44 | + * View.elm |
| 45 | + * elm-package.json |
| 36 46 | |
| 37 47 | These types correspond to the main types of elm programs. |
| 38 48 | |
| @@ -48,7 +58,7 @@ defmodule Mix.Tasks.Elmer.New do | |
| 48 58 | case File.mkdir(Path.expand(app_path)) do |
| 49 59 | :ok -> |
| 50 60 | # Get what type of app this is |
| 51 | - app_type = Mix.shell.prompt("What type of application is this?\n(B)eginner\n(H)TML\n(N)avigation\n|>") |
| 61 | + app_type = Mix.shell.prompt("What type of application is this?\n(B)eginner\n(H)TML\n(N)avigation\n(Ho)p\n|>") |
| 52 62 | |
| 53 63 | # Write the default Main files based on the app type |
| 54 64 | create_main(app_path, app_type) |
| @@ -85,6 +95,16 @@ defmodule Mix.Tasks.Elmer.New do | |
| 85 95 | {&Elmer.Templates.Navigation.render_update/0, "Update.elm"}, |
| 86 96 | {&Elmer.Templates.Navigation.render_view/0, "View.elm"}, |
| 87 97 | {&Elmer.Templates.Navigation.render_package_json/0, "elm-package.json"} |
| 98 | + ], |
| 99 | + "ho" => [{&Elmer.Templates.Hop.render_main/0, "Main.elm"}, |
| 100 | + {&Elmer.Templates.Hop.render_models/0, "Models.elm"}, |
| 101 | + {&Elmer.Templates.Hop.render_msgs/0, "Msgs.elm"}, |
| 102 | + {&Elmer.Templates.Hop.render_ports/0, "Ports.elm"}, |
| 103 | + {&Elmer.Templates.Hop.render_routemsgs/0, "RouteMsgs.elm"}, |
| 104 | + {&Elmer.Templates.Hop.render_routing/0, "Routing.elm"}, |
| 105 | + {&Elmer.Templates.Hop.render_update/0, "Update.elm"}, |
| 106 | + {&Elmer.Templates.Hop.render_view/0, "View.elm"}, |
| 107 | + {&Elmer.Templates.Hop.render_package_json/0, "elm-package.json"} |
| 88 108 | ] |
| 89 109 | } |
| @@ -0,0 +1,312 @@ | |
| 1 | + defmodule Elmer.Templates.Hop do |
| 2 | + @moduledoc """ |
| 3 | + Renders an elm-html application with navigation leveraging sporto/Hop. Use this if you want to build a single page application. Documentation here: https://github.com/sporto/hop |
| 4 | + """ |
| 5 | + def render_main do |
| 6 | + """ |
| 7 | + module Main exposing (..) |
| 8 | + |
| 9 | + import Html.App as App |
| 10 | + import Navigation |
| 11 | + import View exposing (view) |
| 12 | + import Models exposing (AppModel, initialModel) |
| 13 | + import Update exposing (update) |
| 14 | + import Navigation |
| 15 | + import Routing exposing (urlParser, urlUpdate) |
| 16 | + import RouteMsgs exposing (..) |
| 17 | + import Msgs exposing (Msg) |
| 18 | + import Ports exposing (subscriptions) |
| 19 | + import Hop.Types |
| 20 | + |
| 21 | + |
| 22 | + -- MAIN |
| 23 | + |
| 24 | + |
| 25 | + init : ( Route, Hop.Types.Location ) -> ( AppModel, Cmd Msg ) |
| 26 | + init ( route, location ) = |
| 27 | + let |
| 28 | + cmds = |
| 29 | + [] |
| 30 | + |
| 31 | + cmd = |
| 32 | + Cmd.batch cmds |
| 33 | + in |
| 34 | + ( Models.initialModel location route, cmd ) |
| 35 | + |
| 36 | + |
| 37 | + main : Program Never |
| 38 | + main = |
| 39 | + Navigation.program urlParser |
| 40 | + { init = init |
| 41 | + , view = view |
| 42 | + , update = update |
| 43 | + , urlUpdate = urlUpdate |
| 44 | + , subscriptions = subscriptions |
| 45 | + } |
| 46 | + """ |
| 47 | + end |
| 48 | + |
| 49 | + def render_package_json do |
| 50 | + """ |
| 51 | + { |
| 52 | + "version": "1.0.0", |
| 53 | + "summary": "helpful summary of your project, less than 80 characters", |
| 54 | + "repository": "https://github.com/user/project.git", |
| 55 | + "license": "BSD3", |
| 56 | + "source-directories": [ |
| 57 | + "." |
| 58 | + ], |
| 59 | + "exposed-modules": [], |
| 60 | + "dependencies": { |
| 61 | + "elm-lang/core": "4.0.0 <= v < 5.0.0", |
| 62 | + "elm-lang/html": "1.0.0 <= v < 2.0.0", |
| 63 | + "elm-lang/navigation": "1.0.0 <= v < 2.0.0", |
| 64 | + "evancz/elm-http": "3.0.1 <= v < 4.0.0", |
| 65 | + "sporto/hop": "5.0.1 <= v < 6.0.0" |
| 66 | + }, |
| 67 | + "elm-version": "0.17.0 <= v < 0.18.0" |
| 68 | + } |
| 69 | + """ |
| 70 | + end |
| 71 | + |
| 72 | + def render_models do |
| 73 | + """ |
| 74 | + module Models exposing (..) |
| 75 | + |
| 76 | + import RouteMsgs exposing (Route) |
| 77 | + import Hop.Types exposing (Location, Router) |
| 78 | + |
| 79 | + |
| 80 | + type alias AppModel = |
| 81 | + { location : Location |
| 82 | + , route : Route |
| 83 | + } |
| 84 | + |
| 85 | + |
| 86 | + initialModel : Location -> Route -> AppModel |
| 87 | + initialModel location route = |
| 88 | + { location = location |
| 89 | + , route = route |
| 90 | + } |
| 91 | + """ |
| 92 | + end |
| 93 | + |
| 94 | + def render_msgs do |
| 95 | + """ |
| 96 | + module Msgs exposing (..) |
| 97 | + |
| 98 | + import Hop.Types exposing (Query) |
| 99 | + |
| 100 | + type Msg |
| 101 | + = NoOp |
| 102 | + | NavigateTo String |
| 103 | + | SetQuery Query |
| 104 | + """ |
| 105 | + end |
| 106 | + |
| 107 | + def render_ports do |
| 108 | + """ |
| 109 | + port module Ports exposing (..) |
| 110 | + |
| 111 | + import Models exposing (AppModel) |
| 112 | + import Msgs exposing (..) |
| 113 | + |
| 114 | + |
| 115 | + subscriptions : AppModel -> Sub Msg |
| 116 | + subscriptions model = |
| 117 | + Sub.batch |
| 118 | + [ Sub.none |
| 119 | + ] |
| 120 | + """ |
| 121 | + end |
| 122 | + |
| 123 | + def render_routemsgs do |
| 124 | + """ |
| 125 | + module RouteMsgs exposing (..) |
| 126 | + |
| 127 | + {-| |
| 128 | + Define your routes as union types |
| 129 | + You need to provide a route for when the current URL doesn't match any known route i.e. NotFoundRoute |
| 130 | + -} |
| 131 | + |
| 132 | + |
| 133 | + type Route |
| 134 | + = AboutRoute |
| 135 | + | MainRoute |
| 136 | + | NotFoundRoute |
| 137 | + """ |
| 138 | + end |
| 139 | + |
| 140 | + def render_routing do |
| 141 | + """ |
| 142 | + module Routing exposing (..) |
| 143 | + |
| 144 | + import Navigation |
| 145 | + import Hop exposing (makeUrl, makeUrlFromLocation, matchUrl, setQuery) |
| 146 | + import Hop.Types exposing (Config, Query, Location, PathMatcher, Router) |
| 147 | + import Hop.Matchers exposing (..) |
| 148 | + import Msgs exposing (..) |
| 149 | + import RouteMsgs exposing (..) |
| 150 | + import Models exposing (AppModel) |
| 151 | + |
| 152 | + |
| 153 | + -- ROUTES |
| 154 | + |
| 155 | + |
| 156 | + {-| |
| 157 | + Define matchers |
| 158 | + For example: |
| 159 | + match1 AboutRoute "/about" |
| 160 | + Will match "/about" and return AboutRoute |
| 161 | + match2 UserRoute "/users/" int |
| 162 | + Will match "/users/1" and return (UserRoute 1) |
| 163 | + `int` is a matcher that matches only integers, for a string use `str` e.g. |
| 164 | + match2 UserRoute "/users/" str |
| 165 | + Would match "/users/abc" |
| 166 | + -} |
| 167 | + matchers : List (PathMatcher Route) |
| 168 | + matchers = |
| 169 | + [ match1 MainRoute "" |
| 170 | + , match1 AboutRoute "/about" |
| 171 | + ] |
| 172 | + |
| 173 | + |
| 174 | + {-| |
| 175 | + Define your router configuration |
| 176 | + Use `hash = True` for hash routing e.g. `#/users/1` |
| 177 | + Use `hash = False` for push state e.g. `/users/1` |
| 178 | + The `basePath` is only used for path routing. |
| 179 | + This is useful if you application is not located at the root of a url e.g. `/app/v1/users/1` where `/app/v1` is the base path. |
| 180 | + - `matchers` is your list of matchers defined above. |
| 181 | + - `notFound` is a route that will be returned when the path doesn't match any known route. |
| 182 | + -} |
| 183 | + routerConfig : Config Route |
| 184 | + routerConfig = |
| 185 | + { hash = True |
| 186 | + , basePath = "" |
| 187 | + , matchers = matchers |
| 188 | + , notFound = NotFoundRoute |
| 189 | + } |
| 190 | + |
| 191 | + |
| 192 | + urlParser : Navigation.Parser ( Route, Hop.Types.Location ) |
| 193 | + urlParser = |
| 194 | + Navigation.makeParser (.href >> matchUrl routerConfig) |
| 195 | + |
| 196 | + |
| 197 | + urlUpdate : ( Route, Hop.Types.Location ) -> AppModel -> ( AppModel, Cmd Msg ) |
| 198 | + urlUpdate ( route, location ) model = |
| 199 | + ( { model | route = route, location = location }, Cmd.none ) |
| 200 | + """ |
| 201 | + end |
| 202 | + |
| 203 | + def render_update do |
| 204 | + """ |
| 205 | + module Update exposing (..) |
| 206 | + |
| 207 | + import Navigation |
| 208 | + import Hop exposing (makeUrl, makeUrlFromLocation, setQuery) |
| 209 | + import Models exposing (..) |
| 210 | + import Msgs exposing (..) |
| 211 | + import Routing exposing (routerConfig) |
| 212 | + |
| 213 | + |
| 214 | + update : Msg -> AppModel -> ( AppModel, Cmd Msg ) |
| 215 | + update msg model = |
| 216 | + case msg of |
| 217 | + NavigateTo path -> |
| 218 | + let |
| 219 | + command = |
| 220 | + makeUrl routerConfig path |
| 221 | + |> Navigation.modifyUrl |
| 222 | + in |
| 223 | + ( model, command ) |
| 224 | + |
| 225 | + SetQuery query -> |
| 226 | + let |
| 227 | + command = |
| 228 | + model.location |
| 229 | + |> setQuery query |
| 230 | + |> makeUrlFromLocation routerConfig |
| 231 | + |> Navigation.modifyUrl |
| 232 | + in |
| 233 | + ( model, command ) |
| 234 | + |
| 235 | + NoOp -> |
| 236 | + ( model, Cmd.none ) |
| 237 | + """ |
| 238 | + end |
| 239 | + |
| 240 | + def render_view do |
| 241 | + """ |
| 242 | + module View exposing (..) |
| 243 | + |
| 244 | + import Html exposing (..) |
| 245 | + import Html.App as App |
| 246 | + import Html.Events exposing (onClick) |
| 247 | + import Html.Attributes exposing (..) |
| 248 | + import Dict |
| 249 | + import Msgs exposing (..) |
| 250 | + import RouteMsgs exposing (..) |
| 251 | + import Models exposing (..) |
| 252 | + |
| 253 | + |
| 254 | + view : AppModel -> Html Msg |
| 255 | + view model = |
| 256 | + div [] |
| 257 | + [ menu model |
| 258 | + , pageView model |
| 259 | + ] |
| 260 | + |
| 261 | + |
| 262 | + menu : AppModel -> Html Msg |
| 263 | + menu model = |
| 264 | + div [] |
| 265 | + [ div [] |
| 266 | + [ button |
| 267 | + [ class "btnMain" |
| 268 | + , onClick (NavigateTo "") |
| 269 | + ] |
| 270 | + [ text "Main" ] |
| 271 | + , button |
| 272 | + [ class "btnAbout" |
| 273 | + , onClick (NavigateTo "about") |
| 274 | + ] |
| 275 | + [ text "About" ] |
| 276 | + , button |
| 277 | + [ class "btnQuery" |
| 278 | + , onClick (SetQuery (Dict.singleton "keyword" "elm")) |
| 279 | + ] |
| 280 | + [ text "Set query string" ] |
| 281 | + , currentQuery model |
| 282 | + ] |
| 283 | + ] |
| 284 | + |
| 285 | + |
| 286 | + currentQuery : AppModel -> Html msg |
| 287 | + currentQuery model = |
| 288 | + let |
| 289 | + query = |
| 290 | + toString model.location.query |
| 291 | + in |
| 292 | + span [ class "labelQuery" ] |
| 293 | + [ text query ] |
| 294 | + |
| 295 | + |
| 296 | + {-| |
| 297 | + Views can decide what to show using `model.route`. |
| 298 | + -} |
| 299 | + pageView : AppModel -> Html msg |
| 300 | + pageView model = |
| 301 | + case model.route of |
| 302 | + MainRoute -> |
| 303 | + div [] [ h2 [ class "title" ] [ text "Main" ] ] |
| 304 | + |
| 305 | + AboutRoute -> |
| 306 | + div [] [ h2 [ class "title" ] [ text "About" ] ] |
| 307 | + |
| 308 | + NotFoundRoute -> |
| 309 | + div [] [ h2 [ class "title" ] [ text "Not found" ] ] |
| 310 | + """ |
| 311 | + end |
| 312 | + end |
| @@ -7,7 +7,7 @@ defmodule Elmer.Mixfile do | |
| 7 7 | description: "Helper mix tasks for generating elm files like Main, Ports, Models, Msgs, etc.", |
| 8 8 | source_url: "https://github.com/nathanjohnson320/elmer", |
| 9 9 | package: package, |
| 10 | - version: "0.0.11", |
| 10 | + version: "0.0.12", |
| 11 11 | elixir: "~> 1.2", |
| 12 12 | build_embedded: Mix.env == :prod, |
| 13 13 | start_permanent: Mix.env == :prod, |