how to trigger a function automatically in ember.js action helper? - ember.js

I am new to ember.js and working on an application to highlight text like below:
<div>
<h3 class = "title" {{action "highlight" "title"}}>{{document.title}}</h3>
<div class = "date" {{action "highlight" "date"}}>{{document.date}}</div>
<p class = "content" {{action "highlight" "content"}}>{{document.contents}}
</p>
</div>
I created a function highlight which will get the class name and highlight the search text the user input. This function works fine, but I have to click to trigger this highlight function. Is there any way in ember action helper can trigger this function whenever the div node rendered or automatically triggered?

What you probably need is to implement the hook didInsertElement, this will trigger as soon your component got rendered in the screen.
Example:
import Component from '#ember/component';
export default Component.extend({
didInsertElement() {
this._super(...arguments);
const title = this.element.querySelector('.title');
// do something with the title
},
});
More information about didInsertElement can be found in this guide: https://guides.emberjs.com/v2.18.0/components/the-component-lifecycle/

I don't understand your use-case entirely, but I think actions are not a good choice for your problem. I would recommend reading the ember guides about computed properties. These properties are recomputed everytime an underlying property changes.
highlightedContent: computed('userInput', 'content', function() {
....
//return computedContent;
})
I would also recommend reading the guides about handlebar-helpers. You could write a highlight-helper.

Related

Ember component call an action in a route or controller

I have a component the main purpose of which is to display a row of items.
Every row has a delete button to make it possible to delete a row. How is possible to pass an action from a template to the component which will trigger an action in a router ?
Here is the template using the component:
#templates/holiday-hours.hbs
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true}}
{{/each}}
Here is the component template:
# templates/components/holiday-hour.hbs
...
div class="col-sm-1">
{{#if shouldDisplayDeleteIcon}}
<button type="button" class="btn btn-danger btn-sm mt-1" {{action 'deleteHoliday' holiday}}>
<span class="oi oi-trash"></span>
</button>
{{/if}}
</div>
I'm using the same component to display a row and to create a new item (holiday-hour).
I'm using ember 3.1.2
Thank you
You have to send the actions up from the component to the route. The main way to do this is by adding actions to your component that "send" the action to the parent. Once the action is sent you have to tell the component what action on the route to trigger by passing in the action as a parameter. Below is an example of how to do this.
Component js
# components/holiday-hour.js
...
actions: {
deleteHoliday(){
this.sendAction('deleteHoliday');
}
}
Template for route
#templates/holiday-hours.hbs
...
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteHoliday='deleteHoliday'}}
{{/each}}
Route js
#routes/holiday-hours.js
...
actions: {
deleteHoliday(){
//code to delete holiday
}
}
I will try to give a general answer because your question is not giving enough/all info regarding the route actions etc. Long answer short, using closure functions. Assuming this is your route js file routes/holiday-hours.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){ /*... some code */ },
setupController(controller){
this._super(controller);
controller.set('actions', {
passToComponent: function(param) { //.... function logic }
})
}
});
Note: in the above snippet, I'm using setupController to create actions. Alternatively, you can put the actions inside a controller file otherwise actions directly inside the route will throw an error.
So I want the action passToComponent to be called from the component. This is what you do to make it accessible inside the component.
{{#each model as |holidayHour|}} {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true callAction=(action 'passToComponent')} {{/each}}
Now we have passed the action to the component and here's how to call it from the component. Note: I have added a param just to show that it can take a param when called within the component.
import Component from '#ember/component';
export default Component.extend({
actions: {
deleteHoliday: ()=> {
this.get('callAction')() /*Pass in any params in the brackets*/
}
}
});
You will also see demonstrations using sendAction which is rather old and acts more of an event bus that is not very efficient. Read more from this article

Set focus on a text box in ember js

I am trying to change a text to a text box. If a user clicks the text, it will change to a test box and so they can edit the content. I have done that. I am able to set the focus by below code.
{{input value=animal.name autofocus=true}}
It only works for the first time. If I focus-out, the text box will again change to text content. If I click the text again, I am able to see the text box, but it is not in focus. Below is the code I am trying.
Component file:
import Ember from 'ember';
export default Ember.Component.extend({
isTextBox: false,
actions: {
editTest() {
this.set('isTextBox', true);
}
},
focusOut() {
this.set('isTextBox', false);
},
});
Template file:
{{yield}}
<center><h2>
<div onclick = {{action 'editTest'}}>
{{#if isTextBox}}
{{input value=animal.name autofocus=true}}
{{else}}
{{animal.name}}
{{/if}}
</div>
</h2></center>
I am new to ember and I trying to do the focus without using jQuery.
Here is the twiddle https://ember-twiddle.com/d832c6540ba94901a6c42d5bb3cfa65e?openFiles=templates.components.text-input.hbs%2Ctemplates.components.text-input.hbs.
You can do this in Plain Old Javascript as follows:
didRender(){
// This works but is not very ember-ish
// document.getElementById('cat-text').focus();
// For a component, you can do this
this.get('element').querySelector("#cat-text").focus();
}
This assumes you have an input like this:
{{input id='cat-text' value=animal.name }}
Ember 2.13+ is no longer dependent on jQuery (although some of the add-ons that you use may be), so you have the possibility of eliminating 35kb (min + gzip) that jQuery would add to the payload of your app.
To expand on kumkanillam's answer you can just set the focus at the end of the render lifecycle if it's rendering a textbox. Since you've only got one text input in your component you can just find it with a selector. No need to worry about using jQuery here that's what it's there for so it is the Ember way.
didRender(){
if(this.get('isTextBox')){
this.$('input[type=text]:first').focus();
}
}
You can provide id to input helper,
{{input id='cat-text' value=animal.name }}
You can make it focus text field for every rendering,
didRender(){
this.$('#cat-text').focus();
}
components/input-text-focused/component.js:
import TextField from '#ember/component/text-field';
export default TextField.extend({
didInsertElement(...args) {
this._super(...args);
const element = this.get('element');
if (element) element.focus();
},
});
{{input-text-focused type="text" name="search" value=search}}

How to create action for my own component

I am creating one ember app.
Flow is like " page1 displays list of feeds item and clicking on any of the feed will take user to page2 showing details about that feed"
What i am doing:
i have one component named app-feed. Template is as below
<div onclick={{action 'click' feed}}>
{{#paper-card class="card-small" as |card|}}
<!-- --> {{card.image src=feed.imagePath class="small-feed-img" alt=feed.title}}<!---->
{{#card.header class="flex-box short-padding" as |header|}}
{{#header.avatar}}
<img class="profile-small" src="http://app.com/users/{{feed.userName}}.jpg" alt="{{feed.name}}" />
{{/header.avatar}}
<span class="tag-sm like-box">
{{feed.likes}} {{paper-icon "thumb_up" size="18"}}
{{feed.commentCount}}{{paper-icon "chat_bubble" size="18"}}
</span>
{{/card.header}}
{{#card.actions class="action-block"}}
{{#paper-button iconButton=true}}{{paper-icon "favorite" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "share" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "shopping_basket" size="18"}}{{/paper-button}}
{{/card.actions}}
{{/paper-card}}
</div>
component.js is as below
import Ember from 'ember';
export default Ember.Component.extend({
actions:{
click(feed){
console.log("Click event fired:"+feed.id); //Output is correct in console
this.sendAction("onClick", feed); //sending onClick Action
}
}
});
I'm populating list of this component in one of my route.
Template is as below
{{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(action "getDetail" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
route.js is as below
import Ember from 'ember';
export default Ember.Route.extend({
store: Ember.inject.service(),
model(){
//Populating module. Works just fine
} ,
actions:{
getDetails(feed){
console.log("Getting details of "+feed.id);
}
}
});
I have defined getDetails action as mentioned in my template.js of the route still i am getting below error
""Assertion Failed: An action named 'getDetail' was not found in (generated feed.index controller)""
feed.index is my route.
I used same method and modified paper-chip's source to get action corresponding to click on paper-chip's item which worked. But i am not able to do same in my own component.
Please let me know what is missing
Your problem is that in your second last code snippet, the one with your template. You refer to the action as getDetail but in route.js your last code snippet you declare the action as getDetails which is different to the code in your template. It's a common spelling error, one has an "s" st the end whereas the other doesn't.
The actions should be in controllers. And if controller bubbles up then the action in route be called.
For your case you don't need controller.
You can use ember-transition-helper
I assume you have in router.js :
this.route('feeds', function(){
this.route('edit', {path: '/:id'});
});
Now your template is going to be :
{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(transition-to "feeds.edit" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
sendAction is an ancient way to calling action inside controller/route.
The new style is to use closure action, which passes action as a value by creating a closure at the time of value passing.
Yes, you are correct. The action has been sendAction is able to bubble up from,
correspond controller -> correspond route -> upper route -> ... -> application route
However, closure action does NOT bubble.
Please refer to Ember component send action to route where #kumkanillam detailed explained how to call action inside route using different method and the differences between sendAction and closure action.
I have also made a sample project and write a simple explanation for it at,
https://github.com/li-xinyang/FE_Ember_Closure_Action

Prevent click into {{input}} component from propagating/bubbling up (ember)

I have an {{input}} inside of an element that itself has an action.
How do I prevent clicking into the input from triggering the click event on the parent element?
I have tried bubbles=false.
I have also tried extending {{input}} and in the extended input I caught the click event and called event.preventDefault()
Please try my test case: https://ember-twiddle.com/a2cee9abd63a7400124e2745a7820cf8?numColumns=2&openFiles=controllers.application.js%2Ctemplates.application.hbs
Example hbs:
<div {{action "alert"}}>
I don't want the click into the input to trigger the div's onlick. Help!<br/>
{{input bubbles=false}}
</div>
Define component say my-input which extends Ember.TextField and define click function and return false to prevent bubbling.
import Ember from 'ember';
export default Ember.TextField.extend({
click(){
console.log('click my input');
return false;
}
});
For my-input component, you dont need to define anything to handle click event while including the component. you can just say {{my-input }}
Here is the working twiddle

ember.js | How to bind an event of a sub-component to an action of an outer component

unfortunately i am not able to figure out, how to receive an event of a component i use from within a component.
What i mean actually sounds harder than it is, consider the following toy example, with a component my-outer and another component my-inner (a short explanation follows the code, at the end i link to jsbin).
The templates:
<script type='text/x-handlebars' id='components/my-outer'>
<div {{bind-attr class="isRed:red"}}>Buttons should toggle my background color</div>
<button {{action "toggleRed"}}>It works from my-outer</button>
{{my-inner action="toggleRed"}}
</script>
<script type='text/x-handlebars' id='components/my-inner'>
<button {{action "action"}}>It doesn't work from my-inner</button>
</script>
The javascript:
App.MyOuterComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.toggleProperty("isRed");
}
}
});
my-outer contains a short text, with a background-color, which can be toggled from and to red by invoking the toggleRed action. the first button demonstrates that this works in principle.
now i would like to bind the default action of the second component to this same toggleRed action, that's the point of the following line.
{{my-inner action="toggleRed"}}
But on clicking the second button (which is part of my-inner) an error is thrown and the action is not fired.
How do I fix this example?
http://emberjs.jsbin.com/cabasuru/2/edit?html,js,console,output
Thanks so much in advance
(and this is my first question on so, i am happy about any meta-critics)
Since Components work just like views, easiest way is to get the parentView and forward the action. You may have to handle the action in my-inner like following.
App.MyInnerComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.get('parentView').send('toggleRed');
}
}
});
You can see outer component can be accessed as parentView in inner component. Here is the working jsbin link
http://emberjs.jsbin.com/cabasuru/5/edit
My question actually missed the main point. What goes wrong in the example above, is that the action helper in the inner component
<button {{action "action"}}>It doesn't work from my-inner</button>
does not trigger the default action associated with the component. Instead it invokes a new event named action, which is not allowed to bubble (due to the component confinement).
It turns out, there are two ways to solve that:
Properly reroute the event in an actions block on the my-inner component
<button {{action "my-action"}}>...</button>
together with a definition of the my-action action for my-inner:
App.MyInnerComponent = Ember.Component.extend({
actions: {
myaction: function(){
this.sendAction();
}
}
});
This is basically, the idea #CodeJack proposes, with the difference,
that here we rely on the wiring, which is set-up in the template of my-outer.
http://emberjs.jsbin.com/cabasuru/3/edit
As #torazaburo hinted at, setting the target property on the my-inner component to the my-outer component allows the event triggered from the action helper to bypass the component isolation.
{{my-inner target=controller}} in the my-outer template and a <button {{action "toggleRed"}}>...</button> in the my-inner template.