DoneJS StealJS jQuery++ FuncUnit DocumentJS
6.6.1
5.33.3 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
    • getting started
      • CRUD Guide
      • Setting Up CanJS
      • Technology Overview
    • topics
      • HTML
      • Routing
      • Service Layer
      • Debugging
      • Forms
      • Testing
      • Logic
      • Server-Side Rendering
    • app guides
      • Chat Guide
      • TodoMVC Guide
      • TodoMVC with StealJS
    • beginner recipes
      • Canvas Clock
      • Credit Card
      • File Navigator
      • Signup and Login
      • Video Player
      • Weather Report
    • intermediate recipes
      • CTA Bus Map
      • Multiple Modals
      • Text Editor
      • Tinder Carousel
    • advanced recipes
      • Credit Card
      • File Navigator
      • Playlist Editor
      • Search, List, Details
    • upgrade
      • Migrating to CanJS 3
      • Migrating to CanJS 4
      • Migrating to CanJS 5
      • Migrating to CanJS 6
      • Using Codemods
    • other
      • Reading the API Docs
  • API Docs
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

Weather Report

  • Edit on GitHub

This beginner guide walks you through building a simple weather report widget. It takes about 25 minutes to complete. It was written with CanJS 6.2.

The final widget looks like:

See the Pen Weather Report Guide (CanJS 6 Beginner) [Finished] by Bitovi (@bitovi) on CodePen.

To use the widget:

  1. Enter a location (example: Chicago)
  2. See the 5-Day forecast for the city you entered.

Start this tutorial by cloning the following CodePen:

See the Pen Weather Report Guide (CanJS 6 Beginner) [Starter] by Bitovi (@bitovi) on CodePen.

This CodePen has initial prototype HTML and CSS which is useful for getting the application to look right.

This starter code includes:

  • Initial prototype HTML and CSS which is useful for getting the application to look right
  • Pre-made styles so the app looks pretty 😍

The following sections are broken down into:

  • The problem — A description of what the section is trying to accomplish.
  • What you need to know — Information about CanJS that is useful for solving the problem.
  • The solution — The solution to the problem.

Setup

The problem

Set up this CanJS app by:

  1. Defining a WeatherReport can-stache-element class.
  2. Defining a custom element that outputs the pre-constructed HTML.
  3. Rendering the template by using the custom element.

What you need to know

  • A basic CanJS setup uses instances of a StacheElement, which glues ObservableObject-like properties to a view in order to manage its behavior as follows:

    import { StacheElement } from "//unpkg.com/can@6/core.mjs";
    
    // Define the Component
    class MyComponent extends StacheElement {
      static view = `…`;
    
      static props = {};
    }
    
    // Define the custom element tag
    customElements.define("my-component", CCPayment);
    
  • CanJS components will be mounted in the DOM by adding the component tag in the HTML page:

    <my-component></my-component>
    

The solution

Update the JavaScript tab to:

  • Define can-stache-element class
  • Define element’s view by copying the content of the HTML tab in the view property
  • Register the custom element tag by using customElements.define
import { StacheElement } from "//unpkg.com/can@6/core.mjs";

class WeatherReport extends StacheElement {
    static view = `
        <div class="weather-widget">
            <div class="location-entry">
                <label for="location">Enter your location:</label>
                <input id="location" type="text" />
            </div>

            <p class="loading-message">Loading forecast…</p>

            <div class="forecast">
                <h1>5-Day Chicago Weather Forecast</h1>
                <ul>
                    <li>
                        <span class="date">Today</span>
                        <span class="description light-rain">light rain</span>
                        <span class="high-temp">100<sup>°</sup></span>
                        <span class="low-temp">-10<sup>°</sup></span>
                    </li>
                </ul>
            </div>
        </div>
    `;
}
customElements.define("weather-report", WeatherReport);

Update the HTML tab to replace the old template with custom element:

<weather-report></weather-report>

<script>
    const appKey = "17ba01d3931252096b4ab03df56891cd";

    function transformData(data) {
        let forecasts = [];
        const today = new Date();

        const transformedForecast = data.list.map(item => {
            return {
                date: new Date(item.dt_txt),
                high: item.main.temp_max,
                icon: item.weather[0].icon,
                low: item.main.temp_min,
                text: item.weather[0].description
            };
        });

        const todayForecasts = transformedForecast.filter(forecast => {
            const forecastDate = forecast.date;
            return today.getDate() === forecastDate.getDate();
        });

        if (todayForecasts.length) {
            forecasts.push(todayForecasts[0]);
        }

        const nextDaysForecasts = transformedForecast.filter(forecast => {
            const forecastDate = forecast.date;
            return (
                today.getDate() !== forecastDate.getDate() &&
                forecastDate.getHours() === 12
            );
        });

        forecasts = forecasts.concat(nextDaysForecasts);

        return forecasts;
    }
</script>

Allow a user to enter a location

The problem

We want an input element to:

  • Allow a person to type a location to search for weather.
  • Show the user the location they typed.

What you need to know

  • The properties defined in the props object can have default values like:

    class MyComponent extends StacheElement {
      static props = {
        age: {
          default: 34
        }
      };
    }
    
  • CanJS components use can-stache to render data in a template and keep it live.

  • The key:to binding can set an input’s value to an element property like:

    <input value:to="this.age" />
    

The solution

Update the JavaScript tab to:

  1. Define a location property as a string.
  2. Update location value on the element when the input changes.
  3. Show value of the element’s location property.
import { StacheElement } from "//unpkg.com/can@6/core.mjs";

class WeatherReport extends StacheElement {
    static view = `
        <div class="weather-widget">
            <div class="location-entry">
                <label for="location">Enter your location:</label>
                <input id="location" type="text" value:to="this.location" />
            </div>

            <p class="loading-message">Loading forecast…</p>

            <div class="forecast">
                <h1>5-Day {{ this.location }} Weather Forecast</h1>
                <ul>
                    <li>
                        <span class="date">Today</span>
                        <span class="description light-rain">light rain</span>
                        <span class="high-temp">100<sup>°</sup></span>
                        <span class="low-temp">-10<sup>°</sup></span>
                    </li>
                </ul>
            </div>
        </div>
    `;

    static props = {
        location: String
    };
}
customElements.define("weather-report", WeatherReport);

Get the forecast

The problem

Once we’ve entered a city, we need to get the forecast data.

What you need to know

  • ES5 getter syntax can be used to define a props property that changes when another property changes. For example, the following defines an excitedMessage property that always has a ! after the message property:

    class MyElement extends StacheElement
      static props = {
        get excitedMessage() {
          return this.message+"!";
        },
        message: "string"
      }
    });
    
  • Use {{# if(value) }} to do if/else branching in can-stache.

  • Promises are observable in can-stache. Given a promise somePromise, you can:

    • Check if the promise is loading like: {{# if(somePromise.isPending) }}.
  • Open Weather Map provides a service endpoint for retrieving a forecast that matches a city name. For example, the following requests the forecast for Chicago:

    https://api.openweathermap.org/data/2.5/forecast?q=Chicago&mode=json&units=imperial&apiKey=17ba01d3931252096b4ab03df56891cd
    
  • The fetch API is an easy way to make requests to a URL and get back JSON. Use it like:

    fetch(url).then(response => {
      return response.json();
    }).then(data => {
    });
    
  • can-param is able to convert an object into a query string format like:

    param({format: "json", q: "chicago"}) //-> "format=json&q=chicago"
    

The solution

Update the template in the JS tab to:

  1. Wrap the loading message with {{# if(this.forecastPromise.isPending) }}
  2. Wrap the forecast with {{# if(this.forecastPromise.isResolved) }}
  3. Define a forecastPromise property that gets the forecast.
import { param, StacheElement } from "//unpkg.com/can@6/core.mjs";

class WeatherReport extends StacheElement {
    static view = `
        <div class="weather-widget">
            <div class="location-entry">
                <label for="location">Enter your location:</label>
                <input id="location" type="text" value:to="this.location" />
            </div>

            {{# if(this.forecastPromise.isPending) }}
                <p class="loading-message">Loading forecast…</p>
            {{/ if }}

            {{# if(this.forecastPromise.isResolved) }}
                <div class="forecast">
                    <h1>5-Day {{ this.location }} Weather Forecast</h1>
                    <ul>
                        <li>
                            <span class="date">Today</span>
                            <span class="description light-rain">light rain</span>
                            <span class="high-temp">100<sup>°</sup></span>
                            <span class="low-temp">-10<sup>°</sup></span>
                        </li>
                    </ul>
                </div>
            {{/ if }}
        </div>
    `;

    static props = {
        get forecastPromise() {
            if (this.location) {
                return fetch(
                    "https://api.openweathermap.org/data/2.5/forecast?" +
                        param({
                            apiKey: appKey,
                            mode: "json",
                            q: this.location,
                            units: "imperial"
                        })
                )
                    .then(response => {
                        return response.json();
                    })
                    .then(transformData);
            }
        },
        location: String
    };
}
customElements.define("weather-report", WeatherReport);

Display the forecast

The problem

Now that we’ve fetched the forecast data, we need to display it in the app.

What you need to know

  • Use {{# for(of) }} to do looping in can-stache.

  • Promises are observable in can-stache. Given a promise somePromise, you can:

    • Loop through the resolved value of the promise like: {{# for(item of somePromise.value) }}.
  • Methods on the element can be called within a can-stache template like:

    {{ this.myMethod(someValue) }}
    
  • The stylesheet includes icons for classNames that match: sunny, light-rain, scattered-clouds, etc.

  • You can check whether one Date is equal to another Date by calling getDate on both dates and comparing the values.

The solution

Update the template in the JS tab to:

  1. Display each forecast day’s details (date, text, high, and low).
  2. Define a toClassName method that lowercases and hyphenates any text passed in.
  3. Use the toClassName method to convert the forecast’s text into a className value that
  4. Define isToday method that returns true if the passed date is today will be matched by the stylesheet.
  5. Use the isToday method to display the term Today instead of today’s date.
import { param, StacheElement } from "//unpkg.com/can@6/core.mjs";

class WeatherReport extends StacheElement {
    static view = `
        <div class="weather-widget">
            <div class="location-entry">
                <label for="location">Enter your location:</label>
                <input id="location" type="text" value:to="this.location" />
            </div>

            {{# if(this.forecastPromise.isPending) }}
                <p class="loading-message">Loading forecast…</p>
            {{/ if }}

            {{# if(this.forecastPromise.isResolved) }}
                <div class="forecast">
                    <h1>5-Day {{ this.location }} Weather Forecast</h1>
                    <ul>
                        {{# for(forecast of this.forecastPromise.value) }}
                            <li>
                                <span class="date">
                                    {{# if(this.isToday(forecast.date)) }}
                                        Today
                                    {{ else }}
                                        {{ forecast.date.toLocaleDateString() }}
                                    {{/ if }}
                                </span>
                                <span class="description {{ this.toClassName(forecast.text) }}">{{ forecast.text }}</span>
                                <span class="high-temp">{{ forecast.high }}<sup>°</sup></span>
                                <span class="low-temp">{{ forecast.low }}<sup>°</sup></span>
                            </li>
                        {{/ for }}
                    </ul>
                </div>
            {{/ if }}
        </div>
    `;

    static props = {
        get forecastPromise() {
            if (this.location) {
                return fetch(
                    "https://api.openweathermap.org/data/2.5/forecast?" +
                        param({
                            apiKey: appKey,
                            mode: "json",
                            q: this.location,
                            units: "imperial"
                        })
                )
                    .then(response => {
                        return response.json();
                    })
                    .then(transformData);
            }
        },
        location: String
    };

    isToday(date) {
        const today = new Date();
        return today.getDate() === date.getDate();
    }

    toClassName(txt) {
        return txt.toLowerCase().replace(/ /g, "-");
    }
}
customElements.define("weather-report", WeatherReport);

Result

When finished, you should see something like the following CodePen:

See the Pen Weather Report Guide (CanJS 6 Beginner) [Finished] by Bitovi (@bitovi) on CodePen.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 6.6.1.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news