How do I format my AngularJS data model? - json

Hi I am just beginning with angular and I am struggling to find the answer to what I'm sure is quite a simple thing to do.
I am currently getting the values of some input boxes and pushing them into my scope. This is creating one long 'array' eg:
['data-1','data-2','data-3']
I would like to format my data in the following way instead
$scope.data = [
{
'header1': 'data1-1',
'header1': 'data1-2',
'header1': 'data1-3'
},
{
'header1': 'data2-1',
'header1': 'data2-2',
'header1': 'data2-3'
}
]
This is my function as it currently is.
$scope.createRow = function(){
angular.forEach(angular.element("input"), function(value, key){
$scope.td.push($(value).val());
});
}
Any help or pointers would be greatly appreciated as I am just getting my head round the angular way

Doing this isn't hard... but before I give you a gun to shoot yourself in the foot, just to say that I think it would be beneficial to explain WHY you want structure in that other format you are mentioning. You seem to have lots of data repetition and that's always a red flag.
Now for the code, you just need to create object before pushing it to the array like:
$scope.createRow = function(){
angular.forEach(angular.element("input"), function(value, key){
var obj = {
"header1": val + "-1",
"header2": val + "-2"
};
$scope.td.push(obj);
});
}
EDIT:
OK, so you are trying to add new row to the table. First of all, you shouldn't be doing angular.forEach, but rather those input elements in HTML should bind to existing scope object, like:
// obviously use better names than Input1Value
// I am here just giving you example
$scope.bindData = {
Input1Value: null,
Input2Value: null
};
// in HTML you will do
// <input ng-model="bindData.Input1Value" />
// <input ng-model="bindData.Input2Value" />
Now that you've eliminated that nasty angular.forEach you need to have some kind of event handler, for example when user clicks the button you want to add this object to the array to which table is data bound. Just be sure to clone the $scope.bindData object when you add it to array.
$scope.createRow = function(){
var newRowData = $scope.cloneObject($scope.bindData);
$scope.td.push(newRowData);
}
// http://heyjavascript.com/4-creative-ways-to-clone-objects/
// https://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
$scope.cloneObject = function(objToClone) {
var newObj = (JSON.parse(JSON.stringify(objToClone)));
}
To close this answer off - keep in mind, if you ever find yourself directly referencing HTML DOM elements in Javascript with AngularJS - you are doing something wrong. It's a nasty habit to eliminate, especially if you are coming from jQuery background (and how doesn't?), where everything is $("#OhHiThere_ElementWithThisId).
Obviously the main thread on this topic on StackOverflow is this one:
“Thinking in AngularJS” if I have a jQuery background?
However I find that it's too theoretical, so Google around and you may find better overviews like:
jQuery vs. AngularJS: A Comparison and Migration Walkthrough

Related

ReactJS fetch JSON API Data -- The correct way?

I have been fighting with ES6 trying to come up with, what should be, a pretty straightforward operation. I want to call JSON API data for Bitcoin from one of the three following websites:
https://cryptowat.ch
https://coinmarketcap.com/
https://www.cryptocompare.com/
All three sites API endpoints go straight to the price I want and I think this may be the problem. There is no array of data, just the specific price. In my example using #3 above, the only object is "USD". That being said, I think I'm overthinking the process as getting into APIs with much more data and arrays of data -- I have accomplished using ReactJS.
Trying to reach a single endpoint that shows up as the "State" in the React DOM Inspector as "USD" and is pulling in the correct price, I cannot get the price to render on the page even though ReactJS is seeing it and capturing it.
My code:
var BitcoinApp = React.createClass({
getInitialState: function() {
return {
"USD": []
}
},
componentDidMount: function() {
var th = this;
this.serverRequest =
axios.get(this.props.source)
.then(function(result) {
th.setState({
USD: result.data.USD
});
})
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<span>
{this.state.USD.map(function(Data) {
return (
<div key={Data.USD} className="testbtc">
<p>{Data.USD}</p>
</div>
);
})}
</span>
)
}
});
ReactDOM.render(<BitcoinApp source="https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD&e=Coinbase" />, document.querySelector("#btcPrice"));
I will mention that I have done a lot of research into this and have found a lot of answers -- all different! Everyone knows the ReactJS docs are severely outdated so finding the right path with ReactJS is difficult to say the least. Also, I'm using "axios" to "GET" the API data as I've read that "fetch" isn't globally supported yet? Is this still the case in 2017?
Using the above method, I can see this in the Inspector:
But when I go over to the "Console" portion of the inspector, I'm told that "this.state.USD.map is not a function".
I feel like I'm right on the cusp of solving this task, but I think I'm getting something wrong with the mapping of the promise.
the problem is that:
th.setState({
USD: result.data.USD
});
is seting not iterable object. I mean that this.state.USD.map is not a function means that USD is not an array (and you can see this in console).
Try this to see what happens:
th.setState({
USD: [result.data.USD]
});
However tho, you wrote:
There is no array of data, just the specific price.
then I think the best solution is to change just the render method and initial state:
render: function() {
return (
<span>
<div className="testbtc">
<p>{this.state.USD}</p>
</div>
</span>
)
}
getInitialState: function() {
return {
"USD": "",
}
},

vue.js json object array as data

aye folks!
I'm currently learning to do stuff with vue.js. unfortunately i'm stuck atm. what i want to do is sending a request to my sample API which responds with a simple json formatted object.
I want to have this object as data in my component – but it doesn't seem to do that for whatever reason.
Ofc i tried to find a solution on stackoverflow but maybe i'm just blind or this is just like the code other people wrote. i even found this example on the official vue website but they're doing the same thing as i do .. i guess?
btw. When i run the fetchData() function in a separate file it does work and i can access the data i got from my API. no changes in the code .. just no vue around it. i'm really confused right now because i don't know what the mistake is.
code:
var $persons = [];
and inside my component:
data: {
persons: $persons,
currentPerson: '',
modal: false
},
created: function() {
this.fetchData()
},
methods: {
fetchData: function () {
var ajax = new XMLHttpRequest()
ajax.open('GET', 'http://api.unseen.ninja/data/index.php')
ajax.onload = function() {
$persons = JSON.parse(ajax.responseText)
console.log($persons[0].fname)
}
ajax.send()
}
},
[...]
link to the complete code
First, make sure that the onload callback is actually firing. If the GET request causes an error, onload won't fire. (In your case, the error is CORS-related, see this post suggested by #Pradeepb).
Second, you need to reference the persons data property directly, not the $persons array that you initialized persons with.
It would look like this (inside your fetchData method):
var self = this;
ajax.onload = function() {
self.persons = JSON.parse(ajax.responseText)
console.log($persons[0].fname)
}

Special JSON binding in WinJS ListView

I have problems binding this JSON to my list view.
http://pubapi.cryptsy.com/api.php?method=marketdatav2
No data is displayed.
Data.js
(function () {
"use strict";
var _list;
WinJS.xhr({ url: 'http://pubapi.cryptsy.com/api.php?method=marketdatav2' }).then(
function (response) {
var json = JSON.parse(response.responseText);
_list = new WinJS.Binding.List(json.return.markets);
},
function (error) {
//handle error
}
);
var publicMembers =
{
itemList: _list
};
WinJS.Namespace.define("DataExample", publicMembers);
})();
HTML:
<section aria-label="Main content" role="main">
<div id="listItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="listItem">
<div class="listItemTemplate-Detail">
<h4 data-win-bind="innerText: label"></h4>
</div>
</div>
</div>
<div id="listView" data-win-control="WinJS.UI.ListView" data-win-options="{itemDataSource : DataExample.itemList, itemTemplate: select('#listItemTemplate'), layout: {type: WinJS.UI.GridLayout}}"></div>
</section>
I feel that the API is not that well formed.
Isnt this part a bit odd?
"markets":{"ADT/XPM":{...}...}
There are three things going on in your code here.
First, a ListView must be bound to a WinJS.Binding.List's dataSource property, not the List directly. So in your HTML you can use itemDataSource: DataExample.itemList.dataSource, or you can make your DataExample.itemList dereference the dataSource at that level.
Second, you're also running into the issue that the declarative binding of itemDataSource in data-win-options is happening well before DataExample.itemList is even populated. At the point that the ListView gets instantiated, _list and therefore itemList will be undefined. This causes a problem with trying to dereference .dataSource.
The way around this is to make sure that DataExample.itemList is initialized with at least an empty instance of WinJS.Binding.List on startup. So putting this and the first bit together, we have this:
var _list = new WinJS.Binding.List();
var publicMembers =
{
itemList: _list.dataSource
};
With this, you can later replace _list with a different List instance, and the ListView will refresh itself.
This brings us to the third issue, populating the List with your HTTP response data. The WinJS.Binding.List takes an array in its constructor, not an object. You're passing the parsed JSON object straight from the HTTP request, which won't work.
Now if you have a WinJS.Binding.List instance already in _list as before, then you can just walk the object and add items directly to the List as follows:
var jm = json.return.markets;
for (var i in jm) {
_list.push(jm[i]);
}
Alternately, you could populate a separate array and then create a new List from that. In this case, however, you'll need to assign that new List.dataSource to the ListView in code:
var jm = json.return.markets;
var markets = [];
for (var i in jm) {
markets.push(jm[i]);
}
_list = new WinJS.Binding.List(markets);
var listview = document.getElementById("listView").winControl;
listview.itemDataSource = _list.dataSource;
Both ways will work (I tested them). Although the first solution is simpler and shorter, you'll need to make sure to clear out the List if you make another HTTP request and repopulate from that. With the second solution you just create a new List with each request and hand that to the ListView, which might work better depending on your particular needs.
Note also that in the second solution you can remove the itemDataSource option from the HTML altogether, and also eliminate the DataExample namespace and its variables because you'll assign the data source in code each time. Then you can also keep _list entirely local to the HTTP request.
Hope that helps. If you want to know more about ListView intricacies, see Chapter 7 of my free ebook from MSPress, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition.

Using controller-scoped data in a directive's jqlite-generated html

This question is similiar to them one asked in Mike's post Using ng-model within a directive.
I am writing a page which is small spreadsheet that displays calculated output based on user input fields. Using a directive, I'm making custom tags like this:
<wbcalc item="var1" title="Variable 1" type="input"></wbcalc>
<wbcalc item="var2" title="Variable 2" type="input"></wbcalc>
<wbcalc item="calc" title="Calculation" type="calc"></wbcalc>
The 'item' field references scoped data in my controller:
$scope.var1 = '5'; // pre-entered input
$scope.var2 = '10'; // pre-entered input
$scope.calc = function() {
return parseInt($scope.var1) + parseInt($scope.var2);
};
And the 'type' field is used in the directive's logic to know whether to treat the item as a string or a function.
Here's a fiddle for this: http://jsfiddle.net/gregsandell/PTkms/3/ I can get the output elements to work with the astonishing line of code:
html.append(angular.element("<span>")
.html(scope.$eval(attrs.item + "()"))
);
...and I'm using this to get my inputs connected to my scoped controller data (I got this from Mike's post:
var input = angular.element("<input>").attr("ng-model", attrs.item);
$compile(input)(scope);
html.append(input);
...while it does put the values in the fields, they aren't bound to the calculation, as you can see by changing inputs in my fiddle.
Is there a better and/or more intuitive way to link my controller-scoped data to the jqlite-generated html in my directive?
Take a look at this, I think you can simplify the process a fair bit.
http://jsfiddle.net/PTkms/4/
angular.module('calculator', []).directive('wbcalc', function($compile) {
return {
restrict: 'E',
template: '<div><div class="span2">{{title}}</div><input ng-model="item"></div>',
scope: {
title: '#',
item: '='
},
link: function(scope, element, attrs) {
// Don't need to do this.
}
}
});
function calcCtrl($scope) {
$scope.var1 = '5';
$scope.var2 = '10';
$scope.calc = function() {
// Yes, this is a very simple calculation which could
// have been handled in the html with {{0 + var1 + var2}}.
// But in the real app the calculations will be more
// complicated formulae that don't belong in the html.
return parseInt($scope.var1) + parseInt($scope.var2);
};
}
I know you said you like jQuery - but to make best use of Angular you need to think in an Angular way - use bindings, don't manipulate the DOM directly etc.
For this example, it would be helpful to read up on the isolated scope bindings used - '#' and '=', see:
http://docs.angularjs.org/guide/directive

Get a users youtube feed with knockout js?

Is this possible.. here's what I have atm, but my data object is just returning a load of jargon, what am I doing wrong? Am I doing anything.. right, for that matter?
I basically want to print out a list of a users videos (thumbnail and title, and make each one a clickable link to the video itself)
Thanks!
$(document).ready(function(){
$player.init();
})
var $player = (function(){
var player = {};
player.init = function(){
//init Youtube knockout
player.initYoutubeKnockout();
}
player.knockoutModel = {
videoData : ko.observableArray([]),
}
player.initYoutubeKnockout = function()
{
//load the Youtube json feed
$.ajax({
url: 'http://gdata.youtube.com/feeds/api/users/USERNAME/uploads?v=2&alt=json',
type: 'GET',
dataType: 'jsonp',
data: {
count: 5
},
success: function(data, textStatus, xhr) {
player.doYoutubeKnockout(data.item);
}
});
}
player.doYoutubeKnockout = function( data )
{
player.knockoutModel.videoData(data);
ko.applyBindings(player.knockoutModel, $('#youtube-feed')[0]);
console.log($(this));
}
return player;
})();
Frankly you weren't doing much at all.
The JSON data you get back from YouTube is not from data.item, it's in a completely different structure.
I'm assuming you wish to get 5 uploads from the user. The parameter name would be max-results, not count.
Probably the only thing you did fine was set up the url but that's about it.
You need to examine how the JSON returned looks like. Check the API reference for the structure of an atom feed. This is in XML but the corresponding JSON responses will have pretty much the same format with some minor differences. Examine the object by writing it to the console to verify you're getting the right properties.
Once you understand that, you need to use the correct query to get what you're expecting. Check out their API reference on their query parameters.
To help simplify your knockout code, I would strongly recommend you take the response you get back and map it to an object with simplified property names. For instance, to get the thumbnails for an entry, you would have to access the media$group.media$thumbnail array. It would be easier if you can just access it through thumbnail.
Also, if your elements you are binding to need to bind multiple values, it would help to map the values in such a way that your bindings are made easier. For instance, when using the attr binding, you'd set up a property for each of the attributes you want to add. Instead you could just group all the properties in an object and bind to that.
I wrote up a fiddle applying all that I said above to do as you had asked for. This should help give you an idea of what you can do and how to do it.
Demo