I'm trying to make a custom element for the 8Tracks API using Polymer, and am trying to get a user's mixes and display their covers. I'm attempting to do it as follows:
<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">
<polymer-element name="eight-tracks">
<template>
<polymer-jsonp id="ajax" auto url="https://8tracks.com/users/1/mixes.json?api_key=MY_API_KEY?api_version=3&callback=?"></polymer-jsonp>
<div class="instagram">
<template id="mixes" repeat="{{item in mixes}}" index="i">
<div id="{{item.index}}" class="item">
<img src="{{mixes.cover_urls.sq200}}">
</div>
</template>
</div>
</template>
<script>
Polymer('eight-tracks', {
ready: function () {
this.$.mixes.model = this.mixes;
this.$.ajax.addEventListener('polymer-response',
function (e) {
this.mixes = {mixes: e.detail.response.data};
this.$.mixes.model = {mixes: e.detail.response.data};
this.fire('eight-tracks-load', {response: e.detail.response.data});
}.bind(this)
);
}
});
</script>
</polymer-element>
I'm trying to model it on the x-instagram and x-flickr ones with little success. I'm very new to all of this, so any guidance would be appreciated. I know that the JSON/API call is working, because I get this back in the console:
{
"mix_set": {
"pagination": {
"current_page": 1,
"per_page": 12,
"offset_by": 0,
"next_page": 2,
"previous_page": null,
"total_entries": 31,
"total_pages": 3
},
"mixes": [
{
"id": 679109,
"path": "/mixes/679109",
"web_path": "/remi/chill-hip-hop-beats-part-2",
"name": "Chill Hip-Hop Beats (part 2)",
"user_id": 1,
"published": true,
"cover_urls": {
"sq56": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=56&h=56&fit=crop",
"sq100": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=100&h=100&fit=crop",
"sq133": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=133&h=133&fit=crop",
"max133w": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=133&fit=max",
"max200": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=200&h=200&fit=max",
"sq250": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=250&h=250&fit=crop",
"sq500": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=500&h=500&fit=crop",
"max1024": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?fm=jpg&q=65&w=1024&h=1024&fit=max",
"original_imgix_url": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?q=65&sharp=15&vib=10&fm=jpg&fit=crop",
"original": "http://8tracks.imgix.net/i/001/067/826/68609.original-9904.jpg?q=65&sharp=15&vib=10&fm=jpg&fit=crop",
"animated": false
},
"description": "More beats to relax and lounge to\nSequel to http://8tracks.com/remi/laid-back-hip-hop-beats \n",
"plays_count": 1013,
"tag_list_cache": "hip-hip, lounge, beats, chill, smoke",
"first_published_at": "2012-03-26T19:11:15Z",
"first_published_at_timestamp": 1332789075,
"likes_count": 111,
"certification": "gold",
"duration": 2211,
"tracks_count": 8
},
...
}
This continues for all the mixes, but you get the idea.
I know that I'm very likely to have screwed the tag up, but I'm only just starting out with APIs/Web Components, so any help would be brilliant.
One of the key features of Polymer is its data-binding and emphasis on declarative solutions to problems. In your case, you can instruct the polymer-jsonp element to place the response in a variable for you then wire it declaratively into your template.
<polymer-jsonp response="{{response}}" ...></polymer-jsonp>
<template repeat="{{item, index in response.mix_set.mixes}}">...</template>
You can then use Polymer's declarative event mapping to wire up your response handler (to fire the eight-tracks-loaded event for any external listeners that may want to know).
<polymer-jsonp on-polymer-response="{{onResponse}}" ...></polymer-jsonp>
<script>
Polymer('eight-tracks', {
...
onResponse: function(e) {
this.fire('eight-tracks-load', {response: e.detail.response});
}
});
</script>
Here's a working jsbin with a hard-coded response to show that it works, and here's one without the hard-coded response that should work for you if you plug in your API key (although you probably don't want that on jsbin :-)
Related
The following example gives me a blank screen (jsfiddle here). Even the parts which have nothing to do with the loop are not being rendered.
HTML:
<div id="app">
<button #click="objectFromApi">
run objectFromApi function
</button>
<div
v-for="obj in myObject[0].results"
:key="obj.id"
>
<p>
{{ obj.message }}
</p>
</div>
</div>
JavaScript:
new Vue({
el: "#app",
data: {
myObject: []
},
methods: {
objectFromApi: function(){
this.myObject.push(
{
"count": 5,
"results": [
{
"id": 1,
"message": "object 1"
},
{
"id": 2,
"message": "object 2"
}
]
}
)
}
},
//created() {
// this.objectFromApi()
//}
})
Nevertheless it does work if:
1.) Either using objectFromApi function directly in the created life cycle hook (what I don't want!)
created() {
this.objectFromApi()
}
2.) Or (without the use of created life cycle hook) if I go directly into the nested results array and spread the objects out like this (what I also don't want!)
this.myObject.push(
...{
"count": 5,
"next": "http://127.0.0.1:8000/api/someurl/?page=2",
"previous": null,
"results": [
{
"id": 1,
"message": "object 1"
},
{
"id": 2,
"message": "object 2"
}
]
}.results
)
When using option 2.) of course the v-for loop has to look different:
v-for="obj in myObject" instead of v-for="obj in myObject[0].results"
What is wrong with my initial example?
When the component is first rendering the array myObject will be empty.
During rendering it attempts this:
<div
v-for="obj in myObject[0].results"
:key="obj.id"
>
The value of myObject[0] will be undefined. Attempting to access the results property of undefined will result in an error. This error will cause rendering to fail. Nothing will be shown, even the parts that didn't fail.
There are various ways to fix this problem. You could prepopulate the data with suitable empty properties:
data: {
myObject: [
{
results: []
}
]
}
Alternatively, as you've noted, you could change the loop to use v-for="obj in myObject", changing objectFromApi accordingly to only store the results array in myObject. Even if you don't want that exact change some similar change is probably a good idea because the [0] part strongly suggests you've got a problem with your data model. The key thing here is that it avoids trying to access nested objects that don't exist. The use of the spread operator in your second example is largely irrelevant.
Or you could skip the loop in the template:
<template v-if="myObject[0]">
<div
v-for="obj in myObject[0].results"
:key="obj.id"
>
...
</div>
</template>
I have a nav menu that is rendered using a navigation.ts json file for the menu items. When it gets to the navitem component it uses a ngIf to check if the item from the navigation file has a "function" key and if it does, the desired behavior is for it to use the string value from item.function in the object to fill the value for the (click) event.
In reality, the console throws an error saying "_co.item.function is not a function"
HTML
<span class="nav-link" *ngIf="item.function" (click)="item.function()" matRipple>
<mat-icon class="nav-link-icon" *ngIf="item.icon">{{item.icon}}</mat-icon>
<span class="nav-link-title" [translate]="item.translate">{{item.title}}</span>
<span class="nav-link-badge" *ngIf="item.badge" [translate]="item.badge.translate"
[ngStyle]="{'background-color': item.badge.bg,'color': item.badge.fg}">
{{item.badge.title}}
</span>
</span>
Navigation.ts
[{
"id": "accounting",
"title": "Accounting",
"type": "collapse",
"children": [
{
"id" : "salesmenSalesLocation",
"title": "Salesmen Sales Location",
"type": "item",
"function": "handleSelect(ReportTypes.SalesmenSalesLocations)"
},
{
"id": "laggingLedgerEntries",
"title": "Lagging Ledger Entries",
"type": "item",
"function": "handleSelect(ReportTypes.LaggingLedgerEntries)"
}
]}]
I have also tried it as (click)="item.function" with no success.
I'm assuming you can change the data source here, because otherwise I don't see any good solution.
A string is not a function, and while you can turn it into one with eval that is a bad idea. What you should really do instead is just pass in a value that tells the function what to use.
Change your data to something like this:
{
"id" : "salesmenSalesLocation",
"title": "Salesmen Sales Location",
"type": "item",
"reportTypeSource": "SalesmenSalesLocations"
},
{
"id": "laggingLedgerEntries",
"title": "Lagging Ledger Entries",
"type": "item",
"reportTypeSource": "LaggingLedgerEntries"
}
Then pass that value to your function and use that to tell it where to look:
handleSelect (reportTypeSource: string) {
const reportType = ReportTypes[reportTypeSource]
// continue as before
}
And call it in your HTML like this:
(click)="handleSelect(item.reportTypeSource)"
Problem lies here:
"function": "handleSelect(ReportTypes.LaggingLedgerEntries)"
And here:
(click)="item.function()"
You cannot simply pass a string and expect the component to execute a function and also know exactly what to do. Here you need to pass the actual function.
Your setup looks over-config'd. I would tear the config down and put the logic into the component itself. Don't be afraid to have more template as well, if anything it makes things more legible (as opposed to the config)
Does that function exist in the component or just the model? If it is just on the model it won't work. (click) is looking for a method on the component. It is, ostensibly just a string in this instance.
I am trying to redevelop a project I am working on into Angular 2 from Angular 1. Currently I am using Ionic to build it as it is used on iOS. This redevelopment is my first interaction with TypeScript at all.
So far I have been able to get most of what I need done. But now my issue is that I want to access a property of my class and display it in the HTML code but only after it has been set, this way I wouldn't run into errors. But my code or at least Ionic says that the property is undefined even if I use the ngOnInit or ngAfterContentInit functions to try and do this.
I may be going about this code the wrong way but I would like to ask how to access these properties so that I can display them in the HTML as *ngFor= 'let e of EventInfo' then {{e.eventName}}. Please help me out. I will continue to search the previous questions to hopefully find some inspiration to answer my question.
I will add my class code so far below.
To add some more information, the e.eventName I am planning to use on a menu that toggles out and it will display the information I want from eventInfo.
For the code below, I also added what pullJson.getMondayJson looks like.
This is what eventInfo initially looked like when I tried it out.
this.eventInfo = [{rType: this.mondayJson.agendaEvents[0].rowType, eventName:this.mondayJson.agendaEvents[0].eventName, startTime:this.mondayJson.agendaEvents[0].startTime, endTime:this.mondayJson.agendaEvents[0].endTime}];
#Component({
selector: 'center-panel',
host: {'(window:scroll)': 'track($event)'},
templateUrl: 'center-panel.html'
})
export class CenterPanel {
mondayJson;
tuesdayJson;
wednesdayJson;
pages: Array<{title: string}>;
eventInfo: Array<{eventName: string, startTime: any, endTime: any}>;
public constructor(public pullJson:PullJson, public menu: MenuController, locationA2:Location) {
this.pages = [
{ title: 'CenterPanel'},
{ title: 'RightPanel'}
];
pullJson.getMondayJson
.subscribe(
getMondayJson => this.mondayJson = {
dayOfWeek: getMondayJson.dayOfWeek.toUpperCase(),
agendaEvents: getMondayJson.agendaEvents
},
console.error,
() => console.log('Completed HTTP for Monday!!')
);
}
this.getMondayJson = this.http.get('https://../remote-server-file.json')
.map(response => response.json());
Someone asked me to post the template code:
<div id="{{ 'MONDAY-events-' + event.startTime }}" menuToggle class="agenda-event-container" *ngFor="let event of mondayJson.agendaEvents" (click)="event.itemClicked = !event.itemClicked">
<div class="agenda-event-row {{event.rowType}}">
<div class="time-container">
<div class="event-left-border-{{event.iconType}}">
<div class="start-time">
{{event.startTime}}
</div>
<div id="MONDAY-events-endTime" class="end-time">
{{event.endTime}}
</div>
</div>
</div>
</div>
</div>
</div>
Here is an example of what the Json files look like:
{
"rowType": "event",
"eventName": "Facilitator Training",
"iconType": "workshop",
"startTime": "8:00AM",
"endTime": "10:00AM",
"headerLocation": "Go To Brooklyn",
"locationDetails": {
"jsonGroupFile": {
"subData": "",
},
"hardcodedList": ["<b>Test:<br>New York"]
},
"subEvents": [
{
"presentationName": "",
"durationInMinutes": "120",
"speakers": "Test Person"
}
],
"images": [
{
"imageType": "hotel"
},
{
"imageType": "map"
}
],
"files": []
}
I am working on an AngularJS tutorial
This tutorial covers the ng-repeat directive, an AngularJS directive used repeating data.
To show an example of ng-repeat, The author enters periodic table elements in a JSON format, covering element's name, element #, etc into controller logic($scope)
To display the elements(code below), the author simply used the directive with a html un-ordered list
<ul>
<li data-ng-repeat="element in periodic.elements">{{element.name}} </li>
</ul>
I tried doing the same JsFiddle but the list of elements isn't showing up, only {{element.name}}
At first I thought this was an AngularJS syntax issue but I checked over the scope attribute, if the controller names match, etc.... I made sure to enable the AngularJS option in JsFiddle as well.
Does anyone know what the issue is or why this list isn't showing up?
You forget completing controller sytax '});' at the end of the code.
'use strict';
var chemistryApp = angular.module('chemistryApp', []);
chemistryApp.controller(
'chemistryController',
function chemistryController($scope) {
$scope.periodic = {elements: [
{
"atomicNumber": 1,
"name": "Hydrogen",
"atomicWeight": 1.00794,
"phase": "Gas",
"ionization": 13.5984,
"melting": -259.15,
"boiling": -252.87
},
{
"atomicNumber": 2,
"name": "Helium",
"atomicWeight": 4.002602,
"phase": "Gas",
"ionization": 24.5874,
"melting": 0,
"boiling": -268.93
},
{
"atomicNumber": 3,
"name": "Lithium",
"atomicWeight": 6.941,
"phase": "Solid",
"ionization": 5.3917,
"melting": 180.54,
"boiling": 1342
}
]
};
});
Working Fiddle
I'm trying to render some JSON data using jsRender. Below is my sample JSON data
"PageContentList": [
{
"ContentId": 51,
"Title": "60 seconds with Rick",
"ContentMediaTypeList": [
{
"MimeType": "image/png",
"MediaTypeName": "Image",
"Path": "http://local.admin.solutiaconsulting.com/uploads/4a906d8e-983a-4b54-a627-0e8d48145620.png"
},
{
"MimeType": "video/webm",
"MediaTypeName": "Video",
"Path": "http://local.admin.solutiaconsulting.com/uploads/3a6c56c3-0ef9-4f57-9c84-9caa48a09044.webm"
}
]
}
]
I want to pull the different images based on MediaTypeName instead of the ordinal position. I know I can do this:
{{:ContentMediaTypeList[1].Path}}
and I know I can do this:
{{for ContentMediaTypeList}}
{{if MediaTypeName == 'Video'}}
{{:Path}}
{{/if}}
{{/for}}
But the second approach seems cumbersome and wasteful. Is what I want to do even possible? Thanks for your help.
Thank you Charlie for your reply. I ended up changing the structure of my JSON to this:
"PageContentList": [
{
"ContentId": 44,
"Title": "Company Name",
"Gallery": {
"Images": [
{
"Path": "http://local.admin.solutiaconsulting.com/uploads/9b577ef7-ea8a-42a1-b967-89debbc634c0.jpg",
"MimeType": "image/jpeg",
"ImageWidth": 0,
"ImageHeight": 0,
"AltText": null
}
],
"Videos": [ ]
}
},
....
]
Then for my template:
{{for PageContentList}}
<video id="whatIsSolutiaVideo" class="video-js vjs-default-skin" controls width="{{:Gallery.Images[0].ImageWidth}}" height="{{:Gallery.Images[0].ImageHeight}}" poster={{:Gallery.Images[0].Path}}" preload="auto">
{{for Gallery.Videos}}
<source type="{{:MimeType}}" src="{{:Path}}">
{{/for}}
</video>
{{/for}}
I know each JSON data will have a section for Images, Videos, etc. Now it becomes a data entry issue. If the data entry person did their job right, then there will be data. The approach I moved to ensures the correct data structure. Thanks for forcing me to think through this.
There is no easy way to do it, because you are not supposed to perform that kind of logic in the view. Place your filter in the model or a controller before you render it.
The one exception I can think of in this case is if you would like to limit the number of items to display. That might or might not be a suitable task for the view depending on the situation.
Examples:
Keep the logic of a limit out of the view:
- List of Question on Stack Overflow with paging (limit, offset, sorting)
Enforce a limit in the view:
- A list of attached images for each question. Your mobile view only have room to show one per question. The view suitable for desktop have room for three.
How to structure your application and where to place your logic is a debate with many opinions though. This style of separation is what I've been taught from experience and peers, but I am sure that there are many out there with different idéas.
Here's a simple way to filter your data using underscore.js.
var videoContent = _.filter(contentMediaTypeList, function(mediaType) {
return mediaType.mediaTypeName == "video";
});
Working JSFiddle: http://jsfiddle.net/n98yW/
Or create a closure out of it for reusability
var mediaTypeFilter = function(mediaTypeName) {
return function(contentMediaTypeList) {
return _.filter(contentMediaTypeList, function(currMediaType) {
return currMediaType.MediaTypeName == mediaTypeName;
});
}
};
And use it like this
var videoFilter = mediaTypeFilter("video");
var videoContent = videoFilter(contentMediaTypeList);
Here's another JSFiddle for this solution using a closure: http://jsfiddle.net/GA55g/2/