What's this.$eval in Vue doing? - javascript

I'm migrating an old script which I didn't write from version 0.11 to 2.5.4 and I'm getting a warning saying :
Replace this.$eval('reportData | reportFilter false') with a solution using normal JavaScript
Line 327: assets/js/custom-reports.js
Reason: vm.$eval has been removed, as it has no real use
More info: http://vuejs.org/guide/migration.html#vm-eval
I can see in the code its setting these values:
this.$set('reportData[' + key + '].selected', !selectAll);
And using them here:
var data = this.$eval('reportData | reportFilter false');
Has anyone got any idea whats going on here and how I can rewrite this to work in a new way?

That looks like it is filtering reportData with a filter called reportFilter which I'm guessing is being passed false as a parameter. Filters in Vue 2 don't work the same way, so you will need to move reportFilter to a method, however, if this is being used across multiple components you will probably want to create a mixin, so:
// Filters mixin
const Filters = {
methods:{
reportFilter(data, flag){
// reportFilter method
}
}
}
Then in any components where reportFilter is being used you will want to use the mixin:
new Vue({
// ...
mixins: [Filters],
// ...
})
You can then change your $eval code to:
var data = this.reportFilter(reportData, false);
Here's a JSFiddle to give you an idea of how that works: https://jsfiddle.net/4b8vqccs/

Related

Ember: Getting model data in the controller

I am new to ember and I am building a DnD character sheet to learn and practice. Right now I am having a lot of trouble getting access to model data in a controller. After hours and hours of reading related posts it is just not clicking and I think I am misunderstanding what I am passing to the controller.
Basically what I am trying to do is grab data from a model so I can do calculations on them and then display the results of those calculations on the page.
Here is my transforms/router.js:
Router.map(function() {
this.route('characters', { path: '/'})
this.route('new', {path: 'new'});
this.route('view', {path: '/:character_id'});
});
So here I am trying to pull up the view page which has the URL of the character id. Next here is my route for the view page:
export default Route.extend({
character: function () {
return this.store.findRecord('character', id)
}
});
So this is finding the record of the character with the id I pass. My link looks like this and is coming from a component:
<h5 class="card-title">{{#link-to 'view' id}}{{name}}{{/link-to}}</h5>
Now I have my controller for the view page, which looks like this:
init(id){
let character = this.get('character')
}
When I try to log character, it is still undefined. When looking ember information in dev tools it seems the page is getting the information from the model after I refresh the page, but I just can't seem to be figure out how to grab that info in the controller itself to manipulate it.
I've been trying to figure this out for quite a while now, and its getting pretty frustrating. I currently have a work around where I do the calculations beforehand and just store all the calculated results in the model as well, but while I am learning I would like to understand how this works. Thanks is advance.
Edit: As pointed out in comments below I was missing let when defining character.
Your model hook seems wrong. You're using id but never define it. Probably what you want is more like this:
character(params) {
return this.store.findRecord('character', params.character_id);
}
Next your init hook makes no sense:
init(id){
let character = this.get('character')
}
First there is no id passed to the init hook. Second you're missing this._super(...arguments) which should always be called when you override init.
Last is that your controller is first created and later populated with the model. Also the model is populated as model property, not character.
So you could place this in your routes template and it will work:
This is character {{model.id}}
Or if you want to change something before you pass it to the template you should use a computed property in your controller:
foo: computed('model.id', function() {
return this.get('model.id') + ' is the id of the character';
}),
However for this code to run you need to use. The easiest way to use it is to put it into your template:
{{foo}}

Return vue instance from vue.js filter (Vue.js 2)

I'm using filter function for internationalization like this:
<div>{{ "hello" | message }}<div>
message is a filter function that depends on global Vue.config.lang variable.
It works great, but if I change Vue.config.lang, message doesn't rerender.
I wanted to make message rerender anytime Vue.config.lang changes, so I changed my filter function from
message => locale_messages[Vue.config.lang][message]
to
message => new Vue({
template: '{{ message }}',
computed: {
message() { return locale_messages[Vue.config.lang][message]; }
}
})
But it doesn't work. Getting this error:
Uncaught TypeError: Converting circular structure to JSON
at Object.stringify (<anonymous>)
....
Is there anything I can do to make it work? I'm new to Vue.js and can't find a working solution.
Like Evan says, Filters should be pure, so thay can't use a global variable as key to get values from externals arrays. Because of side effects.
So, there is three solutions at your problem that comes in my mind :
Replace filters by methods.
Use vue-i18-n, a simple and powerful module for translation
Use a store system (vuex) wich provides you getters, and helps you manage a global state.
Personnaly I love to use vuex and vue-i18-n together.
In that way I can centralize my data and the language in use. I can also serve specific data in several languages using the store, and let vue-i18-n cares about all the strings in the project.
New to Vue myself, so not quite sure how global variables work, but you can definitely pass params to a custom filter - even a Vue reference. You can do this:
<!-- this = a reference to the vue instance -->
<span class="display-3">{{value|FormatValue(this)}}</span>
[...]
props: ["aIsPercentNeeded"],
[...]
Vue.filter("FormatValue", function (aValue, aVueInstance)
{
if (!aVueInstance["aIsPercentNeeded"])
{
return aValue;
}
return aValue + "%";
});

Angular filtering data in javascript is not displaying results and push of data causes error plunker provided

Ok, seems that I was having too many issues with the way in which my Angular site is setup, so I put it in a plunker as then anyone can see it.
Original question: Angular retrieve specific data into $scope variable is not working
Plunker
http://plnkr.co/edit/NsE29zjraQp9UeklJBiI?p=preview
My issues are
1. i'm not understanding how to use app.filter
2. Issue with app name
3. forEach with push inside $http.get throws error not defined
The plunker Index.html has the template code loop , app.module.js is root and the device.controller.js is where I'm using controller with $http.get call using json file to fake it.
I was attempting to use the other persons answer so this code
$scope.devices = result.data.Devices; // gives all data ...
Filter I was wondering if this with work
<div ng-repeat="device in devices">
{{ device.DeviceStatus }}
</div>
Then this code I'm not sure it in the correct "place"
seems like i'm not understanding "app"
app.filter('deviceStatus', function () {
return function (status_id) {
var statuses = ['Old Device', 'New Device', 'Activated', 'Unactivated'];
return statuses[status_id];
};
});
Example filter:
<td>{{device.DeviceId | deviceStatus}}</td>
Let me try to understand your issue.
As per your question, it seems that you have problems understanding what app is and how to use filter.
This is the working version of your plunkr. Check this url
app in your project is the ng-app directive. The ng-app directive tells AngularJS that the element is the "owner" of an AngularJS application.
For understanding filter functionality. check the below example.
You were trying to push into $scope.statuses which is not defined yet. So first define $scope.statuses to be an empty array i.e `$scope.statuses = [];
Hope this works for you!`
// To declare a filter we pass in two parameters to app.filter
// The first parameter is the name of the filter
// second is a function that will return another function that does the actual work of the filter
//here app is the module name of your project
app.filter('myFilter', function() {
// In the return function, we must pass in a single parameter which will be the data we will work on.
// We have the ability to support multiple other parameters that can be passed into the filter optionally
return function(input, optional1, optional2) {
var output;
// Do filter work here
return output;
}
});

How to see all available variables in handlebars template

I'm working on my first Ember.js app and am having some trouble connecting all the dots. It would be really helpful if I could just see all the variables available within a given handlebars template.
There is a related question, but you have to know the variable that is in scope to use it:
How do I add console.log() JavaScript logic inside of a Handlebars template?
How can I output all the variables?
a good option is to debug the value of 'this' in a template using the Handlebars helpers:
1.
{{#each}}
{{log this}}
{{/each}}
or,
2. similar to #watson suggested
{{#each}}
{{debugger}}
{{/each}}
and then drill in to the Local Scope Variables for 'this' in the Dev Tools
or alternatively, 3. you could log things directly from inside your Controller init method, such as:
App.UsersController = Ember.ArrayController.extend({
init: function() {
console.log(this);
console.log(this.getProperties('.'));
}
});
Make sure you try out Firebug - you'll get a different perspective on things, which I found helpful. But don't abandon chrome completely; you will need the Ember Inspector at some point.
I'm using the same debugging helper everyone recommends, and this is how Chrome displays it:
When I expand the same object in firebug, I get the following info, including the variables I was looking for (sources[]) and some other useful properties I hadn't seen in Chrome.
I created Barhandles a few years ago. It will use the Handlebars parser to produce the AST, and then extract variable references from it. The extractSchema method will — well — extract a schema. That schema is not based on JSON Schema or Joi or anything. It's a homegrown format that captures most of the things you could possibly extract from Handlebars template.
So, this barhandlers.extractSchema('{{foo.bar}}') produces:
{
"foo": {
"_type": "object",
"_optional": false,
"bar": {
"_type": "any",
"_optional": false
}
}
}
It will take into account that an {{#if expr}} will automatically make nested references optional. It correctly handles scope changes based on {{#with expr}} constructs, and it allows you to add support for your own custom directives as well.
http://nxt.flotsam.nl/barhandles
https://medium.com/east-pole/advanced-barhandles-4a7e64c1bc0d
We used it to do validation on the data structures that we passed into the template, and it was working pretty well for that purpose.
If you really need to dump the variables in your template, you can explore the template AST and output the content of the relevant nodes (see the compiler sources). This is not an easy task because you have to find your way through trials and errors, and the code is quite low-level and there are not so many comments.
It seems Handlerbars doesn't have a shortcut for what you're asking, so the steps would be:
precompile a template (see the command line source, I think the function is called handlebars.precompile())
explore the AST
You can do this by leveraging Handlebars.parseWithoutProcessing which takes the input template string. If you use TypeScript, that returns a specific type hbs.AST.Program. You can filter for only the moustache statements, and then iterate through these statements to get the variable names.
This method also supports Handlebars helpers, so you can get the key for that, but because of this, this function is a bit more complex as you'd need to check different properties on the moustache statement:
/**
* Getting the variables from the Handlebars template.
* Supports helpers too.
* #param input
*/
const getHandlebarsVariables = (input = '') => {
const ast = Handlebars.parseWithoutProcessing(input);
return ast.body
.filter(({ type }) => type === 'MustacheStatement')
.map((statement) => statement.params[0]?.original || statement.path?.original);
};
Here's the TypeScript version, which is a bit involved due to the conditional properties, but can help explain the types a bit more:
/**
* Getting the variables from the Handlebars template.
* Supports helpers too.
* #param input
*/
const getHandlebarsVariables = (input: string): string[] => {
const ast: hbs.AST.Program = Handlebars.parseWithoutProcessing(input);
return ast.body.filter(({ type }: hbs.AST.Statement) => (
type === 'MustacheStatement'
))
.map((statement: hbs.AST.Statement) => {
const moustacheStatement: hbs.AST.MustacheStatement = statement as hbs.AST.MustacheStatement;
const paramsExpressionList = moustacheStatement.params as hbs.AST.PathExpression[];
const pathExpression = moustacheStatement.path as hbs.AST.PathExpression;
return paramsExpressionList[0]?.original || pathExpression.original;
});
};
I've made a Codepen that illustrates this. Essentially, given the following template:
Hello, {{first_name}}! The lottery prize is {{formatCurrency prize_amount}}! Good luck!
It will use window.prompt to ask the user for their name and the prize amount. The example also implements a helper formatCurrency. You can see it here: https://codepen.io/tinacious/pen/GRqYWJE
The sample Ember app you mention defines its EmberObjects right in its app.js. So basically, what's available on the objects are the properties that are defined onto them there. (e.g. subreddit has a title, etc).
If you want a globally available way to dump an object's property schema out to the console, one approach would be to create a "debug" helper that walks the members of the passed-in contexts and writes them out. Something like:
Handlebars.registerHelper('debug', function (emberObject) {
if (emberObject && emberObject.contexts) {
var out = '';
for (var context in emberObject.contexts) {
for (var prop in context) {
out += prop + ": " + context[prop] + "\n"
}
}
if (console && console.log) {
console.log("Debug\n----------------\n" + out);
}
}
});
Then call it on whatever you want to inspect:
<div>Some markup</div>{{debug}}<div>Blah</div>
This will use whatever EmberObject is in scope, so pop it inside of an {{#each}} if you want to inspect the list elements, as opposed to the object with that list.
The variables available within a template are only constrained by the model you are using to render the template.
You should set a breakpoint in your app where you render the template and see what is in your model at that point, which will should you what you have available to put in your template.

Can you bind a simple javascript array to your ember.js template?

I'm using ember.js RC1 + ember-data rev 11 (but I also need some plain ajax for configuration like models). I want to loop over a simple objects list and display the records (note -here I create just a basic array)
The content I have bound has the following custom find method defined
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
records: [],
all: function() {
return this.records;
},
find: function() {
var self = this;
$.getJSON('/api/foo/', function(response) {
response.forEach(function(data) {
//say I want to kill everything in the array here for some strange reason...
self.records = [];
//the template still shows the record ... not an empty list ?
}, this);
});
return this.records;
}
});
My other model uses this directly
App.Related = DS.Model.extend({
listings: function() {
return App.Foo.find();
}.property()
});
Now inside my template
{{#each foo in related.listings}}
{{foo.name}}<br />
{{/each}}
The list loads up with whatever I put in the array by default (say I add a simple object using createRecord like so)
add: function(record) {
this.records.addObject(App.Foo.createRecord(record));
},
and when the template is rendered I see anything listed here... but as I put in the comments above, if I decide to remove records or null out the list that is bound it doesn't seem to reflect this in any way.
Is it possible to bind a simple array as I have and yet remove items from it using something basic such as splice? or even a drastic self.records = []; ?
self.records.splice(i, 1);
Even when I query the client manually after the splice or empty work it returns 0
console.log(App.Foo.all().get('length'));
Initially I see records, but then I see they are gone (yet the html doesn't change)
I understood your question this way, that the following remark is the point your are struggling with:
response.forEach(function(data) {
//say I want to kill everything in the array here for some strange reason...
self.records = [];
//the template still shows the record ... not an empty list ?
}, this);
You are wondering, why your template is showing no empty list? It's because you did not tell Ember when to update the template. You can tell Ember this way:
App.Related = DS.Model.extend({
listings: function() {
return App.Foo.find();
}.property("App.Foo.records.#each")
});
Now Ember knows, whenever something is added or removed from your array, it should update the listings property of your model. And therefore it knows that your view needs rerendering.
One additional remark to the orignal question regarding "simple javascript arrays". When you use Ember, you actually do not instantiate simple js arrays. When you declare:
var a = []; // is the same as -> var a = Ember.A();
Ember does some magic and wraps in an enhanced ember version of an array (Ember.NativeArray), which enables you to use such property dependency declarations mentioned above. This enables Ember to use ArrayObservers on those arrays, although they may feel like a plain JS Array.
You need to use the set method when you modify properties and get when you return them, or else Ember won't be able to do its magic and update the template.
In your case, there is an additional problem, which is that in find(), you return a reference to records before your asynchronous getJSON call replaces it with a new empty array. The calling method will never see the new array of records. You probably want to use clear() instead.
Your model should look something like this:
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
records: [],
all: function() {
// can't use 'this.get(...)' within a class method
return Ember.get(this, 'records');
},
findAll: function() {
var records = Ember.get(this, 'records');
$.getJSON('/api/foo/', function(response) {
records.clear();
// in this case my json has a 'foos' root
response.foos.forEach(function(json) {
this.add(json);
}, this);
}, this);
// this gets updated asynchronously
return records;
},
add: function(json) {
// in order to access the store within a
// class method, I cached it at App.store
var store = App.get('store');
store.load(App.Foo, json);
var records = Ember.get(this, 'records');
records.addObject(App.Foo.find(json.id));
}
});
Note that the addObject() method respects observers, so the template updates as expected. removeObject() is the corresponding binding-aware method to remove an element.
Here's a working jsfiddle.

Categories