Yeah, it's a stupid name. I like rhymes.
TODO: implement input for either zipcode or city TODO: °F to °C conversion button
| <html ng-app="weatherApp"> | |
| <body ng-controller="weatherController"> | |
| <div id="main" ng-show="weather.place"> | |
| <div id="place">{{weather.place}}</div> | |
| <div class="current-tile"> | |
| <div class="current-title">Now</div> | |
| <div class="current-info"> | |
| <p class="temp">{{weather.temp + units}} <img class="icon" ng-src="{{weather.icon}}"></p> | |
| <p class="desc"><cite>{{weather.description}}</cite></p> | |
| </div> | |
| </div> | |
| <div id="forecast-toggle"> | |
| <button class="btn btn-danger btn-md" ng-show="!forecast.length" ng-click="showForecast()">Show Forecast</button> | |
| <button class="btn btn-danger btn-md" ng-show="forecast.length" ng-click="hideForecast()">Hide Forecast</button> | |
| </div> | |
| <div id="forecast"> | |
| <div class="forecast-tile" ng-show="forecast" ng-repeat="day in forecast"> | |
| <div class="tile-title">{{day.date | date:'MMM d'}}</div> | |
| <div class="tile-info"> | |
| <p class="temp">{{day.temp + units}}<img class="icon" ng-src="{{day.icon}}"></p> | |
| <p class="desc"><cite>{{day.description}}</cite></p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="footer"> | |
| Weather Tether © 2015 | |
| </div> | |
| </body> | |
| </html> |
| var app = angular.module('weatherApp', ['ngAnimate']); | |
| app.controller('weatherController', ['$scope', 'weatherService', function($scope, weatherService) { | |
| $scope.weather = {}; | |
| $scope.forecast = []; | |
| $scope.fiveDayForecast = []; | |
| $scope.zipCode = 92646; | |
| $scope.city = "Huntington+Beach" | |
| $scope.buttonLabel = "Show Forecast" | |
| $scope.showForecast = function() { | |
| $scope.forecast = $scope.fiveDayForecast; | |
| } | |
| $scope.hideForecast = function() { | |
| $scope.forecast = []; | |
| } | |
| weatherService.getCurrent($scope.zipCode, function(data) { | |
| var obj = data; | |
| $scope.weather.place = obj.name; | |
| $scope.weather.date = obj.dt * 1000; | |
| $scope.weather.temp = Math.round(obj.main.temp); | |
| $scope.units = "°F"; | |
| $scope.weather.description = obj.weather[0].description; | |
| $scope.weather.icon = "http://openweathermap.org/img/w/" + obj.weather[0].icon + ".png"; | |
| }); | |
| weatherService.getFiveDay($scope.city, function(data) { | |
| var obj = data; | |
| for (var i=0; i < obj.cnt; i++) { | |
| var dayObj = {}; | |
| dayObj.date = obj.list[i].dt * 1000; | |
| dayObj.temp = Math.round(obj.list[i].temp.day); | |
| dayObj.description = obj.list[i].weather[0].main; | |
| dayObj.icon = "http://openweathermap.org/img/w/" + obj.list[i].weather[0].icon + ".png"; | |
| $scope.fiveDayForecast.push(dayObj); | |
| } | |
| }); | |
| }]); | |
| app.factory('weatherService', function($http) { | |
| return { | |
| getCurrent: function(zip, callback) { | |
| $http.jsonp("http://api.openweathermap.org/data/2.5/weather?zip=" + zip + ",us" + units + jsonCb) | |
| .success(callback); | |
| }, | |
| getFiveDay: function(city, callback) { | |
| // http://api.openweathermap.org/data/2.5/forecast/daily?q=Huntington+Beach&cnt=5&mode=json | |
| $http.jsonp("http://api.openweathermap.org/data/2.5/forecast/daily?q=" + city + "&cnt=5&mode=json" + units + jsonCb) | |
| .success(callback); | |
| } | |
| }; | |
| }); | |
| var units = "&units=imperial"; | |
| var jsonCb = "&callback=JSON_CALLBACK"; |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-animate.min.js"></script> |
| @import url(http://fonts.googleapis.com/css?family=Open+Sans); | |
| @import url(http://fonts.googleapis.com/css?family=Sansita+One); | |
| $font: 'Open Sans', sans-serif; | |
| $place-font: 'Sansita One', cursive; | |
| $back-color: #2196F3; | |
| $accent-color: #90CAF9; | |
| $border-color: lighten(#0D47A1, 35); | |
| $box-color: lighten(grey, 35); | |
| @mixin border-radius($rad) { | |
| -webkit-border-radius: $rad; | |
| -moz-border-radius: $rad; | |
| -ms-border-radius: $rad; | |
| border-radius: $rad; | |
| } | |
| @mixin border-top-left-radius($rad) { | |
| -webkit-border-top-left-radius: $rad; | |
| -moz-border-top-left-radius: $rad; | |
| -ms-border-top-left-radius: $rad; | |
| border-top-left-radius: $rad; | |
| } | |
| @mixin border-top-right-radius($rad) { | |
| -webkit-border-top-right-radius: $rad; | |
| -moz-border-top-right-radius: $rad; | |
| -ms-border-top-right-radius: $rad; | |
| border-top-right-radius: $rad; | |
| } | |
| @mixin animation($anim) { | |
| -webkit-animation: $anim; | |
| -moz-animation: $anim; | |
| -ms-animation: $anim; | |
| animation: $anim; | |
| } | |
| @mixin animation-delay($time) { | |
| -webkit-animation-delay: $time; | |
| -moz-animation-delay: $time; | |
| -ms-animation-delay: $time; | |
| animation-delay: $time; | |
| } | |
| @mixin animation-duration($time) { | |
| -webkit-animation-duration: $time; | |
| -moz-animation-duration: $time; | |
| -ms-animation-duration: $time; | |
| animation-duration: $time; | |
| } | |
| body { | |
| font-family: $font; | |
| background-color: $back-color; | |
| #main { | |
| text-align: center; | |
| @include animation(fadeIn 1.0s); | |
| #place { | |
| margin-top: 80px; | |
| font-family: $place-font; | |
| font-size: 56px; | |
| } | |
| .current-tile { | |
| display: inline-block; | |
| margin: 40px; | |
| text-align: center; | |
| background-color: $box-color; | |
| border: 1px solid $border-color; | |
| box-shadow: 12px 17px 50px 0 rgba(0,0,0,.19); | |
| box-shadow: 7px 12px 15px 0 rgba(0,0,0,.24); | |
| @include border-radius(6px); | |
| .current-title { | |
| background-color: $accent-color; | |
| border-bottom: 1px solid $border-color; | |
| font-size: 36px; | |
| padding: 5px; | |
| @include border-top-left-radius(6px); | |
| @include border-top-right-radius(6px); | |
| } | |
| .current-info { | |
| padding: 10px 30px; | |
| .temp { | |
| font-weight: bold; | |
| font-size: 48px; | |
| } | |
| .desc { | |
| font-size: 16px; | |
| } | |
| } | |
| } /* End current tile */ | |
| #forecast { | |
| text-align: center; | |
| padding: 15px; | |
| .forecast-tile { | |
| display: inline-block; | |
| border: 1px solid $border-color; | |
| margin: 5px; | |
| background-color: $box-color; | |
| box-shadow: 12px 17px 50px 0 rgba(0,0,0,.19); | |
| box-shadow: 7px 12px 15px 0 rgba(0,0,0,.24); | |
| @include border-radius(6px); | |
| &.ng-enter, &.ng-move { | |
| @include animation(fadeInLeft 0.75s); | |
| @include animation-duration(0); | |
| } | |
| &.ng-enter-stagger { | |
| @include animation-delay(0.1s); | |
| } | |
| &.ng-leave { | |
| @include animation(fadeOutRight 0.75s); | |
| @include animation-duration(0); | |
| } | |
| &.ng-leave-stagger { | |
| @include animation-delay(0.1s); | |
| } | |
| .tile-title { | |
| background-color: $accent-color; | |
| border-bottom: 1px solid $border-color; | |
| @include border-top-left-radius(6px); | |
| @include border-top-right-radius(6px); | |
| } | |
| .tile-info { | |
| padding: 10px; | |
| .temp { | |
| font-weight: bold; | |
| font-size: 36px; | |
| } | |
| .desc { | |
| font-size: 14px; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #footer { | |
| position: fixed; | |
| text-align: left; | |
| padding-left: 5px; | |
| color: $box-color; | |
| width: 100%; | |
| bottom: 0; | |
| background-color: black; | |
| } | |
| } |
| <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" /> | |
| <link href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.3/animate.min.css" rel="stylesheet" /> |