How to parse string to JSON in Ember Handlebar - ember.js

I am using Ember 2.16.0 and I can import string into Handlebar but when I try to access JSON property I am not getting expected result. Is there a helper that will convert a string into JSON inside Handlebar template?

Similar to the answer the OP posted, except I'd make the schemaJson property a computed property so that if schemasString changed, it would automatically update:
export default Component.extend({
schemasJson: computed('schemasString', function() {
return JSON.parse(this.schemasString);
}
});

Generally the intent of Handlebars is to keep as much logic out of the template as possible, so more typically in Ember you might perform the JSON parsing in a class, like a controller or component. That way by the time the data is sent to the template it’s already in the final data format you need. Is that an option for you?

I ended up using something like this to make data available to the component template. Not 100% sure if this is the best approach.
export default Component.extend({
init() {
this._super(...arguments);
this.set('schemasJson', Ember.$.parseJSON(this.schemasString));
}
});

Related

Fetch new data from API in jQuery plugin's callback

I am new to ember, so please treat me like a fool. What I'm trying to do first is to understand the concept.
In my application I heavily rely on few jQuery plugins they fetch new portion of data in their callbacks, that's how these plugins are designed, but I am not sure how can I trigger them to fetch a new portion of data from API passing to API updated query parameters after plugin has been rendered.
I have wrapped the plugin in a component, in component's template I send data to it as (I use emblem.js syntax here)
= plotly-chart chartData=model
In model I have
//app/models/data-points.js
import DS from 'ember-data';
export default DS.Model.extend({
// time: DS.attr(),
ch1: DS.attr(),
ch2: DS.attr(),
ch3: DS.attr(),
temperature: DS.attr(),
});
And then in component itself I fetch data
//app/components/plotly-chart.js
dataPoints: Ember.computed.map('chartData', function(item){
return item.getProperties('ch1', 'ch2', 'ch3', 'temperature');
}),
and make some manipulations with data, which isn't so important for the question itself.
Ah, and I have a route graph/ which later calls that component
//app/routes/graph.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
start_timestamp: {
refreshModel: true
},
end_timestamp: {
refreshModel: true
}
},
model(params) {
return this.get('store').query('data-point', params);
}
});
So as you see I have tried to fetch new properties via query params, finally it works great if I just update the url in browser, but now can I trigger new call to API and fetch new data and get this new data in a component itself?
Also I'm struggling to understand what role controllers play in all of these. It is mentioned that controllers will be deprecated soon, but still used here https://guides.emberjs.com/v2.10.0/routing/query-params/
My code seems to work without controllers, so this is really confusing.
Also I suspect maybe I should use services for what I'm trying to achieve, but not sure how.
Ember experts, could you please point me into a right direction? The most important thing is how to fetch new portion of data from API with updated query parameters (query parameters to API itself, not nessesarely the ember application, but I suspect in ember-data it is the same thing? or not %) %) %)).
UPDATE 1
Really I should use services for that, shouldn't I? Pass arguments into a service and query a store there. Would that be a correct pattern?
Then query parameters in url are not the same as querying the store and is an independent thing. Am I right?
but how can I trigger new call to API and fetch new data and get this new data in a component itself?
If you change your queryParam values in a controller using an action (combined with your current route setup) it will adjust your route and re-call your API, as the values are bound together to make this particular use case simple :-) You're about 98% of the way there ... :-)
Re controllers going away, they won't for a long time as the replacement hasn't been worked out yet. You could do some of this in a service if you want to, but there is no need as you are almost done.
Thanks, that make sense though. I just worried I'm doing it wrong.
By they way finally I found a way to access store from the controller Access store from component but:
1. I was unable to take out the data from that variable, probably it's me being stupid.
2. I double it's the right way to access store directly in a component and better to use services for that or rely on “Data Down Actions Up” (DDAU) paradigm?
Finally I was able to fetch new portion of a data calling a controller's action from within the controller, but then the next problem raised - the data was updated, but the JS code did not know about that because I feed the jQuery plugin with this data and it did not pick up changes automatically. I think I might be doing it a wrong way there %)
But finally I get it working by adding an Ember's observer to that variable and in observer calling a redraw function (for chart in this particular place).
#acorncom Thanks!

Ember.JS - 'TypeError: internalModel.getRecord is not a function' when trying to reverse a collection of records

--Using Ember Data 2.7.1--
I am trying to reverse the order of a collection of records without first turning them into an array using toArray(). This collection of objects comes from the promise returned by this.store.findAll('history-item').
I want to do this the ember way instead of making them plain javascript. I am getting a TypeError: internalModel.getRecord coming from record-array.js. For some reason when it is trying to do objectAtContent(), the content it is looking seems to not have a type. Through the stack trace I can see that the object I am dealing with is [Class], class being the history-item model. A few stack calls before the objectAtContent(), the object being dealt with switches from that history-item model to some other Class object that has no type attribute.
I am able to use Ember Inspector to see my data correctly, and if I just displayed the original collection of records on my template, it shows properly.
Has anyone run into this?
Some thoughts and considerations:
-Is there anything special about how findAll() works with its promise that doesn't allow for reversal since it is reloading in the background? I do want it to keep reloading live data.
-I am using ember-cli-mirage to mock my db and endpoints and I've follow the instructions to the letter I think. I am using an unconfigured JSONAPISerializer for mirage and and a unconfigured JSONAPIAdapter for ember. Could it have anything to do with metadata that is being sent from the back? Could it have something to with the models or records not being set up? Is there something special I have to do?
Route Segment that defines model and tries to reverse it:
[note: I know it may not be convention to prep the data (ordering) in the route but I just put it in here for ease of description. I usually do it outside in the controller or component]
model(){
return this.get('store').findAll('history-item').then(function(items){
return items.reverseObjects();
}).catch(failure);
History list model declaration:
export default DS.Model.extend({
question: DS.attr('string'),
answer: DS.attr('string')
});
Ember-Cli-Mirage config.js end points:
this.get('/history-items', (schema) => {
return schema.historyItems.all();
});
Ember-Cli-Mirage fixture for history-items:
export default [
{id: 1, question: "1is this working?", answer: "Of course!"}
}
Error:
TypeError: internalModel.getRecord coming from record-array.js
This issue also happens when I try to create a save a record. The save is successful but when the model gets reloaded (and tries to reverse), it fails with the same error. It doesn't matter if I the fixture or not.
Controller:
var newHistoryItem = this.store.createRecord('history-item', {
question: question,
answer: answer
});
newHistoryItem.save().then(success).catch(failure);
The result returned from store.findAll and store.query is an AdapterPopulatedRecordArray (live array), mutation methods like addObject,addObjects,removeObject,removeObjects,
unshiftObject,unshiftObjects,pushObject,pushObjects,reverseObjects,setObjects,shiftObject,clear,popObject,removeAt,removeObject,removeObjects,insertAt should not be used.
Have a look at corresponding discussion and
Proposed PR to throw error and suggestions to use toArray() to copy array instead of mutating.
I think using toArray is fine, no need to reinvent the wheel. Even Ember's enumerable/array methods are implemented using toArray under the hood.
I like keeping transforms on controllers/components, so Routes are only concerned with [URL -> data] logic. I think here I would keep the model hook returning the server data, and use a computed property on the controller:
import Ember from 'ember';
export default Ember.Controller.extend({
reversedItems: Ember.computed('model.[]', function() {
return this.get('model').toArray().reverse();
})
});
Twiddle: https://ember-twiddle.com/6527ef6d5f617449b8780148e7afe595?openFiles=controllers.application.js%2C
You could also use the reverse helper from Ember Composable Helpers and do it in the template:
{{#each (reverse model) as |item|}}
...
{{/each}}

Formatting query parameters that are arrays in Ember.Data

Let's say that I have an Ember.Data query I'd like to make:
this.store.find('items', {itemIds: [1,2,3]});
By default, Ember.Data creates a URL that looks like this:
items?itemIds%5B%5D=1&itemIds%5B%5D=2&itemIds%5B%5D=3
But the REST api I am connecting to wants it in this format:
items?itemIds=1&itemIds=2&itemIds=3
How do I achieve this adaptation?
Extend the RESTAdapter and override the ajax method and create the URL that you want to use based on the circumstances.
App.ItemsAdapter = DS.RESTAdapter.extend({
ajax: function(url, type, options){
if(myCircumstance){
var data = options.data;
delete options.data;
url = url + ......;
}
return this._super(url, type, options);
}
});
REST Adapter implementation: https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js
From what I see looking on the ember data code, you'd have to overwrite the RestAdapter's findQuery or ajax method, see http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_findMany (see https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js). Both are private, but the store expects the findQuery to be there (https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/system/store/finders.js#L137), so I wouldn't expect this behaviour to change soon.
If you use this for production, you'd better open a bug report to have this or something similar exposed as a public hook, as I cannot see one being there thus far.
#Kingpin2k's answer is the right direction, but the solution is even simpler. To create the query params, Ember Data simply yields the data object, wrapped in the options object, to the jQuery.ajax function.
Knowing that, we just need another query param serializer. By default, jQuery will serialize arrays the way TS described. You can change the way of serialization by overriding the $.param method. But luckily we don't even have too, because the $.param function has a second argument called traditional serialization. If set to true, the serialization will be like TS wanted*.
The jQuery.Ajax function also has the traditional flag to use the traditional style of param serialization. Combining this facts, you just need to set this flag yourself:
DS.RESTAdapter.extend({
ajax(url, type, options) {
if (options) {
options.traditional = true;
}
return this._super(...arguments);
}
});
P.S. If you use the JSONAPIAdapter, the trick is the same, because the JSONAPIAdapter extends the RESTAdapter.
*If you need another serialization, you need to override the $.param.

Accessing an Ember.Mixin within a custom handlebars helper

Is there an elegant way of using methods defined in an Ember.Mixin object within a custom handlebars helper?
For example, in my case I have a mixin, App.Format which contains a bunch of different methods for formatting numbers, dates, ect and I would like to make some of these methods accessible directly from templates via custom helpers (e.g. {{formatNumber 450324}}). The reason I don't just put the method implementation directly in the helper instead of the mixin is that I also need these methods to be available in controllers, views, ect. And in the interest of keeping my code DRY, I definitely do not want to have duplicate code in the mixin and the helpers.
Is there any canonical, "emberish" way to accomplish this, or am I just thinking about it in the completely wrong way altogether?
Edit: Here is a jsbin to better illustrate the problem:
http://emberjs.jsbin.com/wunug/1/edit?html,js,output (look at lines 33-37)
The method Mixin.apply makes the job.
Ember.Handlebars.helper('formatNumber', function(num, decimals) {
var format = App.Format.apply({});
return format.formatNumber(num, decimals || 2);
});
I am using Ember 2.12 and this is what I worked for me. Hopefully this helps somebody.
Create a new mixin named formatter:
../mixins/formatters.js
import Ember from 'ember';
export default Ember.Mixin.create({
shortDate: function(date) {
return `Date should be formatted: ${date}`;
}
});
Create a new helper that imports the above mixin and uses it.
../helpers/short-date.js
import Ember from 'ember';
import FormatDateMixin from '../mixins/formatters';
export default Ember.Helper.extend(FormatDateMixin, {
compute: function(params /*, hash*/) {
return this.shortDate(params);
}
});
In the template *.hbs file, use the helper as below:
{{short-date today}}

using an ember-rails handlebars template outside the context of an ember view

I have a rails app that is using the ember-rails gem.
There is a section of my site that is not on ember, but where it would be convenient to use one of the handlebar templates served via the asset pipeline. However, something seems to be going wrong. Specifically, my template is returned like so:
Ember.TEMPLATES["views/wanderlists/templates/gallery"] = Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { helpers = helpers || Ember.Handlebars.helpers; var self=this;
data.buffer.push("<h1>Gallery!</h2>\n"); });
However, if I try to use this template:
Ember.TEMPLATES["views/wanderlists/templates/gallery"]({})
TypeError: Cannot read property 'buffer' of undefined
Any idea why the generated template would be having trouble?
Any idea why the generated template would be having trouble?
You can't call handlebars templates compiled by the ember handlebars compiler as if they were normal handlebars templates. They expect a completely different set of arguments. Specifically, they expect to be passed (context, options) where options has a data.buffer that output will be written to. So for example, if you try:
Ember.TEMPLATES["views/wanderlists/templates/gallery"](this, {data: {buffer: 'NOT-A-BUFFER'}})
console should output TypeError: Object NOT-A-BUFFER has no method 'push'
There is a section of my site that is not on ember, but where it would be convenient to use one of the handlebar templates served via the asset pipeline.
OK. This is really easy to do, just not by accessing Ember.TEMPLATES directly. Instead use an Ember.View, and call appendTo() directly to render. For example:
App = Ember.Application.create({});
var view = Ember.View.create({
templateName: "views/wanderlists/templates/gallery",
name: "Bob"
});
view.appendTo("#message");
Working example here: http://jsfiddle.net/mgrassotti/VWmFq/1/
For more details see Ember Guides: Defining a view