This official guide describes how you can bind a boolean property to disabled attribute of a HTML element. Yet it talks about a controller.
I have a button, that when clicked transitions the route (sorry it has to be a button and cannot be a link-to):
/templates/trails.hbs
<button type="button" class="btn btn-primary" disabled={{isEditing}}
onclick={{route-action 'addNew'}}>Add New</button>
(route-action is a helper that allows me to use closure actions in routes)
/routes/trails.js
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
addNew() {
this.transitionTo('trails.new');
}
}
});
So, after the button is clicked, the route is changed to 'trails.new'
/routes/trails/new.js
import Ember from 'ember';
export default Ember.Route.extend({
isEditing: true,
});
This property appears to be ignored and is not bound as I had expected it would be. I also tried adding a controller:
/controllers/trails/new.js
import Ember from 'ember';
export default Ember.Controller.extend({
isEditing: true,
});
So how does the official guide suggest something that seems to not work? What piece of ember magic am I missing here?
Your template is templates/trails.hbs but you set isEditing in a subroute controller controllers/trails/new.js
You need to have controllers/trails.js and deinfe isEditing in it.
So in routes/trails.js implement this :
actions: {
willTransition: function(transition) {
if(transtions.targetName === 'trails.new'){
this.controller.set('isEditing', true);
}
else{
this.controller.set('isEditing', false);
}
}
}
After some digging around I discovered that what I was trying to do is not the right way to go about this at all. I would have to add a controller/trails.js and put the property 'isEditing' in that.
So I refactored this into a component: add-new-button. This is a far more 'ember' way.
First, I need an initializer (thanks to this question):
app/initializers/router.js
export function initialize(application) {
application.inject('route', 'router', 'router:main');
application.inject('component', 'router', 'router:main');
}
export default {
name: 'router',
initialize
};
(this injects the router into the component, so I can watch it for changes and also 'grab' the currentRoute)
My code refactored into the component:
app/components/add-new-button.js
import Ember from 'ember';
export default Ember.Component.extend({
isEditing: function() {
let currentRoute = this.get('router.currentRouteName');
return ~currentRoute.indexOf('new');
}.property('router.currentRouteName')
});
templates/components/add-new-button.hbs
<button type="button" class="btn btn-primary" disabled={{isEditing}}
onclick={{route-action 'addNew'}}>Add New</button>
templates/trails.hbs
{{add-new-button}}
The beauty of this is now I can use this button on my other top level templates to trigger route changes to the new route for each resource (and disable the button on arrival at the new route).
NOTE
return ~currentRoute.indexOf('new');
is doing a substring check on the route, if it finds 'new' returns true, otherwise returns false. See this.
In ES6 it can be replaced with (so I have!):
return currentRoute.includes('new);
Related
Is it possible to update a property on all instances of a component?
If I have 10 instances of the component below on a page, I would like to set the currentTrack property to false on all of them. Is this possible? Can it be done from inside one of the components?
import Ember from 'ember';
export default Ember.Component.extend({
currentTrack: true,
});
I'm using Ember 2.12
You can use Ember.Evented for this use case.
Here, there is a simple twiddle for it.
template.hbs
{{your-component-name currentTrack=currentTrack}}
{{your-component-name currentTrack=currentTrack}}
{{your-component-name currentTrack=currentTrack}}
// btn for disabling
<a href="#" class="btn" onclick={{action 'makeAllcurrentTracksFalse'}}>To false</a>
controller.js
currentTrack: true,
actions: {
makeAllcurrentTracksFalse() {this.set('currentTrack', false)}
}
or in your-component-name.js - you can use the same action as above and it will be applied to all components
How about you create entries for what ever thing your're trying to achieve.
const SongEntry = Ember.Object.extend({
});
To create an entry you would call (probably add a song to playlist?)
songs: [],
addNewSongToList: function(songName) {
const newEntry = MyMusicEntry.create({
isCurrent: false,
title: songName
});
this.get('songs').pushObject(newEntry);
},
activateNewSong: function(newSongToActivate) {
this.get('songs').forEach(s => s.set('isCurrent', false);
newSongToActivate.set('isCurrent', true)
}
Template would look like this
{{each songs as |song|}}
{{my-song-component songEntry=song changeSong=(action "activateNewSong")}}
{{/each}}
//my-song-component.js
<div class="song-layout" {{action "changeSong" song}}>
I have two simple ember components; a list component and a list-item component. Data gets passed as an array to the list where it runs an each loop and makes a list-item component for each item in the array.
I'd like to, within the list-item component, take the data being passed to it from its parent list component and overwrite it. Eventually I would like to do more than just overwrite it and use it as a parameter in a function to return a new, parsed, value.
For the sake of example, lets say that this is a list of tweets.
Here is my code.
ROUTER.JS
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('tweets');
});
export default Router;
TEMPLATES/TWEETS.HBS
{{tweet-list tweets=model}}
ROUTES/TWEETS.JS
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return[{
tweetText: "this is an example #request tweet text1 #user"
},{
tweetText: "tweet of the #text2 how #cool"
}, {
tweetText: "tweet toot took text3"
}];
}
});
COMPONENTS/TWEET-LIST/COMPONENT.JS
import Ember from 'ember';
export default Ember.Component.extend({
});
COMPONENTS/TWEET-LIST/TEMPLATE.HBS
<ul>
{{#each tweets as |tweet|}}
<li>{{tweet-item tweet=tweet}}</li>
{{/each}}
</ul>
COMPONENTS/TWEET-ITEM/COMPONENT.JS
import Ember from 'ember';
export default Ember.Component.extend({
// model(){
// return "over written value here"
// }
});
COMPONENTS/TWEET-ITEM/TEMPLATE.HBS
{{tweet.tweetText}} - <!-- {{overwritten value here}} -->
I believe I have to do the overwriting in the COMPONENTS/TWEET-ITEM/COMPONENT.JS file ? How do I go about overwriting or, even better, returning a new value based off of the data passed down from the parent component?
Use different component properties for given and overwritten tweets. For example:
// components/tweet-item/component.js
import Ember from 'ember';
export default Ember.Component.extend({
// given tweet
tweet: null,
// overwritten tweet
parsedTweet: Ember.computed('tweet', function() {
return {
tweetText: this.get('tweet').tweetText + ' #overwritten'
};
}),
// you may also modify given tweet here
// but the better approach to send action up
// in favor of data-down-action-up principe
actions: {
publish: function(tweet) {
this.sendAction('publish', tweet);
}
}
});
// components/tweet-item/template.hbs
tweetText: {{parsedTweet.tweetText}}
<button {{action 'publish' tweet}}> Publish </button>
// components/tweet-list/component.js
actions: {
publish: function(tweet) {
// your logic
}
}
// components/tweet-list/template.hbs
<ul>
{{#each tweets as |tweet|}}
<li>{{tweet-item tweet=tweet publish='publish'}}</li>
{{/each}}
</ul>
How do i get the container name of a component within itself?
this.componentNameStream used to kind of work in 1.11.
// in components/my-component.js
export default Component.extend({
layoutName: "components/my-component",
partialName: function() {
//return "my-component"; somehow....
}.property();
});
why? for subclassing reasons:
// in components/blah.js
export default MyComponent.extend({});
// in templates/components/my-component.hbs
<div class="someLayout">
// partialName is now "components/blah"
{{ partial partialName }}
</div>
Not sure I fully understand what you are trying to do but the usage of {{partial}} is not recommended (see https://github.com/dockyard/styleguides/blob/master/ember.md#templates).
Why not simply share a template between two components using layoutName?
// in components/my-foo.js
export default Component.extend({
showSomething: false
});
// in components/my-bar.js
export default MyFooComponent.extend({
layoutName: "components/my-foo",
showSomething: true
});
// in templates/components/my-foo.hbs
<div class="someLayout">
{{#if showSomething}}
I am the bar component!
{{/if}}
</div>
If you really need to use a partial, you could replace showSomething by a property which contains the name of the partial to be displayed as a string.
I am trying to develop my first mixin but I'm having trouble getting the actions to play nicely.
I want my controllers to be able to toggle an editing property and to set it to false when the model is saved or rolled back. So I've written a mixin to add this capability.
in myapp/mixins/editable.js:
import Ember from "ember";
export default Ember.Mixin.create({
editing: false,
actions: {
toggleEditing: function() {
this.toggleProperty('editing');
},
cancel: function () {
console.log("editable mixin: cancel");
this.set('editing', false);
return true;
},
save: function () {
console.log("editable mixin: save");
this.set('editing', false);
return true;
}
}
});
I thought this would be great as I can have consistent edit buttons in my templates like this.
in myapp/templates/sometemplate.hbs:
{{#if editing}}
{{#if model.isDirty}}
<button class="action-icon" {{action "cancel"}}>{{fa-icon "times" title="discard changes"}}</button>
<button class="action-icon" {{action "save"}}>{{fa-icon "save" title="save changes"}}</button>
{{else}}
<button class="action-icon" {{action "toggleEditing"}}>{{fa-icon "times" title="cancel"}}</button>
{{/if}}
{{else}}
<button class="action-icon" {{action "toggleEditing"}}>{{fa-icon "pencil" title="edit"}}</button>
{{/if}}
...and I can control saving and cancelling in my route, something like this:
in myapp/route/someroute.js:
import Ember from "ember";
export default Ember.Route.extend({
model: function(params) {
return this.store.find('somemodel', params.model_id);
},
actions: {
save: function () {
console.log("route: save");
this.modelFor('somemodel').save();
},
cancel: function () {
console.log("route: cancel");
this.modelFor('somemodel').rollback();
},
}
});
However, I am now confused... what happens if the save fails? How can I plumb it together so that the editing property is set to false only when the save has successfully completed?
Is there some way to access a promise from an action on the route? Am I heading in the right direction with this?
This is possible, but there's a little bit of weirdness. Here is a JSBin demonstrating the concept. Applied to your code, you'll have to do this:
const SomeRoute = Ember.Route.extend({
actions: {
save() {
return this.modelFor('somemodel').save();
}
}
});
SomeRoute.reopen(EditableMixin);
export default SomeRoute;
Then, in your Mixin:
export default Mixin.create({
actions: {
save() {
this._super().then(() => {
this.set('editing', false);
});
}
}
});
Note that if you apply the mixin at extend time, the super chain won't be set up correctly, so you'll have to reopen the route to apply the mixin.
According to this, http://ember-addons.github.io/bootstrap-for-ember/#/show_components/popover, shows the documentation on how this is supposed to work...quoted below may be the bit I'm not understanding (it's a little confusing because this all mentions tooltips and not popovers but I'm making a leap that since it's all in the same documentation that it's all related...)
(controllers)dashboard.js
import Ember from 'ember';
export default Bootstrap.TooltipBoxController=Ember.Controller.extend({
hoverPop: Ember.Object.create({
title: "I'm a title!",
content: "And i'm a content!",
trigger: "hover",
placement: "right",
sticky: true
})
});
(templates)dashboard.hbs
<div class="pageheader">
{{bs-bind-popover hoverPop}} ?
</div>
application.js inside this file i have:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function() {
// Render default outlet
this.render();
// render extra outlets
var controller = this.controllerFor('tooltip-box');
this.render("bs-tooltip-box", {
outlet: "bs-tooltip-box",
controller: controller,
into: "application" // important when using at root level
});
},
....
Ember console:
Uncaught TypeError: undefined is not a function (refers to this line export default Bootstrap.TooltipBoxController=Ember.Controller.extend({
Any ideas? Has anyone solved this problem?