How trigger a function inside component whenever arguments change in Emnber octane - ember.js

so i have a parent in controller like this
import Controller from '#ember/controller';
export default class IndexController extends Controller {
#service firebaseApp;
#service fastboot;
#tracked user =false;
async init(){
super.init(...arguments);
if (!this.fastboot.isFastBoot){
const auth = await this.firebaseApp.auth();
auth.onAuthStateChanged((user)=>{
if(user){
this.user = true
} else {
this.user = false
}
})
}
}
then call a component loadData like this
<LoadData #user={{this.user}}/>
the question is how to execute a function inside component loadData when #user change?

Triggering an action when an argument changes is not that well supported by Ember Octane primitives yet. A common approach is to use either #ember/render-modifiers or ember-render-helpers.
#ember/render-modifiers provide a {{did-update}} modifier.
ember-render-helpers provide a {{did-update}} helper. Both the modifier and the helper except a function as first position argument. That function is executed whenever one of the other positional arguments changes.
{{did-update}} modifier is helpful when the function executed needs access to a DOM element. It sets the DOM element, which it is attached to, as an argument on the function when called.
{{did-update}} helper is helpful when the function executed does not need access to a DOM element.
{{! A Glimmer template }}
{{! did-update helper }}
{{did-update this.loadData #user}}
{{! did-update modifier }}
<div class="loading" {{did-update this.showLoadingSpinner #user}} />
The main use case for {{did-update}} modifier is to ease the migration from classical #ember/component to #glimmer/component. In nearly all cases a specific modifier containing the logic, which should be executed, itself is a better solution. It provides better reusability, can be tested in isolation and has clear boundaries to the components in which it is used.
The main use case for {{did-update}} helper is to fill a gap in current Ember Octance programming model. Ember Octance provides an awesome developer experience for derived state thanks to autotracking and native getters. It provides a great experience to modify a DOM element depending on a value. But it does not have great primitives yet to trigger actions like data loading when an argument changes.
The community current experiments with different approaches to fill that gap. It seems to settle on #use decorator and resources as proposed by Chris Garret (pzuraq) in an RFC and in a recent blog post. It's available for experiments as part of ember-could-get-used-to-this package.
The {{did-update}} helper provided by ember-render-helpers is the most established solution to fill that gap until something like resources settle in Ember.

Related

Ember 3+ passing action from component to controller not working

We have recently shifted from ember 2+ to Ember 3.18.0 and i am struggling to call the controller function from a component. In previous version we were using sendAction to bubble the action but now as sendAction is depreciated and closures are being used i am not able to make it correctly.
Below is my code
Controller.hbs
{{generic-err-modal err=receivedErr showDialog= this.showErrorModal onSave=(action "closePromptDialog")}}
Controller.js
#action
closePromptDialog(){
this.set("showErrorModal",false);
}
Component.hbs
{{#if #showDialog}}
<PaperDialog id="genericModal" class="flex-50" #fullscreen={{fullscreen}} #onClose={{action "closePromptDialog"}} #origin={{dialogOrigin}}>
<PaperDialogContent class="text-align-center">
{{paper-icon "remove_circle_outline" class="red" size=48}}
</PaperDialogContent>
<PaperDialogContent>
<h2>{{#err.errorMessage}}</h2>
</PaperDialogContent>
<PaperDialogActions #class="layout-row">
<span class="flex"></span>
<PaperButton #primary={{true}} #onClick={{action "hideModal"}} #raised={{true}}>Ok</PaperButton>
</PaperDialogActions>
</PaperDialog>
{{/if}}
Component.js
#action
hideModal(){
this.args.onSave();
}
on this i am getting error as
Uncaught TypeError: method is not a function
Any help will be very much appreciated.
Ember version i am using is 3.18.0
From Octane edition, everything becomes more explicit in Ember 😃 One such thing is the {{action}} helper to pass functions. In classic (pre-octane) model, when passing a string to the action helper like, {{action "closePromptDialog"}}, Ember will search for the action closePromptDialog inside the actions hash of the corresponding controller as mentioned in the example in the guide.
Since the introduction of native class and #action decorator, we don't have to use the {{action}} helper as well as actions hash. We can directly pass the method to the component without action helper as mentioned in the guides of 3.18. So,
Controller.hbs:
{{generic-err-modal
err=receivedErr
showDialog=this.showErrorModal
onSave=this.closePromptDialog
}}
Here, this.closePromptDialog will directly refer to the method in the backing class similar to any other class property like, receivedErr, in your case. The proper this binding will be taken care of by the #action decorator. The same is applicable for your actions inside Component.hbs file.

Loading several data sources into an Ember UI

I'm trying to understand how to compose a page with multiple data sources and loading spinners in Ember.
Let's you have a UI like this:
http://postimg.org/image/6i1ko340f/
(sorry, don't have reputation to embed image)
And let's say each screen 'module' [books, shows, movies, tweets] has a separate data source at a remote API url.
somesite.com/api/books.json
somesite.com/api/shows.json
somesite.com/api/books.json
twitter.com/api/tweets.json
In order to simplify, and get to the essence of the question, I ask that you don't use:
1) Components
2) A data library (e.g. Ember Model or Ember Data) unless that's essential to the answer.
How would you approach architecting this in Ember so that each module has its own spinner and they load at their own pace (separate route hooks?).
Please help me understand how you would compose your routes, controllers, and models, or whatever else to elegantly solve this extremely common problem. I'll attach some of my approaches and thoughts in the comments :)
Well, I don't know if this is the best way by any means, but this is how I approach that problem. Dummy example using timeouts instead of async calls.
Write some sort of async wrapper. I use ic-ajax wrapper around jQuery ajax to make ajax calls that return promises in a class I call my rest-client. I use initializers to inject this object into all of my routes as restClient as a convenience. You can always just import when you need it.
I would normally block for my model retrieving via Ember.RSVP.hash() calls, but lets assume I did not as you've asked (this is our index route):
import bookModel from 'app/models/book-model';
setupController: function(controller, model){
var self = this;
//do this for each "module" call
this.restClient.get('books/url').then(function(response){
var booksController = this.controllerFor('booksTemplate');
//data is json for the properties of your Book model
var books = model.create(response.data);
booksController.set('isLoading', false);
booksController.set('model', books);
});
}
Make an asynchronous call, and on its success, set the model for the template's controller. Your template books.hbs:
{{#if isLoading}}
<img src="spinner.gif">
{{else}}
{{#each}}
{{this.title}}
{{/each}}
{{/if}}
This template shows a spinner or while loading happens. Notice in the setupController call that we set isLoading to false on complete of the ajax call. This causes the else block to display.
And your books.js controller:
export default Ember.ArrayController.extend({
isLoading: true;
});
And finally, your index.hbs:
{{render 'books'}}
{{render 'otherModel'}}
{{render 'anotherModel'}}
Caveat, if you render the same template twice with a render partial and do not specify the model, the two templates share a singleton instance of the controller meaning isLoading is shared across instances. Holler if you have any questions

Emberjs: how to reach the action of a parentView within an itemsViewClass

Cannot get my head around the following problem. I got a Uncaught TypeError: Cannot read property 'send' of undefined whatever I try. I think it must be the controllerBinding in the itemsViewClass, however I think it is defined correctly.
In the code below there are two showMenu actions. The first one works, but the last one in the itemsViewClass does not.
Please take a look at my code below (I show only the relevant code):
//views/menu.js
import Ember from "ember";
var MenuitemsView = Ember.View.extend({
template: Ember.Handlebars.compile('<div{{action "showMenu" target="view"}}>this works already</div>\
much more code here'),
contentBinding: 'content',
itemsView: Ember.CollectionView.extend({
contentBinding: 'parentView.subCategories',
itemViewClass: Ember.View.extend({
controllerBinding: 'view.parentView.controller', // tried to add controllerBinding but did not help
// this is where the question is all about
template: Ember.Handlebars.compile('<div {{action "showMenu" target="parentView"}}>dummy</div>')
}),
actions: {
showMenu: function(){
// dummy for testing
console.log('showmenu itemsView');
}
}
}),
actions: {
showMenu: function() {
console.log('showMenu parentView!'); // how to reach this action?
}
}
});
export default MenuitemsView;
I have tested with {{action "showMenu" target="view"}} and without a target. It seems not to help.
Do someone have a clue why the second showMenu action cannot be reached?
OK, so this is by no means the only way to do logic separation in Ember, but it's the method I use, and seems to be the method generally used in the examples across the web.
The idea is to think of events and actions as separate logic pools, where an event is some manipulation of the DOM itself, and an action is a translatable function that modifies the underlying logic of the application in some way.
Therefore, the flow would look something like this:
Template -> (User Clicks) -> View[click event] -> (sends action to) -> Controller[handleLogic]
The views and the controllers are only loosely connected (which is why you can't directly access views from controllers), so you would need to bind a controller to a view so that you could access it to perform an action.
I have a jsfiddle which gives you an idea of how to use nested views/controllers in this way:
jsfiddle
If you look at the Javascript for that fiddle, it shows that if you use the view/controller separation, you can specifically target controllers to use their actions, utilising the needs keyword within the controller. This is demonstrated in the LevelTwoEntryController in the fiddle.
At an overview level, what should happen if your bindings are correct, is that you perform an action on the template (either by using a click event handler in the view, or using an {{action}} helper in the template itself, which sends the action to the controller for that template. Which controller that is will depend on how your bindings and routing are set up (i've seen it where I've created a view with a template inside a containerView, but the controller is for the containerView itself, not the child view). If the action is not found within that controller, it will then bubble up to the router itself (not the parent controller), and the router is given a chance to handle the action. If you need to hit a controller action at a different level (such as a parent controller or sibling), you use the needs keyword within the controller (see the fiddle).
I hope i've explained this in an understandable way. The view/controller logic separation and loose coupling confused me for a long time in Ember. What this explaination doesn't do, is explain why you are able to use action handlers in your view, as I didn't even know that was possible :(

Make a programmatically Created Controller Delegate (Bubble) Events

I have an app with many similar views which I instantiate programmatically to "DRY-up" my app.
The problem is that controllers instantiated programmatically do not delegate actions in the actions hash further. This is clear because there is nothing from which the controller can derive the hierarchy. There should be a way, however, to tell a controller which parent controller it has to use for event bubbling. Does anyone know it?
You shouldn't be initializing controller's on your own. All controller initialization should be handled by Ember itself. Another interesting note, controller's are intended to be singletons in the application. The only exception to this being the itemController when looping over an ArrayController. You can read more about it in the guides. Quote from the guides:
In Ember.js applications, you will always specify your controllers as
classes, and the framework is responsible for instantiating them and
providing them to your templates.
This makes it super-simple to test your controllers, and ensures that
your entire application shares a single instance of each controller.
Update 1:
An example of how to do routing for a wizard:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
This way, you can have a separate controller/view/template per step of the wizard. If you have logic around how much of each step should be completed prior to transitioning to the next one, you can handle that in the individual routes.
Update 2:
In the event that the number of steps aren't predetermined, but are based on the data being fed to the app, you can make a WizardController that is an ArrayController where each item in the array is a step in the wizard. Then, use the lookupItemController hook on the ArrayController, kind of like this:
App.WizardRoute = Ember.Route.extend({
model: function() {
return [
{controllerName: 'step1', templateName: 'step1'},
{controllerName: 'step2', templateName: 'step2'},
{controllerName: 'step3', templateName: 'step3'}
];
}
});
App.WizardController = Ember.ArrayController.extend({
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
{{#each step in controller}}
{{view Ember.View templateName=step.templateName}}
{{/each}}
As another, probably better, alternative, you can override the renderTemplate hook in the route where you're pulling down the model for the next step in the wizard and pass in the appropriate templateName and controller in the render call, kind of like you see here.
Point being, I think it should be possible to do this without having to instantiate controllers yourself.

Using Ember.js, how do I run some JS after a view is rendered?

How do I run a function after an Ember View is inserted into the DOM?
Here's my use-case: I'd like to use jQuery UI sortable to allow sorting.
You need to override didInsertElement as it's "Called when the element of the view has been inserted into the DOM. Override this function to do any set up that requires an element in the document body."
Inside the didInsertElement callback, you can use this.$() to get a jQuery object for the view's element.
Reference: https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js
You can also use afterRender method
didInsertElement: function () {
Ember.run.scheduleOnce('afterRender', this, function () {
//Put your code here what you want to do after render of a view
});
}
Ember 2.x: View is deprecated, use component instead
You have to understand the component's lifecycle to know when does certain things happen.
As components are rendered, re-rendered and finally removed, Ember provides lifecycle hooks that allow you to run code at specific times in a component's life.
https://guides.emberjs.com/v2.6.0/components/the-component-lifecycle/
Generally, didInsertElement is a great place to integrate with 3rd-party libraries.
This hook guarantees two (2) things,
The component's element has been both created and inserted into the DOM.
The component's element is accessible via the component's $() method.
In you need JavaScript to run whenever the attributes change
Run your code inside didRender hook.
Once again, please read the lifecycle documentation above for more information
Starting with Ember 3.13, you can use components that inherit from Glimmer, and this example below shows what that could look like:
import Component from '#glimmer/component';
import { action } from '#ember/object';
/* global jQuery */
export default class MyOctaneComponent extends Component {
#action configureSorting(element) {
jQuery(element).sortable();
}
}
<div {{did-insert this.configureSorting}}>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
These view style components don't have lifecycle hooks directly, instead, you can use render-modifiers to attach a function. Unofficial introduction to modifiers can be found here
The benefit of this is that, it's clearer what the responsibilities of the template are and become.
Here is a runnable codesandbox if you want to play around with this:
https://codesandbox.io/s/octane-starter-ftt8s
You need to fire whatever you want in the didInsertElement callback in your View:
MyEmberApp.PostsIndexView = Ember.View.extend({
didInsertElement: function(){
// 'this' refers to the view's template element.
this.$('table.has-datatable').DataTable();
}
});