I've got and app that takes quote input (purity, weight, total), and it pushes to $scope.quote:
// Controller action //
$scope.quote.push({
total: ((($scope.karat * $scope.spot) * $scope.percentage) / 20) * $scope.estimatedWeight,
karat: $scope.karat * 100,
description: $scope.description,
actualWeight: $scope.actualWeight,
estimatedWeight: $scope.estimatedWeight,
percent: $scope.percentage * 100,
spot: $scope.spot
})
and
// Factory //
app.factory('quoteFactory', function() {
var quote = [];
var factory = {};
factory.getQuote = function () {
return quote;
};
return factory;
})
and the post/save upon quote completion
$scope.save = function() {
var now = $scope.getDate();
$scope.quote.push({
createdOn: $scope.getDate()
})
Restangular.all('quote').post($scope.quote).then(function(quote){
$location.path('#/scrap')
});
};
When trying to access the quote JSON for list or edit I can't access all the information needed because of the JSON structure.
{
"0": {
"total": 401.79040000000003,
"karat": 74,
"description": "Rings",
"actualWeight": 12,
"estimatedWeight": 11,
"percent": 80,
"spot": 1234
},
"1": {
"total": 560.7296,
"karat": 56.8,
"description": "Test",
"actualWeight": 22,
"estimatedWeight": 20,
"percent": 80,
"spot": 1234
},
"2": {
"total": 48.5625,
"karat": 92.5,
"description": "Testing",
"actualWeight": 80,
"estimatedWeight": 75,
"percent": 70,
"spot": 20
},
"3": {
"createdOn": "2013-11-26T21:26:42.253Z"
},
"_id": {
"$oid": "52951213e4b05f03172f14e7"
}
}
Each index represents a line item of the quote and the createdOn info. What I'm trying to figure out is if there is a way to be able to access all the line item information without having to call each individual index?
I've looked into some lodash/underscore, thought about restructuring the backend... Not really sure where to go from here.
Complete project code at github
Since there is not much details available as to how your backend works or is handling the data I am assuming the problem is with generating the JSON.
What I would do is change the way Angular posts the data to the server, for example:
the Save function:
$scope.save = function() {
var url = 'http:127.0.0.1:3000/url/you/send/your/data/to',
json = JSON.stringify($scope.quote);
$http({
method: 'POST',
url: url,
data: json,
headers: {
'Content-Type': 'application/json'
}
}).then(function(response) {
$location.path('#/scrap');
});
};
This will give you more control, and will make sure you control how the data gets turned into proper JSON, in this case using the .stringify() method of the JSON object.
I see from your comments that you are concerned with indexing; conventionally objects that sync with the server have a property id that represents their database id's NOT their client array index. This is a good way to keep the model data on the client a representation of the truth on your server side.
There are quite some libraries out there that help with this process client side, I would suggest - since you are using Angular already - reading into Angular Resource.
NOTE: if you want to support older browsers with the JSON.stringify() method, make sure you use Crockford's JSON2.js
Related
My API has two records: Car and Account. An Account may have many associated Car records.
I have REST routes for updating deleting creating a car record.
Normally, a GET route for call for all cars would look like this: /car
A route for a specific car would be /car/:id the :id being from the Car.
How would I set up a REST route to get call cars by account ID? Would I have to do something like account/:id/car?
You can do it hierarchical with URI path or use querystring. The URI RFC covers this I think.
/cars?account=123
/accounts/123/cars
As of REST you can return a hyperlink with the upper, something like
{
"operation": "ListCarsAssociatedWithAccount(accountId)",
"method": "POST",
"URI": "/accounts/{accountId}/cars",
"params": {"accountId": {"type":"AccountId"}}
}
The REST client should know only about how to call the ListCarsAssociatedWithAccount(accountId) and the URI and body templates can be filled with the params.
With this approach you can even describe the body of the POST request and the expected response if you want to and automate it further:
{
"operation": "ListCarsAssociatedWithAccount(accountId, x)",
"params": {
"accountId": {"type": "AccountId"},
"x": {"type": "Number"}
},
"method": "POST",
"URI": "/accounts/{accountId}/cars",
"body": {
"q": {
"w": {"param": "x"}
}
},
"returns": {
"type":"CarList",
}
}
ListCarsAssociatedWithAccount(123, 5)
->
POST "/accounts/123/cars"
{
"q": {
"w": 5
}
}
200 OK
{
operations: [...],
values: [
{"carId": 34, operations: [...]},
{"carId": 3, ...},
{"carId": 4, ...},
...
]
}
->
var cl = new CarList();
cl.support(o.operations);
var item1 = new Car("carId": o.values[0].carId);
item1.support(o.values[0].operations);
cl.add(item1)
...
return cl;
Something similar (but a lot more complex) is used in the Hydra framework. http://www.hydra-cg.com/
The endpoints are consider flexible and cheap to add (and modify while preserving backward compatibility). But normally something like this:
GET/UPDATE/DELETE: accounts/:id/cars/:carid
GET(search)/CREATE: accounts/:id/cars/
and you could also get the same information from:
GET/UPDATE/DELETE: cars/:carid
GET(search)/CREATE: cars/
Note that on the backend, you should be using the same logic for both endpoints though (reusing code here between the endpoints). The idea of the first set of endpoints is that you can drill into specific elements in a logical/hierarchical manner. So if you needed some sub element of cars for a specific account:
GET/UPDATE/DELETE: accounts/:id/cars/:carid/wheels
That allows maximal use/reuse of data without the need to constantly add filters based on account/car in the various endpoints.
NOTE: normally the endpoints would be pluralized so that you can do searches from the same endpoint, so `GET /accounts' could include search parameters to gather result sets.
I have the following API Data:
[
{
"symbol": "AAPL",
"name": "Apple Inc.",
"price": 144.98,
"changesPercentage": -1.22,
"change": -1.79,
"dayLow": 142.54,
"dayHigh": 146.96,
"yearHigh": 150,
"yearLow": 93.7125,
"marketCap": 2419368132608,
"priceAvg50": 138.45343,
"priceAvg200": 131.05212,
"volume": 113982837,
"avgVolume": 85057328,
"exchange": "NASDAQ",
"open": 144.81,
"previousClose": 146.77,
"eps": 4.449,
"pe": 32.587097,
"earningsAnnouncement": "2021-07-27T16:30:00.000+0000",
"sharesOutstanding": 16687599204,
"timestamp": 1627504655
}
]
And here is my code with a fake API code:
var $stocks = $('#stocks');
$.ajax({
type: 'GET',
url: 'https://financialmodelingprep.com/api/v3/quote/AAPL?apikey=c0dabd2d8a382584d2144bdefde830f2',
success: function (percent) {
$.each(percent, function (i, item) {
$stocks.append(percent.changesPercentage);
})
}
});
All I would like to do is get the changesPercentage and display it on my webpage with HTML and CSS. I will have various data from different companies, so a versatile solution would be very helpful.
You're trying to get changesPercentage from percent, which is the entire response array. So that property would be undefined.
Instead, it looks like you want to get that value from each element in the array. For that you would use the item parameter of the callback function in $.each:
$stocks.append(item.changesPercentage);
Alternatively, you could also use the i parameter as an indexer for the original array:
$stocks.append(percent[i].changesPercentage);
As an aside, this appears to be a result of using poor variable names which confuse yourself. percent is a misleading name for an array of objects. Something like results would be better, or perhaps the plural form of whatever noun actually describes what these API results are.
Names are important in code. They carry information about what that thing is and how it can be used.
I have been trying to figure out how to do 2fa with webauthn and I have the registration part working. The details are really poorly documented, especially all of the encoding payloads in javascript. I am able to register a device to a user, but I am not able to authenticate with that device. For reference, I'm using these resources:
https://github.com/cedarcode/webauthn-ruby
https://www.passwordless.dev/js/mfa.register.js
And specifically, for authentication, I'm trying to mimic this js functionality:
https://www.passwordless.dev/js/mfa.register.js
In my user model, I have a webauthn_id, and several u2f devices, each of which has a public_key and a webauthn_id.
In my Rails app, I do:
options = WebAuthn::Credential.options_for_get(allow: :webauthn_id)
session[:webauthn_options] = options
In my javascript, I try to mimic the js file above and I do (this is embedded ruby):
options = <%= raw #options.as_json.to_json %>
options.challenge = WebAuthnHelpers.coerceToArrayBuffer(options.challenge);
options.allowCredentials = options.allowCredentials.map((c) => {
c.id = WebAuthnHelpers.coerceToArrayBuffer(c.id);
return c;
});
navigator.credentials.get({ "publicKey": options }).then(function (credentialInfoAssertion)
{
// send assertion response back to the server
// to proceed with the control of the credential
alert('here');
}).catch(function (err)
{
debugger
console.error(err); /* THIS IS WHERE THE ERROR IS THROWN */
});
The problem is, I cannot get past navigator.credentials.get, I get this error in the javascript console:
TypeError: CredentialsContainer.get: Element of 'allowCredentials' member of PublicKeyCredentialRequestOptions can't be converted to a dictionary
options at the time navigator.credentials.get is called looks like this:
I've tried every which way to convert my db-stored user and device variables into javascript properly encoded and parsed variables but cannot seem to get it to work. Anything obvious about what I'm doing wrong?
Thanks for any help,
Kevin
UPDATE -
Adding options json generated by the server:
"{\"challenge\":\"SSDYi4I7kRWt5wc5KjuAvgJ3dsQhjy7IPOJ0hvR5tMg\",\"timeout\":120000,\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"OUckfxGNLGGASUfGiX-1_8FzehlXh3fKvJ98tm59mVukJkKb_CGk1avnorL4sQQASVO9aGqmgn01jf629Jt0Z0SmBpDKd9sL1T5Z9loDrkLTTCIzrIRqhwPC6yrkfBFi\"},{\"type\":\"public-key\",\"id\":\"Fj5T-WPmEMTz139mY-Vo0DTfsNmjwy_mUx6jn5rUEPx-LsY51mxNYidprJ39_cHeAOieg-W12X47iJm42K0Tsixj4_Fl6KjdgYoxQtEYsNF-LPhwtoKwYsy1hZgVojp3\"}]}"
This is an example of the serialised JSON data returned by our implementation:
{
"challenge": "MQ1S8MBSU0M2kiJqJD8wnQ",
"timeout": 60000,
"rpId": "identity.acme.com",
"allowCredentials": [
{
"type": "public-key",
"id": "k5Ti8dLdko1GANsBT-_NZ5L_-8j_8TnoNOYe8mUcs4o",
"transports": [
"internal"
]
},
{
"type": "public-key",
"id": "LAqkKEO99XPCQ7fsUa3stz7K76A_mE5dQwX4S3QS6jdbI9ttSn9Hu37BA31JUGXqgyhTtskL5obe6uZxitbIfA",
"transports": [
"usb"
]
},
{
"type": "public-key",
"id": "nbN3S08Wv2GElRsW9AmK70J1INEpwIywQcOl6rp_DWLm4mcQiH96TmAXSrZRHciZBENVB9rJdE94HPHbeVjtZg",
"transports": [
"usb"
]
}
],
"userVerification": "discouraged",
"extensions": {
"txAuthSimple": "Sign in to your ACME account",
"exts": true,
"uvi": true,
"loc": true,
"uvm": true
}
}
This is parsed to an object and the code used to coerce those base64url encoded values is:
credentialRequestOptions.challenge = WebAuthnHelpers.coerceToArrayBuffer(credentialRequestOptions.challenge);
credentialRequestOptions.allowCredentials = credentialRequestOptions.allowCredentials.map((c) => {
c.id = WebAuthnHelpers.coerceToArrayBuffer(c.id);
return c;
});
Hope that helps. The JSON data is retreived via a fetch() call and the byte[] fields are encoded as base64url on the serverside.
I'm currently trying to put something together with ember + emberdata + router + asp.net web api. Most of it seem to work, however I stuck in an error message I get when ember-data tries to findAll through the adapter for my models.
In my backend I have a model like this (C#):
public class Genre {
[Key]
public int Id { get; set; }
[Required]
[StringLength(50, MinimumLength=3)]
public string Name { get; set; }
}
Which in my app I represent it like this using ember-data:
App.Genre = DS.Model.extend({
id: DS.attr("number"),
name: DS.attr("string")
}).reopenClass({
url: 'api/genre'
});
I have also a Store defined in my App using the RESTAdapter like so:
App.store = DS.Store.create({
revision: 4,
adapter: DS.RESTAdapter.create({
bulkCommit: false
})
});
And the store is used in my controller as below:
App.GenreController = Ember.ArrayController.extend({
content: App.store.findAll(App.Genre),
selectedGenre: null
});
The router is defined as
App.router = Em.Router.create({
enableLogging: true,
location: 'hash',
root: Ember.Route.extend({
//...
genre: Em.Route.extend({
route: '/genre',
index: Ember.Route.extend({
connectOutlets: function (router, context) {
router.get('applicationController').connectOutlet('genre');
}
})
}),
//...
})
})
When I run my application, I get the following message for every object that has this same structure:
Uncaught Error: assertion failed: Your server returned a hash with the
key 0 but you have no mappings
For reference, here's the json the service is returning:
[
{
"id": 1,
"name": "Action"
},
{
"id": 2,
"name": "Drama"
},
{
"id": 3,
"name": "Comedy"
},
{
"id": 4,
"name": "Romance"
}
]
I cannot tell exactly what the problem is and since the assertion is mentioning that I need mapping, I'd like to know:
What this mapping is and how to use it.
Since the returned json is an array, should I be using a different type of controller in my app ,or is there anything I should know about when working with this type of json in ember-data? or should I change the JsonFormatter options in the server?
Any help is welcome.
I can definitely add more information if you feel this isn't enough to understand the problem.
EDIT: I've changed a few things in my backend and now my findAll() equivalent action in the server serializes the the output as the following json:
{
"genres": [
{ "id": 1, "name": "Action" },
{ "id": 2, "name": "Drama" },
{ "id": 3, "name": "Comedy" },
{ "id": 4, "name": "Romance" }
]
}
But I still can't get it to populate my models in the client and my error message has changed to this:
Uncaught Error: assertion failed: Your server returned a hash with the
key genres but you have no mappings
Not sure what else I might be doing wrong.
The method that throws this exception is sideload and checks for the mappings like this:
sideload: function (store, type, json, root) {
var sideloadedType, mappings, loaded = {};
loaded[root] = true;
for (var prop in json) {
if (!json.hasOwnProperty(prop)) { continue; }
if (prop === root) { continue; }
sideloadedType = type.typeForAssociation(prop);
if (!sideloadedType) {
mappings = get(this, 'mappings');
Ember.assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
//...
This call sideloadedType = type.typeForAssociation(prop); returns undefined and then I get the error message. The method typeForAssociation() checks for the for 'associationsByName' key which returns an empty Ember.Map.
Still no solution for this at the moment.
By the way...
My action is now like this:
// GET api/genres
public object GetGenres() {
return new { genres = context.Genres.AsQueryable() };
}
// GET api/genres
//[Queryable]
//public IQueryable<Genre> GetGenres()
//{
// return context.Genres.AsQueryable();
//}
I had to remove the original implementation which gets serialized by json.NET as I could not find config options to produce a json output as Ember-Data expects ( as in {resource_name : [json, json,...]}). Side effect of this is that I've lost built-in OData support, but I'd like to keep it. Does anyone know how could I configure it to produce different json for a collection?
The mapping can be defined in the DS.RESTAdapter. I think you could try to define something like this:
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.create({
bulkCommit: true,
mappings: {
genres: App.Genre
},
// you can also define plurals, if there is a unregular plural
// usually, RESTAdapter simply add a 's' for plurals.
// for example at work we have to define something like this
plurals: {
business_process: 'business_processes'
//else it tries to fetch business_processs
}
}),
revision: 4
});
Hope this resolves your problem.
Update:
At this time, this is not well documented, I don't remember if we found it by ourself reading the code, or perhaps Tom Dale pointed on it.
Anyway, here is the point for plurals
For the mappings, I think we were driven by the same error as you, and either we tried, either Tom teached us about this.
The RESTAdapter expects the returned JSON to be of the form:
{
"genres": [{
"id": 1,
"name": "action"
},{
"id": 2,
"name": "Drama"
}]
}
The tests are a good source of documentation, see https://github.com/emberjs/data/blob/master/packages/ember-data/tests/unit/rest_adapter_test.js#L315-329
I'm using Ember Data rev. 11 and it seems that the plurals config in DS.RESTAdapter.create never works. I looked into the codes and found a solution as following:
App.Adapter = DS.RESTAdapter.extend({
bulkCommit: false
})
App.Adapter.configure('plurals', {
series: 'series'
})
suppose I have a config javascript file:
window.Config = {};
Config.UI = {
"Area": {},
"Layer": {},
"Sprites": {},
"Audio": {}
};
Config.UI.Area = {
"prop": {
"uuid": {
"_type": "string",
},
"frame": {
"_type": "rect",
"_value": {
"x": "0",
},
"_aka": "frame"
},
"zIndex": {
"_type": "string",
}
},
then I want use $.ajax to read this file:
$.ajax({
url:'js/config.js',
success:function (data, textStatus) {
console.log(data);
}
})
the question is how can I get some key's value in the config,by use the data $.ajax return?
like the "Config.UI" or the 'uuid' in ui.area.prop?Or can I convert them to json?
Rather than use AJAX, why not just insert a script?
var script = $('<script>');
script.attr('type', 'text/javascript');
script.attr('src', 'js/config.js');
script.bind('load', function() {
// use window.Config
});
script.appendTo('head');
icktoofay has a good suggestion, and the issue with the jQuery.ajax call looks to be a missing dataType: 'script' option which will evaluate the response and should give you object access. You might want to look into jQuery.getscript() as well.
I find it very useful and powerful to store data on the server as javascript objects and read them using Ajax. And it is very easy to do. Let me give you an example from an educational application I have written.
This is an example table of contents file (l1contents.js) that I would store on the server:
{
title : "Lesson 1",
topics : [
{name : "Topic 1", file : "l1t1data.js" },
{name : "Topic 2", file : "l1t2data.js" },
]
}
This is the javascript code I use to process the file:
$.ajax({
url : contentsFileName, // would be set to 'l1contents.js'
dataType : 'text', // yes this is correct, I want jquery to think this is text
cache : false,
success: function(data) {
var contentsObj = eval('(' + data + ')');
var lessonTitle = contentsObj.title;
for (var i = 0; i < contentsObj.topics.length; i++) {
var topic = contentsObj.topics [i];
// process topic.name and topic.file here
}
}
});
Obviously, this is simplified, but hopefully you get the idea. I simply use eval to set the object. And note that I don't even need any javascript code defining the structure of contentsObj. (I, of course, do have extensive comments defining the structure of my objects, but they are simply comments, not code.)
if your json file contains json data than you can use parseJSON() , toJSON() method.
and another solution is use eval(), this conver json data to javascript object so you can easly get a value by giving key.