I have an action that I want to trigger in my component hbs file if a conditional returns true. For example, If my component.js file looks like this:
export default Ember.Component.extend({
toggleMe: false,
actions: {
changeValue() {
return this.toggleProperty('toggleMe');
}
}
});
I want to call that changeValue action in my hbs file. This is the approach that I have tried in my component.hbs file:
{{#if model.property}}
{{action changeValue}}
{{/if}}
I'm getting an error
"Assertion Failed: Action passed is null or undefined"
First, you have a misspelled syntax in the component hbs. It should start with {{.
Second, your requirement can be done by using a Ember observer.
Created a live ember twiddle for your understanding.
Modify your component js file as,
import Ember from 'ember';
export default Ember.Component.extend({
toggleMe: false,
handleProperty : function()
{
this.send("changeValue");
}.observes('modeldata.property').on('didInsertElement'),
actions: {
changeValue() {
//console.log(this.get("toggleMe"));
this.toggleProperty('toggleMe');
//console.log(this.get("toggleMe"));
}
}
});
Also you may want to read about Ember computed properties and Ember observers.
You should not call an action in a template file.
There are computed properties for this.
Here is an example:
import { computed } from '#ember/object';
export default Ember.Component.extend({
toggleMe: false,
toggleComputedProperty: computed('toggleMe', function() {^
return this.toggleProperty('toggleMe');
}
}
});
Now you have the toggleComputedProperty availible to use in your template or in your logic.
As a rule of thumb: Never try to do logic/state changing in your template file. Use computed properties or other features for this.
The error is due to the misspelled syntax during your action call. You must use double quotes for your action name when invoking them.
{#if model.property}}
{{action "changeValue"}}
{{/if}}
I have also added an twiddle for your reference.
Related
I want to make enums work in in vue file.
I first define my enums in a js file
const Status= Object.freeze({
Normal: "normal",
Loading: "loading",
Error: "error",
Done: "done",
});
export default Status;
My main.vue file can't compile:
<template>
<div v-if="status == AudioCardStatus.Normal">
</template>
import Status from "./../enums/status"
Error is
Property or method "Status" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I have already looked at another similar SO question but the solution seems to be to use Typescript.
You should add that imported object to the data option in order to be available for the template :
import Status from "./../enums/status"
export default{
data(){
return{
status:Status
}
}
}
First, You import it as Status but use it as AudioCardStatus, Also you need to asign it to property in your data.
Second, you should import it inside a script tag.
Third, you don't need the extra ./ this enough ../enums/status
I have a controller in my ember 2.18 app and I want to make it listen to some event myEventName triggered somewhere in the app, to change the model properties.
According to the API docs for this version the way to catch an emitted event is like this:
import Evented from '#ember/object/evented';
import Controller from '#ember/controller';
// extend some object, like a Controller
const myExtendedObject = Controller.extend(Evented, {
// stuff ...
});
myExtendedObject.on('myEventName', function (args) {
console.log("catch importDone ok", args);
});
export default myExtendedObject;
the code compiles well with ember build dev,
but in the browser's JS console I get this error message:
Error: Assertion Failed: on called without valid event names error.js:40
I tried renaming event, but I cannot find why the event name is wrong.
I also tried to set three arguments to the on method, but it is still bugging with the event name.
What am I missing?
Edit:
I made an Ember twiddle showing that events are scoped to components.
I would like to catch them from anywhere;
like triggering an event from a Service and catch it in a Route.
https://ember-twiddle.com/ce22cc0a542e8e0997a1ad57a4a9688c?fullScreen=true
You can use it like this:
import { on } from '#ember/object/evented';
export default Controller.extend(Evented, {
init() {
this._super(...arguments);
this.trigger('greet');
},
myGreetImplementation: on('greet', function() {
console.log('hello');
})
});
Another example: Using a service to manage the user settings and subscribing to changes:
export default Service.extend(Evented, {
...
changeProfilePicture() {
...
this.trigger('profilePictureChanged', ... event parameters ...);
}
});
Then you could watch for such event anywhere using the on method:
export default Controller.extend({
userSettings: service(),
init() {
this._super(...arguments);
this.get('userSettings').on('profilePictureChanged', this, this.profilePictureChanged);
},
profilePictureChanged(... event parameters ...) {
...
}
});
There error you get is coming from here:
https://github.com/emberjs/ember.js/blob/6fc89cdf13124d88b8ae6adf99bb02a8c0cdf508/packages/ember-metal/lib/events.ts#L211
(You can just paste error messages in the github search or use the debugger to find out more about why something is failing)
Update: Extending your twiddle to have the Controller register on the service's event:
import Ember from 'ember';
import { inject as service } from '#ember/service';
import { on } from '#ember/object/evented';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
myService: service(),
init() {
this._super(...arguments);
this.get('myService').on('mycall', this, this.mycallHandler);
},
mycallHandler: on('mycall', function() {
console.log('here we are');
})
});
I currently have three steps in a form that I want to show sequentially, so I created three components - one for each step of the process.
My app.js file:
import LocationList from './components/LocationList.vue';
import ChooseTime from './components/ChooseTime.vue';
import ChooseMethod from './components/ChooseMethod.vue';
Vue.component('location-list', LocationList);
Vue.component('choose-time', ChooseTime);
Vue.component('choose-method', ChooseMethod);
let store = {
isVisible: {
steps: {
one: true,
two: false,
three: false,
}
}
};
new Vue({
el: '#app-order',
data: store,
router
});
Now, when my one and only route is called,
import VueRouter from 'vue-router';
let routes = [
{
path: '/order',
component: require('./views/Order.vue')
}
];
export default new VueRouter({
routes
});
all these components are being loaded properly. The issue is that when I try to v-show them one at a time:
Order.vue:
<template>
// ...
<location-list v-show="isVisible.steps.one"></location-list>
<choose-time v-show="isVisible.steps.two"></choose-time>
<choose-method v-show="isVisible.steps.three"></choose-method>
// ...
</template>
<script>
</script>
<style>
</style>
The error message I receive is:
[Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
But when I check within Vue's browser extension, isVisible is defined within the root element?
As you can see it is in the root-element, but not inside the Order view though.
Thanks for any help!
In Vue, child components do not have direct access to data defined in their parents. You have to pass the data down.
I think you would probably save yourself a little trouble if you just defined isVisible in Order.vue. However, if you want to leave it where it is, you need to pass it into the component.
One easy way to do that is to define isVisble as a property of Order.vue and then pass it through your router-view.
<router-view :is-visible="isVisible"></router-view>
There are other ways of passing props to routes that are defined in the router documentation.
The reason I say you would save your self some trouble defining isVisible in Order.vue is because whenever you want to change the values of your steps, you will need to do it at the root as you currently have it defined.
I have a controller A that sent an action with this.send('makeItHappen'), and I want to handle it in controller B. How do I do it?
JS:
// controllers/documents/datasets/controller-A
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
sendToDataCenter() {
this.send('makeItHappen'); // this throws an error
}
}
});
// controllers/controller-B
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
makeItHappen() {
console.log('It works!!');
}
}
});
In Controller B, it throws an error:
Uncaught Error: Nothing handled the action 'makeItHappen'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
Please, can anyone help? Thank you.
In general, each route will have one default controller if it's not defined.
In controller-A, this line of code this.send('makeItHappen'); will look for the makeItHappen method in actions hash of the datasheets,documents, application controller and its corresponding route if makeItHappen is defined anywhere then won't get this error.
To implement what you need,
Currently, in your route/controller hierarchy, there is no parent-child relationship between controller-A and controller-B. so you can just inject controller-B inside controller-A and call makeItHappen directly.
// controllers/documents/datasets/controller-A
import Ember from 'ember';
export default Ember.Controller.extend({
controllerB:Ember.inject.controller('controller-B');//this should be already instantiated ie,this corresponding route should be visited earlier otherwise you will get `unknown injection: controller:users' Error
actions: {
sendToDataCenter() {
this.get('controllerB').send('makeItHappen');
}
}
});
I have a form that transitions through several views. Currently each controller.js file has a long list of these Ember.computed.alias. How can I break that out into one file and import it into each controller?
Currently in each controller.js
entityEmail: Ember.computed.alias('controllers.checkout.entityEmail'),
entityDOB: Ember.computed.alias('controllers.checkout.entityDOB'),
entityPhone: Ember.computed.alias('controllers.checkout.entityPhone'),
entityAddress1: Ember.computed.alias('controllers.checkout.entityAddress1'),
entityAddress2: Ember.computed.alias('controllers.checkout.entityAddress2'),
entityCity: Ember.computed.alias('controllers.checkout.entityCity'),
I would like to pull all that out into a file so I can simply import some 1 liner in each controller.js
This is a classic use-case for Ember.Mixin.
You can extract all these computed props into a single mixin and extend every controller (that needs to have these props) with it.
Add the following mixin to your app
// app/mixins/entity-form.js
import Ember from 'ember';
const { Mixin, inject, computed: { alias } } = Ember;
export default Mixin.create({
checkout: inject.controller(),
entityEmail: alias('checkout.entityEmail'),
entityDOB: alias('checkout.entityDOB'),
entityPhone: alias('checkout.entityPhone'),
entityAddress1: alias('checkout.entityAddress1'),
entityAddress2: alias('checkout.entityAddress2'),
entityCity: alias('checkout.entityCity')
});
And then use it in a controller
// app/controllers/example.js
import EntityFormMixin from 'yourAppName/mixins/entity-form';
const { Controller } = Ember;
export default Controller.extend(EntityFormMixin, {
// rest of controller's props and functions
});
Note: Ember.inject API is available since Ember 1.10.0. In case you are using an older version you need to replace the inject line with: needs: ['checkout'] and prefix the aliases with "controllers." like you did in your example.