{"id":20109795,"url":"https://github.com/hyper63/weather","last_synced_at":"2025-07-03T06:06:23.359Z","repository":{"id":110719227,"uuid":"298017515","full_name":"hyper63/weather","owner":"hyper63","description":"svelte template for svelte workshop","archived":false,"fork":false,"pushed_at":"2021-07-07T10:34:57.000Z","size":160,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-02T18:32:20.674Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyper63.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-23T15:42:01.000Z","updated_at":"2021-07-07T10:34:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"e403c952-478f-42a8-aa85-1e5d761b4400","html_url":"https://github.com/hyper63/weather","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hyper63/weather","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyper63%2Fweather","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyper63%2Fweather/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyper63%2Fweather/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyper63%2Fweather/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyper63","download_url":"https://codeload.github.com/hyper63/weather/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyper63%2Fweather/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263271501,"owners_count":23440396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T18:09:27.987Z","updated_at":"2025-07-03T06:06:23.328Z","avatar_url":"https://github.com/hyper63.png","language":"JavaScript","readme":"---\ntitle: Svelte101 Workshop\ncss: https://unpkg.com/mvp.css\ncode: true\n---\n\n\u003cmain\u003e\n\n# Svelte101 Workshop Notes\n\n\u003e ### What will I learn?\n\u003e\n\u003e * Svelte Basics\n\u003e * How to create a Svelte Component\n\u003e * How reactivity works\n\u003e * How to call async functions\n\u003e * How to create lists in Svelte\n\u003e * How to dispatch events from one component to another\n\u003e * How to setup and use single page routing\n\u003e * \n\n## About this workshop\n\nThis workshop is an immersive dive into the Svelte Framework, as a group, we will build a front-end application in Svelte and through the course of building this application, we will learn many of the features and benefits that Svelte has to offer. The notes will reference links to the svelte documentation if you want to dig deeper into any one feature. The purpose of this workshop is to give you a hands on feel of what it is like to work with the framework day in and day out. If you are up to the challenge please code along with the instructor, if the pace is too fast for you, don't worry, after the event the recorded version will be available for your to go at your own pace.\n\n## About me\n\nTom Wilson has been in the software development industry for over 25 years. Continuous learning and teaching have been a part of his journey. Since 2007, Tom has participated in the tech community hosting and running meetups and providing workshops focused on software development. In 2016, Tom launched a coding school in Charleston, SC, JRS Coding School, focused on full-stack javascript. In 2020, Tom founded hyper63; a company focused on leading engineers from beginner to expert.\n\n## About the project\n\nIn this workshop, we are going to build a weather application, this application, will show the current weather of a given location as well as list our favorite locations and and switch the current weather display from one of the favorite locations, then we will allow the user to add a new favorite city, to their favorites.\n\n## Housekeeping\n\nIn order to follow along with this workshop you will need an api key from https://weatherbit.io it is free, it takes a little bit of time to provision, so you may want to do this soon.\n\n## Setup\n\nPull workshop template down from github\nand initialize it and provide link to clone.\n\n```sh\nnpx degit hyper63/weather weather-app\ncd $_\ngit init\ngit add .\ngit commit -am \"first commit\"\ngh create s101-2021-7-7 # install gh: https://cli.github.com/manual/installation\n# add remote repo\nyarn\n```\n\nOpen in vscode\n\n```sh\ncode .\n```\n\n## Tour Repository\n\nCheck out quickstart guide:\n\nhttps://svelte.dev/blog/the-easiest-way-to-get-started\n\nQuick tour of a common svelte project\n\n- public - contains all public assets\n- src - contains your source code\n\n\n```sh\ngit checkout -b 1-template-basics\n```\n\n---\n\n\n## Svelte Basics\n\nhttps://svelte.dev/docs\n\nLets turn this static html into a template:\n\n`src/views/Current.svelte`\n\n```html\n\u003cscript\u003e\n  const weather = {\n    city: \"Charleston, SC\",\n    temp: \"72 \u0026deg; F\",\n    icon: \"a01n\",\n    description: \"clear\",\n  };\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003cfigure\u003e\n    \u003cimg alt=\"{weather.description}\" src=\"/icons/{weather.icon}.png\" /\u003e\n  \u003c/figure\u003e\n  \u003ch3\u003e{weather.city}\u003c/h3\u003e\n  \u003ch1\u003e{@html weather.temp}\u003c/h1\u003e\n  \u003cp\u003e{weather.description}\u003c/p\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nDeclare variables in the `\u003cscript\u003e\u003c/script\u003e` block and reference them in your template using `{ }` curly braces. NOTE: you can place any JS expression in the curly braces and it will be evaluated.\n\n\u003c/aside\u003e\u003c/article\u003e\n\n\u003carticle\u003e\u003caside\u003e\n\nYou can also curly braces within properties, `src=\"/icons/{weather.icon}.png\"`\n\n\u003c/aside\u003e\u003c/article\u003e\n\n\u003carticle\u003e\u003caside\u003e\n\nUse the `{@html ...}` to convert a variable to html. https://svelte.dev/docs#html\n\n\u003c/aside\u003e\u003c/article\u003e\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"1-template-basics\"\ngit push origin 1-template-basics\ngit checkout -b 2-creating-component\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nSvelte is designed to be similar to an old school html page, markup, styles, and scripts in one file. Then `{}` for dynamic\ncontent. Being able to declare a local variable in a script tag\nand reference the value in the markdup is a clean design. It is very intuitive when you look at the component.\n\n---\n\n## Components - Creating a component\n\nLets convert the main section of the Current.svelte component into a reusable weather component:\n\n`src/components/Weather.svelte`\n\n```html\n\u003cscript\u003e\n  // proptypes (we can validate props at runtime using proptypes module)\n  export let icon, description, city, temp;\n\u003c/script\u003e\n\u003cfigure\u003e\n  \u003cimg alt=\"{description}\" src=\"/icons/{icon}.png\" /\u003e\n\u003c/figure\u003e\n\u003ch3\u003e{city}\u003c/h3\u003e\n\u003ch1\u003e{@html temp}\u003c/h1\u003e\n\u003cp\u003e{description}\u003c/p\u003e\n```\n\nNow our `src/views/Current.svelte` component looks like this\n\n```html\n\u003cscript\u003e\n  import Weather from \"../components/Weather.svelte\";\n  const weather = {\n    city: \"Charleston, SC\",\n    temp: \"72 \u0026deg; F\",\n    icon: \"a01n\",\n    description: \"clear\",\n  };\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003cWeather {...weather} /\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\nWe can pass props by using `name={value}` or if the prop is the same name as the variable. `{value}` or we can use the spread operator: `{...value}` which will take an object and spread each key as a property.\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate Checkpoint\n\n```sh\ngit commit -am \"2-creating-component\"\ngit push origin 2-creating-component\ngit checkout -b 3-reactivity\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nSvelte leverages the single file component design, this design keeps your files focused on one specfic unit of work. This allows the scope to be explicit, if I declare a variable in the script tag, it is available to all of the markup. While some may argue that it reduces flexibility, it does lean towards clarity.\n\n---\n\n## Reactivity - Changing display of temp\n\nIn the top left corner of our app, we have a couple of links, on `F` and one `C` we want to capture the `click` event of these links and then based on the current temperature unit, we want to convert to the other temperature unit. We can use a module called `temperature` to help with the conversion. But we need to know the current value of the temperature and the current unit it is in. For example, now we have it listed as Fahrenheit.\n\nIn order to listen for a click event, we can use the `on` directive. The `on` directive takes an event that we want to listen to and we assign it to a function.\n\n```html\n\u003cbutton on:click=\"{handleClick}\"\u003e...\u003c/button\u003e\n\u003cscript\u003e\n  function handleClick() {\n    console.log(\"click\");\n  }\n\u003c/script\u003e\n```\n\nThen in the function we want to change the temp value and unit based on the click function.\n\nThis is where reactivity comes into play. We can listen to the temp and unit variables and create a new derived variable called `displayTemp` this new variable will be the string we want to present.\n\n```html\n\u003cscript\u003e\n  import { fahrenheitToCelsius, celsiusToFahrenheit } from \"temperature\";\n\n  import Weather from \"../components/Weather.svelte\";\n\n  let temp = 21.7;\n  let unit = \"c\";\n\n  $: displayTemp = `${Math.floor(temp)} \u0026deg; ${unit.toUpperCase()}`;\n\n  function convertToF() {\n    temp = celsiusToFahrenheit(temp);\n    unit = \"f\";\n  }\n\n  function convertToC() {\n    temp = fahrenheitToCelsius(temp);\n    unit = \"c\";\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"#\" on:click|preventDefault=\"{convertToF}\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"#\" on:click|preventDefault=\"{convertToC}\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e{@html displayTemp} ...\u003c/main\u003e\n```\n\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"3-reactivity\"\ngit push origin 3-reactivity\ngit checkout -b 4-async\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n## Summary\n\nIn this lesson, we learned about the `on` directive and how we can `|preventDefault` to directives. Also we learned about the `$:` reactivity command and how it can work like a formula in excel. `$:` label is valid javascript and is rarely used, in svelte you can think of it as a way to mark a variable or block of code as reactive, which will create watches on every variable in the calculation logic of the expression or block. And when they change, Svelte will run the block or assign the variable.\n\n---\n\n## Async - Calling an API\n\nNow that we have moved our presentation to a component, lets add an api call to our Current view. We want to call the `/api/weather` endpoint using a `GET` method call. We need to give the request a querystring of `city=${city}\u0026country=${country}`.\n\n\u003carticle\u003e\u003caside\u003e\n\nAt this point, we will need the api key from weatherbit.io. We will want to add it to our `.env` file and restart our server.\n\n\u003c/aside\u003e\u003c/article\u003e\n\n```text\nWEATHER_KEY=(Your key)\n```\n\nLets use the `async` library in our `lib` folder to call the weather api.\n\n`src/views/Current.svelte`\n\n```html\n\u003cscript\u003e\n  import { getJSON } from \"../lib/async.js\";\n\n  getJSON(\"/api/weather?city=charleston,sc\u0026country=us\").then(\n    console.log.bind(console)\n  );\n\u003c/script\u003e\n```\n\nWe should see the weather data come back from the api. We want to use this data to render our weather component.\n\nUsing the `onMount` function from svelte will let us handle async requests and apply the results to local variables.\n\n```html\n\u003cscript\u003e\n  import { getJSON } from \"../lib/async.js\";\n  import Weather from \"../components/Weather.svelte\";\n  import { onMount } from \"svelte\";\n\n  let weather = {\n    city: \"Charleston, SC\",\n    temp: \"72 \u0026deg; F\",\n    icon: \"a01n\",\n    description: \"clear\",\n  };\n\n  onMount(async () =\u003e {\n    const results = await getJSON(\"/api/weather?city=charleston,sc\u0026country=us\");\n    weather = {\n      temp: `${results.temp} \u0026deg; C`,\n      icon: results.weather.icon,\n      description: results.weather.description,\n      city: `${results.city_name} ${results.state_code}`,\n    };\n  });\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003cWeather {...weather} /\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\nOr we could create an async function and use the `{#await}` command.\n\n```html\n\u003cscript\u003e\n  import { getJSON } from \"../lib/async.js\";\n  import Weather from \"../components/Weather.svelte\";\n\n  function getWeather() {\n    return getJSON(\"/api/weather?city=charleston,sc\u0026country=us\").then(\n      (results) =\u003e ({\n        temp: `${results.temp} \u0026deg; C`,\n        icon: results.weather.icon,\n        description: results.weather.description,\n        city: `${results.city_name} ${results.state_code}`,\n      })\n    );\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  {#await getWeather()} Loading... {:then weather}\n  \u003cWeather {...weather} /\u003e\n  {/await}\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"4-async\"\ngit push origin 4-async\ngit checkout -b 5-events\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nIn this lesson, we learned about two ways to handle async requests from components at the point of loading. \n\n---\n\n## {#each} and Dispatching Events\n\nLets start working on the `src/views/Favorites.svelte` view. This view lists the users favorite cities that they would like to get the weather from. Eventually, we will pull this list from the database, but for now, we will create a local variable.\n\n\u003carticle\u003e\u003caside\u003e\n\nChange `App` to render the `Favorites` view\n\n\u003c/aside\u003e\u003c/article\u003e\n\n`src/views/Favorites.svelte`\n\nWe need to handle the onclick event of each item, lets create a Card component then dispatch the click event from the card component.\n\nFrom the click event, we need to route to the current view with the correct city parameters.\n\n```html\n\u003cscript\u003e\n  import CityCard from \"../components/CityCard.svelte\";\n\n  let cities = [\"Charleston, SC\", \"New york, NY\", \"San Francisco, CA\"];\n\n  function changeCurrentCity(city) {\n    return () =\u003e {\n      console.log(\"city\", city);\n    };\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003eFavorites\u003c/div\u003e\n  \u003ca href=\"\"\u003eAdd\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    {#each cities as city}\n    \u003cCityCard {city} on:click=\"{changeCurrentCity(city)}\" /\u003e\n    {/each}\n  \u003c/section\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    height: 24px;\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n\u003c/style\u003e\n```\n\n`src/components/CityCard.svelte`\n\n```html\n\u003cscript\u003e\n  import { createEventDispatcher } from \"svelte\";\n  export let city;\n  const dispatch = createEventDispatcher();\n\n  function handleClick() {\n    dispatch(\"click\", { city });\n  }\n\u003c/script\u003e\n\u003caside on:click=\"{handleClick}\"\u003e\n  \u003ch3\u003e{city}\u003c/h3\u003e\n\u003c/aside\u003e\n\u003cstyle\u003e\n  aside:hover {\n    background-color: whitesmoke;\n  }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"5-events\"\ngit push origin 5-events\ngit checkout -b 6-routing\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nIn this lesson, we created another component to handle each city card and used the `{#each}` template command to iterate over a list of cities and display the city card. Then we added a custom event to the city card which we handled in the Favorites component. Now we need to navigate from the favorites component to the Current component.\n\n---\n\n### Take Break\n\n---\n\n## Routing\n\nFor routing, we are going to use pagejs a framework agnostic routing component modeled after express.\n\n`src/App.svelte`\n\n```html\n\u003cscript\u003e\n  import {Route} from \"tinro\";\n\n  import Current from \"./views/Current.svelte\";\n  import Favorites from \"./views/Favorites.svelte\";\n\n\u003c/script\u003e\n\n\u003cRoute path=\"/\"\u003e\u003cCurrent /\u003e\u003c/Route\u003e\n\u003cRoute path=\"/favorites/*\"\u003e\n  \u003cRoute path=\"/\"\u003e\u003cFavorites /\u003e\u003c/Route\u003e\n\u003c/Route\u003e\n\n```\n\n`src/views/Current.svelte`\n\nAdd link to favorites\n\n`\u003ca href=\"/favorites\"\u003eFavorites\u003c/a\u003e`\n\nUse page to navigate to Current\n\n```html\n\u003cscript\u003e\n  import CityCard from \"../components/CityCard.svelte\";\n  import { router } from 'tinro';\n\n  let cities = [\"Charleston, SC\", \"New york, NY\", \"San Francisco, CA\"];\n\n  function changeCurrentCity(city) {\n    return () =\u003e {\n      // set current city...then\n      router.goto(\"/\");\n    };\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003eFavorites\u003c/div\u003e\n  \u003ca href=\"\"\u003eAdd\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    {#each cities as city}\n    \u003cCityCard {city} on:click=\"{changeCurrentCity(city)}\" /\u003e\n    {/each}\n  \u003c/section\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    height: 24px;\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"6-routing\"\ngit push origin 6-routing\ngit checkout -b 7-stores\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nIn this section, we learned how to use `tinro` router to navigate from one component to another component both declaratively and programatically. There are more features to check out with `tinro` - https://github.com/AlexxNB/tinro \n\n---\n\n## Stores - Sharing data between components\n\nThere are several ways to pass data one way is to use svelte stores.\n\nhttps://svelte.dev/docs#svelte_store\n\n`src/store.js`\n\n```js\nimport { writable } from \"svelte/store\";\n\nexport const store = writable({ current: \"Charleston, SC\" });\n\nexport const dispatch = (action) =\u003e\n  new Promise((resolve) =\u003e\n    store.update((state) =\u003e {\n      resolve();\n      if (action.type === \"SET_CURRENT\") {\n        return { ...state, current: action.payload };\n      }\n      return state;\n    })\n  );\n```\n\n`src/views/Favorites.svelte`\n\n```html\n\u003cscript\u003e\n  import { dispatch } from \"../store\";\n  import CityCard from \"../components/CityCard.svelte\";\n  import page from \"page\";\n\n  let cities = [\"Charleston, SC\", \"New york, NY\", \"San Francisco, CA\"];\n\n  function changeCurrentCity(city) {\n    return () =\u003e {\n      // set current city...then\n      dispatch({ type: \"SET_CURRENT\", payload: city }).then(() =\u003e page(\"/\"));\n    };\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003eFavorites\u003c/div\u003e\n  \u003ca href=\"\"\u003eAdd\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    {#each cities as city}\n    \u003cCityCard {city} on:click=\"{changeCurrentCity(city)}\" /\u003e\n    {/each}\n  \u003c/section\u003e\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    height: 24px;\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n\u003c/style\u003e\n```\n\n`src/views/Current.svelte`\n\n```html\n\u003cscript\u003e\n  import { getJSON } from \"../lib/async.js\";\n  import { fahrenheitToCelsius, celsiusToFahrenheit } from \"temperature\";\n  import { store } from \"../store\";\n\n  import Weather from \"../components/Weather.svelte\";\n\n  let temp = 21.7;\n  let unit = \"c\";\n\n  $: displayTemp = `${Math.floor(temp)} \u0026deg; ${unit.toUpperCase()}`;\n\n  function getWeather() {\n    const current = $store.current.toLowerCase().replace(\", \", \",\");\n    return getJSON(`/api/weather?city=${current}\u0026country=us`).then(\n      (results) =\u003e ({\n        temp: `${results.temp} \u0026deg; C`,\n        icon: results.weather.icon,\n        description: results.weather.description,\n        city: `${results.city_name} ${results.state_code}`,\n      })\n    );\n  }\n\n  function convertToF() {\n    temp = celsiusToFahrenheit(temp);\n    unit = \"f\";\n  }\n\n  function convertToC() {\n    temp = fahrenheitToCelsius(temp);\n    unit = \"c\";\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"#\" on:click=\"{convertToF}\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"#\" on:click=\"{convertToC}\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"/favorites\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  {@html displayTemp} {#await getWeather()} Loading... {:then weather}\n  \u003cWeather {...weather} /\u003e\n  {/await}\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"7-stores\"\ngit push origin 7-stores\ngit checkout -b 8-transitions\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nA Svelte store is a clear tool to manage state in an application, the API of the Svelte store is so expressive, you can implement single state store patterns like redux or streaming patterns like RxJS and any thing in between. Check out the Svelte Store documentation for a deeper dive into Svelte stores.\n\n---\n\n## Transitions\n\nhttps://svelte.dev/docs#svelte_transition\n\n`src/views/Current.svelte`\n\n```html\n\u003cscript\u003e\n  import { getJSON } from \"../lib/async.js\";\n  import { fahrenheitToCelsius, celsiusToFahrenheit } from \"temperature\";\n  import { store } from \"../store\";\n  import { fade, blur } from \"svelte/transition\";\n\n  import Weather from \"../components/Weather.svelte\";\n\n  let temp = 21.7;\n  let unit = \"c\";\n\n  $: displayTemp = `${Math.floor(temp)} \u0026deg; ${unit.toUpperCase()}`;\n\n  function getWeather() {\n    const current = $store.current.toLowerCase().replace(\", \", \",\");\n    return getJSON(`/api/weather?city=${current}\u0026country=us`).then(\n      (results) =\u003e ({\n        temp: `${results.temp} \u0026deg; C`,\n        icon: results.weather.icon,\n        description: results.weather.description,\n        city: `${results.city_name} ${results.state_code}`,\n      })\n    );\n  }\n\n  function convertToF() {\n    temp = celsiusToFahrenheit(temp);\n    unit = \"f\";\n  }\n\n  function convertToC() {\n    temp = fahrenheitToCelsius(temp);\n    unit = \"c\";\n  }\n\u003c/script\u003e\n\u003cnav\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"#\" on:click=\"{convertToF}\"\u003eF\u003c/a\u003e\n    |\n    \u003ca href=\"#\" on:click=\"{convertToC}\"\u003eC\u003c/a\u003e\n  \u003c/div\u003e\n  \u003ca href=\"/favorites\"\u003eFavorites\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain transition:fade=\"{{duration:\" 1000}}\u003e\n  {@html displayTemp} {#await getWeather()} Loading... {:then weather}\n  \u003cWeather {...weather} /\u003e\n  {/await}\n\u003c/main\u003e\n\u003cstyle\u003e\n  nav {\n    margin-bottom: 0;\n    margin-left: 8px;\n    margin-right: 8px;\n  }\n  main {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n  }\n\u003c/style\u003e\n```\n\n`src/views/Favorites.svelte`\n\n```html\n\u003cscript\u003e\n  import { blur } from 'svelte/transition'\n  import { dispatch } from '../store'\n  import CityCard from '../components/CityCard.svelte'\n  import { router } from 'tinro'\n\n  let cities = ['Charleston, SC', 'New york, NY', 'San Francisco, CA']\n\n  function changeCurrentCity(city) {\n    return () =\u003e {\n      // set current city...then\n      dispatch({type: 'SET_CURRENT', payload: city})\n        .then(() =\u003e router.goto('/'))\n    }\n  }\n\n\u003c/script\u003e\n\u003cdiv in:blur={{delay: 1000, duration: 1000}}\u003e\n\u003cnav\u003e\n  \u003cdiv\u003eFavorites\u003c/div\u003e\n  \u003ca href=\"\"\u003eAdd\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    {#each cities as city}\n      \u003cCityCard {city} on:click={changeCurrentCity(city)} /\u003e\n    {/each}\n  \u003c/section\u003e\n\u003c/main\u003e\n\u003c/div\u003e\n\u003cstyle\u003e\n nav {\n   height: 24px;\n   margin-bottom: 0;\n   margin-left: 8px;\n   margin-right: 8px;\n }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"8-transitions\"\ngit push origin 8-transitions\ngit checkout -b 9-modal-actions\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nHaving transitions and animations built into Svelte is a huge bonus to the framework, the transitions are declarative and intuitive. With this section we just introduced the concept. For more about transitions checkout the svelte transition section in Svelte Docs: https://svelte.dev/docs#svelte_transition, there are also many talks about svelte transitions on the Svelte Society Youtube channel - https://www.youtube.com/c/sveltesociety\n\n---\n\n## Take Break\n\n---\n\n## Modal and Actions\n\nTo handle adding a new city, lets use a modal component.\n\nhttps://svelte.dev/repl/e94473c00c5c422fa736ba60a2ca0e61?version=3.26.0\n\n```html\n\u003cscript\u003e\n  import { createEventDispatcher } from \"svelte\";\n  import { scale } from \"svelte/transition\";\n  export let open = false;\n  const dispatch = createEventDispatcher();\n\n  function handleCloseClick() {\n    dispatch(\"close\");\n  }\n\n  // action\n\n  function modalAction(node) {\n    let fns = [];\n    if (document.body.style.overflow !== \"hidden\") {\n      const original = document.body.style.overflow;\n      document.body.style.overflow = \"hidden\";\n      fns = [...fns, () =\u003e (document.body.style.overflow = original)];\n    }\n    return {\n      destroy: () =\u003e fns.map((fn) =\u003e fn()),\n    };\n  }\n\u003c/script\u003e\n{#if open}\n\u003cdiv use:modalAction on:click=\"{handleCloseClick}\"\u003e\n  \u003csection on:click|stopPropagation\u003e\n    \u003caside in:scale out:scale=\"{{duration:\" 500}}\u003e\n      \u003cslot /\u003e\n      \u003cbr /\u003e\n      \u003cbutton on:click|preventDefault=\"{handleCloseClick}\"\u003eClose\u003c/button\u003e\n    \u003c/aside\u003e\n  \u003c/section\u003e\n\u003c/div\u003e\n{/if}\n\u003cstyle\u003e\n  section {\n    height: 100%;\n    display: grid;\n    place-items: center;\n  }\n  aside {\n    background-color: white;\n  }\n  div {\n    position: absolute;\n    background-color: rgba(0, 0, 0, 0.8);\n    top: 0;\n    left: 0;\n    height: 100%;\n    width: 100%;\n  }\n\u003c/style\u003e\n```\n\n`src/views/Add.svelte`\n\n```html\n\u003cscript\u003e\n  import { createEventDispatcher } from \"svelte\";\n  const dispatch = createEventDispatcher();\n  let city;\n\n  function handleSubmit() {\n    dispatch(\"add\", { city });\n    city = \"\";\n  }\n\u003c/script\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    \u003ch3\u003eAdd Location\u003c/h3\u003e\n    \u003cform on:submit|preventDefault=\"{handleSubmit}\"\u003e\n      \u003cdiv\u003e\n        \u003clabel for=\"city\"\u003eCity\u003c/label\u003e\n        \u003cinput type=\"text\" id=\"city\" bind:value=\"{city}\" /\u003e\n      \u003c/div\u003e\n      \u003c!--\n      \u003cdiv\u003e\n        \u003clabel for=\"country\"\u003eCountry\u003c/label\u003e\n        \u003cselect id=\"country\"\u003e\n          \u003coption\u003e-- select --\u003c/option\u003e\n        \u003c/select\u003e\n      \u003c/div\u003e\n      --\u003e\n      \u003cdiv\u003e\n        \u003cbutton id=\"add-btn\"\u003eAdd\u003c/button\u003e\n        \u003ca href=\"\"\u003eCancel\u003c/a\u003e\n      \u003c/div\u003e\n    \u003c/form\u003e\n  \u003c/section\u003e\n\u003c/main\u003e\n```\n\n`src/views/Favorites.svelte`\n\n```html\n\u003cscript\u003e\n  import { blur } from 'svelte/transition'\n  import { dispatch } from '../store'\n  import CityCard from '../components/CityCard.svelte'\n  import { router } from 'tinro'\n  import Modal from '../components/Modal.svelte'\n  import Add from './Add.svelte'\n\n  let cities = ['Charleston, SC', 'New york, NY', 'San Francisco, CA']\n  let open = false\n\n  function changeCurrentCity(city) {\n    return () =\u003e {\n      // set current city...then\n      dispatch({type: 'SET_CURRENT', payload: city})\n        .then(() =\u003e router.goto('/'))\n    }\n  }\n\n  function openModal() {\n    open = true\n  }\n\n  function addCity({detail}) {\n    cities = [...cities, detail.city]\n    open = false\n  }\n\n\u003c/script\u003e\n\u003cdiv in:blur={{delay: 1000, duration: 1000}}\u003e\n\u003cnav\u003e\n  \u003cdiv\u003eFavorites\u003c/div\u003e\n  \u003ca href=\"#\" on:click={openModal}\u003eAdd\u003c/a\u003e\n\u003c/nav\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    {#each cities as city}\n      \u003cCityCard {city} on:click={changeCurrentCity(city)} /\u003e\n    {/each}\n  \u003c/section\u003e\n\u003c/main\u003e\n\u003c/div\u003e\n\u003cModal {open} on:close={_ =\u003e open = false}\u003e\n  \u003cAdd on:add={addCity} /\u003e\n\u003c/Modal\u003e\n\u003cstyle\u003e\n nav {\n   height: 24px;\n   margin-bottom: 0;\n   margin-left: 8px;\n   margin-right: 8px;\n }\n\u003c/style\u003e\n```\n\n\u003carticle\u003e\u003caside\u003e\n\nCreate checkpoint\n\n```sh\ngit commit -am \"9-modal-actions\"\ngit push origin 9-modal-actions\ngit checkout -b 10-testing\n```\n\n\u003c/aside\u003e\u003c/article\u003e\n\n### Summary\n\nIn this section, we introduced slots and actions. With slots you can add content to your component adding markup within the element tags. With actions, you can hook into the browser event system to directly manage elements, as you would with jquery. Actions are great for interacting with third party javascript modules, like d3 and map components. \n\n\n---\n\n## Testing\n\n## Install cypress\n\nThe first thing we need to do is install cypress and cypress-svelte-unit-test:\n\n```sh\nyarn add -D cypress@6.9.1 cypress-svelte-unit-test\n```\n\n## Initialize Cypress\n\n```sh\nyarn run cypress open\n```\n\nOnce you see the modal, you click `ok` and then close the cypress window.\n\n\u003carticle\u003e\u003caside\u003e\n\nCypress comes with a bunch of examples, but we don't need them for this walkthrough, you can get rid of them by running `rm -rf cypress/integration/examples`\n\n\u003c/aside\u003e\u003c/article\u003e\n\n## Tell cypress to use rollup when working with svelte components\n\nOpen an index.js file in the cypress/plugins directory and edit the following function:\n\n```js\nmodule.exports = (on) =\u003e {\n  const filePreprocessor = require(\"@bahmutov/cy-rollup\");\n  on(\"file:preprocessor\", filePreprocessor());\n};\n```\n\nThis code will give cypress the information it needs to compile the svelte component\n\n## Turn on component support for cypress\n\nWhen you installed cypress, the install created a cypress.json file, in this file we need to add the following entries:\n\n```json\n{\n  \"experimentalComponentTesting\": true,\n  \"componentFolder\": \"src\",\n  \"testFiles\": \"**/*spec.js\"\n}\n```\n\nThe first entry is a flag to enable component testing, the second is the location of the components, finally the third entry is a pattern matcher for the test files.\n\n## Write a test\n\nNow, we have our project configured we can write a test.\n\nIn our src folder, lets create a test for the App component.\n\ncreate a new file src/views/Add.spec.js\n\n```js\nimport Add from \"./Add.svelte\";\nimport { mount } from \"cypress-svelte-unit-test\";\n\nit(\"add city using form\", () =\u003e {\n  mount(Add, {\n    callbacks: {\n      add: cy.stub().as(\"add\"),\n    },\n  });\n  cy.get(\"input#city\").type(\"Boston, MA\");\n  cy.get(\"button#add-btn\").click();\n  cy.get(\"@add\")\n    .should(\"be.called\")\n    .its(\"firstCall.args.0.detail\")\n    .should(\"deep.equal\", { city: \"Boston, MA\" });\n});\n```\n\n`src/views/Add.svelte`\n\n```html\n\u003cscript\u003e\n  import { createEventDispatcher } from \"svelte\";\n  const dispatch = createEventDispatcher();\n\n  let city = \"\";\n  let selected = \"\";\n\n  function submitCity() {\n    dispatch(\"add\", { city });\n    selected = city;\n    city = \"\";\n  }\n\u003c/script\u003e\n\u003cmain\u003e\n  \u003csection\u003e\n    \u003ch3\u003eAdd Location\u003c/h3\u003e\n    {#if selected !== ''}\n    \u003cdiv id=\"selected\"\u003eselected: {selected}\u003c/div\u003e\n    {/if}\n    \u003cform on:submit|preventDefault=\"{submitCity}\"\u003e\n      \u003cdiv\u003e\n        \u003clabel for=\"city\"\u003eCity\u003c/label\u003e\n        \u003cinput type=\"text\" id=\"city\" bind:value=\"{city}\" /\u003e\n      \u003c/div\u003e\n      \u003c!--\n      \u003cdiv\u003e\n        \u003clabel for=\"country\"\u003eCountry\u003c/label\u003e\n        \u003cselect id=\"country\"\u003e\n          \u003coption\u003e-- select --\u003c/option\u003e\n        \u003c/select\u003e\n      \u003c/div\u003e\n      --\u003e\n      \u003cdiv\u003e\n        \u003cbutton id=\"add-btn\"\u003eAdd\u003c/button\u003e\n        \u003ca href=\"\"\u003eCancel\u003c/a\u003e\n      \u003c/div\u003e\n    \u003c/form\u003e\n  \u003c/section\u003e\n\u003c/main\u003e\n```\n\nThe `it` function takes a description and a `callback` function, in the callback function,\nwe use the imported mount function to mount the App component passing the name\nprop. Then we use cypress contains function to find the dom element h1 and validate\nif it contains the following text 'Hello World!'\n\n## Run the Test\n\nNow that we have our test, lets run it and see if it passes.\n\n```\nyarn run cypress run\n```\n\nThis command will run cypress in the console.\n\nIf everything went as planned you should see a print out showing App.spec.js passed and all specs passed!\n\nhttps://docs.cypress.io/guides/overview/why-cypress.html#In-a-nutshell\n\n\n### Summary\n\nThis is a very short introduction to cypress, cypress is a powerful tool for testing Javascript using different styles and patterns. Cypress gives you a clean way to TDD with Svelte. \n\n---\n\n## Fin\n\nThis ends the workshop, but you do not have to end the journey here, you can take this application to greater lengths:\n\n* Add a dynamic background, based on the weather\n* Store your favorite cities on localStorage\n* Show wind and tidal information from weatherbit\n* Convert web app to mobile app using capacitorjs\n* What else?\n\n\u003e This workshop is designed to continue to practice and re-enforce component architecture concepts. So feel free to go through the workshop as a tutorial a couple of times to get some refinement. Then, try to create the app with no direction from the guide, see what you remember, and see what you don't, then refer to complete the parts you are not familar with, then repeat. \n\n### Thank you!\n\n## Feedback Form\n\nhttps://forms.gle/5hV7or82nd8qGFAd8\n\n\u003c/main\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyper63%2Fweather","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyper63%2Fweather","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyper63%2Fweather/lists"}