Related
Ember and Braintree Hosted Fields are not a good mix so far, Braintree Support are out of ideas on this one. When the form renders on the page it calls the action to create the client. The client is undefined.
picture-this-44ac48bef9f8df633632a4d202da2379.js:57 Uncaught TypeError: Cannot read property 'client' of undefined
component hbs
<script src="https://js.braintreegateway.com/web/3.81.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.81.0/js/hosted-fields.min.js"></script>
<div class="demo-frame" {{did-insert this.setupBraintreeHostedFields}}>
<form action="/" method="post" id="cardForm" >
<label class="hosted-fields--label" for="card-number">Card Number</label>
<div id="card-number" class="hosted-field"></div>
<label class="hosted-fields--label" for="expiration-date">Expiration Date</label>
<div id="expiration-date" class="hosted-field"></div>
<label class="hosted-fields--label" for="cvv">CVV</label>
<div id="cvv" class="hosted-field"></div>
<label class="hosted-fields--label" for="postal-code">Postal Code</label>
<div id="postal-code" class="hosted-field"></div>
<div class="button-container">
<input type="submit" class="button button--small button--green" value="Purchase" id="submit"/>
</div>
</form>
</div>
component class
import Component from '#glimmer/component';
import { action } from '#ember/object';
import { inject as service } from '#ember/service';
import { tracked } from '#glimmer/tracking';
import { braintree } from 'braintree-web';
export default class CardPaymentComponent extends Component {
#action
setupBraintreeHostedFields() {
alert('booh');
var form = document.querySelector('#cardForm');
var authorization = 'sandbox_24nzd6x7_gyvpsk2myght4c2p';
braintree.client.create({
authorization: authorization
}, function(err, clientInstance) {
if (err) {
console.error(err);
return;
}
createHostedFields(clientInstance);
});
function createHostedFields(clientInstance) {
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'font-size': '16px',
'font-family': 'courier, monospace',
'font-weight': 'lighter',
'color': '#ccc'
},
':focus': {
'color': 'black'
},
'.valid': {
'color': '#8bdda8'
}
},
fields: {
number: {
selector: '#card-number',
placeholder: '4111 1111 1111 1111'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationDate: {
selector: '#expiration-date',
placeholder: 'MM/YYYY'
},
postalCode: {
selector: '#postal-code',
placeholder: '11111'
}
}
}, function (err, hostedFieldsInstance) {
var tokenize = function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (err, payload) {
if (err) {
alert('Something went wrong. Check your card details and try again.');
return;
}
alert('Submit your nonce (' + payload.nonce + ') to your server here!');
});
};
form.addEventListener('submit', tokenize, false);
});
}
}
}
package.json
...
"ember-cli": "^3.25.2",
"braintree-web": "^3.81.0",
...
** Final Solution **
NPM braintree-web not required. Component class does not have access to the Braintree Window object. Move the tags to the app/index.html as outlined in the accepted answer.
component hbs
<article class="rental">
<form action="/" method="post" id="cardForm">
<label class="hosted-fields--label" for="card-number">Cardholder Name</label>
<div id="card-holder-name" class="hosted-field payment"></div>
<label class="hosted-fields--label" for="card-number">Email</label>
<div id="email" class="hosted-field payment"></div>
<label class="hosted-fields--label" for="card-number">Card Number</label>
<div id="card-number" class="hosted-field payment"></div>
<label class="hosted-fields--label" for="expiration-date">Expiration Date</label>
<div id="expiration-date" class="hosted-field payment"></div>
<label class="hosted-fields--label" for="cvv">CVV</label>
<div id="cvv" class="hosted-field payment"></div>
<label class="hosted-fields--label" for="postal-code">Postal Code</label>
<div id="postal-code" class="hosted-field payment"></div>
<div class="button-container">
<input type="submit" class="button" value="Purchase" id="submit"/>
</div>
</form>
</article>
<script>
var form = document.querySelector('#cardForm');
var authorization = 'sandbox_24nzd6x7_gyvpsk2myght4c2p';
braintree.client.create({
authorization: authorization
}, function(err, clientInstance) {
if (err) {
console.error(err);
return;
}
createHostedFields(clientInstance);
});
function createHostedFields(clientInstance) {
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'font-size': '1.2em',
'font-family': 'courier, monospace',
'font-weight': 'lighter',
'color': '#ccc'
},
':focus': {
'color': 'black'
},
'.valid': {
'color': '#8bdda8'
}
},
fields: {
number: {
selector: '#card-number',
placeholder: '4111 1111 1111 1111'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationDate: {
selector: '#expiration-date',
placeholder: 'MM/YYYY'
},
postalCode: {
selector: '#postal-code',
placeholder: '11111'
}
}
}, function (err, hostedFieldsInstance) {
var tokenize = function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (err, payload) {
if (err) {
alert('Something went wrong. Check your card details and try again.');
return;
}
alert('Submit your nonce (' + payload.nonce + ') to your server here!');
});
};
form.addEventListener('submit', tokenize, false);
});
}
</script>
You can use Braintree SDK via either the direct script tag or using the npm module with the help of ember-auto-import. In your case, you are using both.
For simplicity, let's use the script tag to inject the SDK. The issue in your snippet is that you are trying to load the script tag inside a component handlebar file. the handlebars (.hbs file) cannot load scripts using a <script> tag. We need to move the script tag to the index.html file present inside the app folder. This will load the SDK properly to be used inside a component.
app/index.html:
<body>
...
<script src="https://js.braintreegateway.com/web/3.81.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.81.0/js/hosted-fields.min.js"></script>
{{content-for "body-footer"}}
</body>
Once you inject the SDK properly, you can use the braintree window object without any issue.
I want to select an image and POST that to the Django REST API that I have built but I am getting an error in doing so
""product_image": ["The submitted data was not a file. Check the encoding type on the form."]"
I can create and update strings and integers but when I try add the image field I get this error. I can POST images through Postman so I don't think its my API that's at fault.
Can anyone help me out here as I have looked around but can't seem to find anything.
add-product.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { ApiService } from 'src/app/api.service';
import { Product } from 'src/app/models/Product';
import { Location } from '#angular/common';
import { Shop } from '../../../models/Shop';
#Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.css']
})
export class AddProductComponent implements OnInit {
product!: Product;
selectedFile!: File;
colours = [
'Red',
'Blue',
'Orange',
'Yellow',
'White',
'Black',
'Navy',
'Brown',
'Purple',
'Pink'
]
categories = [
'Food & Beverages',
'Clothing & Accessories',
'Home & Garden',
'Health & Beauty',
'Sports & Leisure',
'Electronic & Computing'
]
stock = [
'In Stock',
'Out Of Stock',
]
productForm = new FormGroup({
name: new FormControl(''),
price: new FormControl(0.00),
product_image: new FormControl(''),
product_description: new FormControl(''),
quantity: new FormControl(0),
colour: new FormControl(''),
stock: new FormControl(''),
shipping_fee: new FormControl(0.00),
weight: new FormControl(0.00),
shop: new FormControl(''),
category: new FormControl(''),
});
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private location: Location,
private router: Router,
private postService: ApiService
) { }
ngOnInit(): void {
}
goBack(): void {
this.location.back();
}
onFileSelected(event: any) {
this.selectedFile = <File>event.target.files[0]
console.log(event)
}
addProduct(): void {
console.log(this.productForm.value)
this.apiService.addProduct(
this.productForm.value
).subscribe(() => this.goBack(),
result => console.log(result),
);
}
}
add-product.component.html
<form [formGroup]="productForm">
<label>Product Name</label><br>
<input type="text" formControlName="name"><br>
<label>Price</label><br>
<input type="text" formControlName="price"/><br>
<input type="file" formControlName="product_image" (change)="onFileSelected($event)"><br>
<label>Description</label><br>
<textarea type="text" formControlName="product_description"></textarea><br>
<label>Quantity of Products</label><br>
<input type="text" formControlName="quantity"/><br>
<label>Colour of Product</label><br>
<select formControlName="colour">
<option value="" disabled>Choose a Colour</option>
<option *ngFor="let colour of colours" [ngValue]="colour">
{{ colour }}
</option>
</select><br>
<label>Stock Availability</label><br>
<select formControlName="stock">
<option value="" disabled>Stock Availability</option>
<option *ngFor="let stock of stock" [ngValue]="stock">
{{ stock }}
</option>
</select><br>
<label>Shipping Fee Price</label><br>
<input type="text" formControlName="shipping_fee"/><br>
<label>Weight (kg)</label><br>
<input type="text" formControlName="weight"/><br>
<label>Shop</label><br>
<input type="text" formControlName="shop"/><br>
<label>Category</label><br>
<select formControlName="category">
<option value="" disabled>Choose a Category</option>
<option *ngFor="let category of categories" [ngValue]="category">
{{ category }}
</option>
</select><br>
</form>
<button type="submit" (click)="addProduct()">Add</button>
<button (click)="goBack()">go back</button>
api.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Product } from './models/Product';
import { CookieService } from 'ngx-cookie-service';
import { Category } from './models/Category';
import { Shop } from './models/Shop';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ApiService {
baseUrl = 'http://127.0.0.1:8000/';
baseProductUrl = `${this.baseUrl}app/products/`
baseCategoryUrl = `${this.baseUrl}app/categories/`
baseShopUrl = `${this.baseUrl}app/shops/`
headers = new HttpHeaders({
'Content-Type': 'application/json',
});
constructor(
private httpClient: HttpClient,
private cookieService: CookieService,
) { }
/* Product CRUD */
/* POST: add a product to the server */
addProduct(product: Product) {
const productUrl = `${this.baseProductUrl}`
return this.httpClient.post<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// DELETE: delete the product from the server
deleteProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.delete<Product>(productUrl, {headers: this.getAuthHeaders()})
}
// PUT: update the product on the server
updateProduct(product_code: number, product: Product): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.put<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// GET: get all products from the server
getProducts() {
return this.httpClient.get<Product[]>(this.baseProductUrl, {headers: this.getAuthHeaders()});
}
// GET: get one product from the server
getProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.get<Product>(productUrl, {headers: this.getAuthHeaders()});
}
// GET: get all categories from the server
getCategories() {
return this.httpClient.get<Category[]>(this.baseCategoryUrl, {headers: this.getAuthHeaders()});
}
// GET: get one category from the server
getCategory(id: number): Observable<Category> {
const url = `${this.baseCategoryUrl}${id}/`;
return this.httpClient.get<Category>(url, {headers: this.getAuthHeaders()});
}
// GET: get all shops from the server
getShops() {
return this.httpClient.get<Shop[]>(this.baseShopUrl, {headers: this.getAuthHeaders()});
}
// GET: get one shop from the server
getShop(business_reg: string): Observable<Shop> {
const url = `${this.baseShopUrl}${business_reg}/`;
return this.httpClient.get<Shop>(url, {headers: this.getAuthHeaders()});
}
loginUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}auth/`, body, {headers: this.headers});
}
registerUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}user/users/`, body, {headers: this.headers});
}
getAuthHeaders() {
const token = this.cookieService.get('ur-token')
return new HttpHeaders({
'Content-Type': 'application/json',
Authorization: `Token ${token}`
});
}
}
The content-type must be multipart/form-data instead of application-json
that way you can send files to an API
I am trying to test a form submit.. it seems that trigger is not appropriate
1) calls store action login when the form is submitted
LoginPage.vue
TypeError: wrapper.find(...).trigger is not a function
at Context.<anonymous> (webpack:///test/unit/specs/pages/LoginPage.spec.js:35:28 <- index.js:50826:29)
my vue component to be tested
LoginPage.vue
<template>
<div class="container">
<div class="login-page">
<h1 class="title">Login to existing account</h1>
<form #submit.prevent="submit()" class="form form--login grid">
<div class="row">
<label for="login__email">Email</label>
<input type="text" id="login__email" class="input--block" v-model="email" v-on:keyup="clearErrorMessage" />
</div>
<div class="row">
<label for="login__password">Password</label>
<input type="password" id="login__password" class="input--block" v-model="password" v-on:keyup="clearErrorMessage" />
</div><!-- /.row -->
<div class="row">
<label></label>
<button id="submit" type="submit">Login</button>
</div><!-- /.row -->
<div v-show='hasError' class="row">
<label></label>
<p class="error">Invalid credentials</p>
</div>
</form>
</div><!-- /.login-page -->
</div>
</template>
<script>
import store from '#/vuex/store'
import { mapActions } from 'vuex'
import _ from 'underscore'
export default {
name: 'loginPage',
data () {
return {
email: 'john.doe#domain.com',
password: 'john123',
hasError: false
}
},
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
store
}
</script>
LoginPage.spec.js
import LoginPage from '#/pages/LoginPage'
import Vue from 'vue'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('LoginPage.vue', () => {
let actions
let getters
let store
beforeEach(() => {
actions = {
login: sinon.stub()
}
getters = {
isAuthenticated: () => {
state => state.isAuthenticated
}
}
store = new Vuex.Store({
actions,
getters,
state: {
isAuthenticated: false,
currentUserId: ''
}
})
})
it('calls store action login when the form is submitted', (done) => {
const wrapper = mount(LoginPage, { store })
wrapper.find('#submit').trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
done()
})
})
})
Should be trigger 'click' on the form tag !
it('calls store action login when the form is submitted', (done) => {
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
done()
})
})
I have an ionic 2 app and am using native FB Login to retrieve name/pic and saving it to NativeStorage. The flow is that I open WelcomePage, log in, and save the data. From there, navPush to HomePage. So far it works great.
However, I have a ProfilePage (accessible via tabRoot), the fails. The reason is that in my profile.html I have the following tag that should render Username (this works on HomePage, but not on ProfilePage):
{{ user.name }}
The error I get on XCode is:
2017-05-02 18:40:41.657374 FoxBox App[1102:226159] ERROR: Failed to navigate: undefined is not an object (evaluating 'co.user.picture')
Note that for some reason it prepends it with 'co.' which I have no idea where its coming from or what it means.
Here is the WelcomePage code:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { HomePage } from '../home/home';
import { AboutPage } from '../about/about';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
import {GoogleAnalytics} from 'ionic-native';
#Component({
selector: 'page-welcome',
templateUrl: 'welcome.html'
})
export class WelcomePage {
FB_APP_ID: number = 1234567890;
homePage = HomePage;
aboutPage = AboutPage;
constructor(
public navCtrl: NavController,
//public facebookAuth: FacebookAuth,
//public auth: Auth,
//public user: User,
) {
Facebook.browserInit(this.FB_APP_ID, "v2.8");
}
doFbLogin(){
//alert("fb is logged in");
let permissions = new Array();
let nav = this.navCtrl;
//the permissions your facebook app needs from the user
permissions = ["public_profile"];
Facebook.login(permissions)
.then(function(response){
let userId = response.authResponse.userID;
let params = new Array();
//Getting name and gender properties
Facebook.api("/me?fields=name,gender", params)
.then(function(user) {
user.picture = "https://graph.facebook.com/" + userId + "/picture?type=large";
//now we have the users info, let's save it in the NativeStorage
NativeStorage.setItem('user',
{
name: user.name,
gender: user.gender,
picture: user.picture,
email: user.email,
})
.then(function(){
nav.push(HomePage);
console.log("User Data Stored");
}, function (error) {
console.log(error);
})
})
}, function(error){
console.log(error);
});
}
}
Here is the HomePage code:
import { Component } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
import { ClaimPage } from '../claim/claim';
import { SocialSharing } from '#ionic-native/social-sharing';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
import {GoogleAnalytics} from 'ionic-native';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
posts: any;
sendme: any;
claimPage = ClaimPage;
user: any;
userReady: boolean = false;
constructor(
public navCtrl: NavController,
public http: Http,
private sharingVar: SocialSharing,
public platform: Platform,
) {
// Check to see if user already exists (via FB login)
let env = this;
NativeStorage.getItem('user')
.then(function (data){
env.user = {
name: data.name,
gender: data.gender,
picture: data.picture
};
env.userReady = true;
// console.log(data.name);
}, function(error){
console.log(error);
});
this.platform.ready().then(() => {
//alert("platform is ready");
GoogleAnalytics.trackView("Home-Page", "http://foxboxapp.com/home", true);
//alert("GA called");
});
this.http.get('http://getyourtryston.com/foox/sample.php').map(res => res.json()).subscribe(data => {
this.posts = data.data.children;
});
}
otherShare(){
this.sharingVar.share("FoxBox App","Get Awesome College Deals",null/*File*/,"http://fooxsocial.com")
.then(()=>{
//alert("Success");
},
()=>{
alert("Sharing Failed!")
})
}
}
And here is the ProfilePage code which fails:
import { Component } from '#angular/core';
import { NavController, Platform } from 'ionic-angular';
import { WelcomePage } from '../welcome/welcome';
import {GoogleAnalytics} from 'ionic-native';
import { SocialSharing } from '#ionic-native/social-sharing';
import { Facebook, NativeStorage } from 'ionic-native';
//import { FacebookAuth, User, Auth } from '#ionic/cloud-angular';
//import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
#Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
user: any;
userReady: boolean = false;
constructor(
public navCtrl: NavController,
public platform: Platform,
private sharingVar: SocialSharing,
//public facebookAuth:FacebookAuth,
//public auth:Auth,
) {
// Check to see if user already exists (via FB login)
let env = this;
NativeStorage.getItem('user')
.then(function (data){
env.user = {
name: data.name,
gender: data.gender,
picture: data.picture
};
env.userReady = true;
// console.log(data.name);
}, function(error){
console.log(error);
});
// PLATFORM READY, do your thang!
this.platform.ready().then(() => {
// Ping Google Analytics
GoogleAnalytics.trackView("Profile Page", "http://foxboxapp.com/home", true);
});
}
otherShare(){
this.sharingVar.share("FOOX Social App","Get Awesome College Deals",null/*File*/,"http://fooxsocial.com")
.then(()=>{
//alert("Success");
},
()=>{
alert("Sharing Failed!")
})
}
doFbLogout(){
var nav = this.navCtrl;
Facebook.logout()
.then(function(response) {
//user logged out so we will remove him from the NativeStorage
NativeStorage.remove('user');
nav.push(WelcomePage);
}, function(error){
console.log(error);
});
}
}
And here is ProfilePage.html
<ion-header>
<ion-navbar color="light" hideBackButton="true">
<ion-buttons end>
<button ion-button icon-only (click)="otherShare()">
<ion-icon name="share"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card class="pCard">
<div class="pHeader" align="center">
<div *ngIf="user" class="pImgBox" align="center">
<img class="pImage" src="{{ user.picture }}">
</div>
<div class="pUsername" align="center">
<div *ngIf="user"> {{user.name}} </div>
<br>
<span class="pSchool">(Santa Monica College)</span>
</div>
</div>
<ion-list>
<ion-item class="pItems">
Share App
</ion-item>
<ion-item class="pItems">
Give Us Feedback
</ion-item>
<ion-item class="pItems">
Suggest Vendors
</ion-item>
<ion-item class="pItems">
Privacy & Terms of Service
</ion-item>
<ion-item class="pItems">
Log Out
</ion-item>
<ion-item class="pItems">
Delete Account
</ion-item>
</ion-list>
</ion-card>
<button ion-button round (click)="doFbLogout()">Log Out</button>
</ion-content>
I should mention that, if I remove {{ user.name }} and {{ user.picture }} from my ProfilePage.html, there does NOT seem to be any problems. In fact, if you notice in the ts of ProfilePage, I can both Alert and Console.log the username (data.name) without any issues.
I'm a beginner and would appreciate any concise help in this regard. Thank you.
I finally found a solution. In the html file (ProfilePage.html), I used an *ngIf conditional:
<div *ngIf="user"> {{user.name}} </div>
This will introduce a delay such that the 'user' object is no longer null as it reads from NativeStorage.
Alternatively, an Elvis Operator also works for me:
<div> {{ user?.name }} </div>
I am trying to make an website that connets with a weather api and gets some info about current weather in given city. When i looked at network flow in my application i do recive a json that contais that information but i cannot map it and display results. Error i revice is :
TypeError: response.json(...).map is not a function WeatherSearchComponent.ts:138
at MapSubscriber.project (WeatherSearchComponent.ts:84)
at MapSubscriber._next (map.js:77)
at MapSubscriber.Subscriber.next (Subscriber.js:89)
at XMLHttpRequest.onLoad (http.umd.js:1083)
at ZoneDelegate.invokeTask (zone.js:225)
at Object.onInvokeTask (core.umd.js:6004)
at ZoneDelegate.invokeTask (zone.js:224)
at Zone.runTask (zone.js:125)
at XMLHttpRequest.ZoneTask.invoke (zone.js:293)
Heres WeatherSearchComponent:
import {
Component,
Injectable,
OnInit,
ElementRef,
EventEmitter,
Inject
} from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs';
import 'rxjs/Rx';
import 'rxjs/add/operator/map';
export var WEATHER_API_KEY: string = 'api_key';
export var WEATHER_API_URL: string ='http://api.openweathermap.org/data/2.5/weather';
export var GDANSK_ID: string = '3099434';
/*let loadingGif: string = ((<any>window).__karma__) ? '' :
require('images/loading.gif');*/
class SearchResult {
content: string;
constructor(obj?: any) {
this.content= obj && obj.content ||
WEATHER_API_URL + 'id=' + obj.city + 'appid=' + WEATHER_API_KEY;
}
}
// http://api.openweathermap.org/data/2.5/weather?
// id=3099434&appid=api_key
#Injectable()
export class WeatherService {
constructor(private http: Http,
#Inject(WEATHER_API_KEY) private apiKey: string,
#Inject(WEATHER_API_URL) private apiUrl: string) {
}
search(city: string): Observable<SearchResult[]> {
let params: string = [
`q=${city}`,
`appid=${this.apiKey}`
].join('&');
let queryUrl: string = `${this.apiUrl}?${params}`;
console.log("query url" , queryUrl);
var getRequest = this.http.get(queryUrl);
console.log("getRequest", getRequest);
return getRequest
.map((response: Response) => {
return (<any>response.json()).map(item => {
console.log("raw item", item); // uncomment if you want to debug
return new SearchResult({
content: item.main.temp
});
});
});
}
}
export var weatherServiceInjectables: Array<any> = [
{provide: WeatherService, useClass: WeatherService},
{provide: WEATHER_API_KEY, useValue: WEATHER_API_KEY},
{provide: WEATHER_API_URL, useValue: WEATHER_API_URL}
];
/**
* SearchBox displays the search box and emits events based on the results
*/
#Component({
outputs: ['loading', 'results'],
selector: 'search-box',
template: `
<input type="text" class="form-control" placeholder="Search" autofocus>
`
})
export class SearchBox implements OnInit {
loading: EventEmitter<boolean> = new EventEmitter<boolean>();
results: EventEmitter<SearchResult[]> = new EventEmitter<SearchResult[]> ();
constructor(private weather: WeatherService,
private el: ElementRef) {
}
ngOnInit(): void {
// convert the `keyup` event into an observable stream
var observable =
Observable.fromEvent(this.el.nativeElement, 'keyup');
observable
.map((e: any) => e.target.value) // extract the value of the input
.filter((text: string) => text.length > 1) // filter out if empty
.debounceTime(250) // only once every 250ms
.do(() => this.loading.next(true)) // enable loading
// search, discarding old events if new input comes in
.map((query: string) => this.weather.search(query))
.switch()
// act on the return of the search
.subscribe(
(results: SearchResult[]) => { // on sucesss
this.loading.next(false);
this.results.next(results);
},
(err: any) => { // on error
console.log(err);
this.loading.next(false);
},
() => { // on completion
this.loading.next(false);
}
);
}
}
#Component({
inputs: ['result'],
selector: 'search-result',
template: `
<div class="col-sm-6 col-md-3">
<div class="thumbnail">
<img src="{{result.thumbnailUrl}}">
<div class="caption">
<h3>{{result}}</h3>
<p>{{result}}</p>
<p><a href="{{result.videoUrl}}"
class="btn btn-default" role="button">Watch</a></p>
</div>
</div>
</div>
`
})
export class SearchResultComponent {
result: SearchResult;
}
#Component({
selector: 'weather-search',
template: `
<div class='container'>
<div class="page-header">
<h1>Weather Search</h1>
</div>
<div class="row">
<div class="input-group input-group-lg col-md-12">
<search-box
(loading)="loading = $event"
(results)="updateResults($event)"
></search-box>
</div>
</div>
<div class="row">
<search-result
*ngFor="let result of results"
[result]="result">
</search-result>
</div>
</div>
`
})
export class WeatherSearchComponent {
results: SearchResult[];
updateResults(results: SearchResult[]): void {
this.results = results;
// console.log("results:", this.results); // uncomment to take a look
}
}
This is what i do recive from api call :
/*
{"coord":{"lon":18.65,"lat":54.35},
"weather":[{"id":801,"main":"Clouds",
"description":"few clouds","icon":"02n"}],"base":"stations",
"main":{"temp":270.15,"pressure":1035,"humidity":92,
"temp_min":270.15,"temp_max":270.15},"visibility":10000,
"wind":{"speed":2.1,"deg":290},"clouds":{"all":20},
"dt":1484665200,
"sys":{"type":1,"id":5349,"message":0.0029,
"country":"PL","sunrise":1484636080,"sunset":1484665040},"
id":3099434,"name":"Gdansk","cod":200}
*/
It's not clear from your question whether you're trying to .map() on a variable or on an observable.
Scenario #1: variable.map()
variable must be an array. Looks from your code that your variable contains an object.
const jsonData = {
"coord":{"lon":18.65,"lat":54.35},
// Abridged for brevity...
id":3099434,"name":"Gdansk","cod":200
};
jsonData.map(...); // WON'T WORK - `jsonData` must be an array
Scenario #2: observable.map()
You must import the map operator in your code before mapping:
import 'rxjs/add/operator/map';
// Then, later
Observable.map(...);