Get the value of another select control in Ember - ember.js

My situation is I have a table where the user can add suppliers, and edit any existing ones (so there are potentially multiple records). Three of the fields (Type, Name, Version) come from a lookup object returned by the API (which is one table in the backend database).
Before clicking 'edit'
In edit mode
The thing is, I need these select elements to be "chained" (or cascading), but since they're populated from the same object, it's more like the selection of "Type" will filter the options available for "Name" and likewise selecting Name will further restrict the options available for Version.
However, since this is just one record being edited, these selects are in an {{#each supplier in suppliers}} block to generate the rows, and show selects if that record's isEditing property is true, so the value or selection is the per-record value, e.g. supplier.type and not a single property on the whole controller.
I've tried to come up with multiple ways to do this, but so far haven't found a solution to cascading dropdowns with multiple records since that means the value of any one select is dependent on the record.
I think I could get the option filtering to work if I knew how to reference the value of say the Type dropdown from within the controller, but then again it's conceivable that two records could be in edit mode at once, so modifying any property on the controller to populate the selects would affect the others too, and that's not good. I just really wanted to figure this out so I didn't have to pop up a modal dialog to edit the record.

You should use components to handle each row seperately.
Let's say that you have something like this:
{{#each suppliers as |supplier|}}
// .. a lot of if's, selects and others
{{/each}}
If you find yourself using {{#each}} helper and your block passed to that helper is more than one line, than it's a good sign you probably need a component there.
If you create a component named, let's say, SupplierRow you could make it as follow:
module export Ember.Component({
editing: Ember.computed.alias('model.isEditing'),
types: Ember.computed('passedTypes', function() {
// .. return types array for that exact supplier
}),
names: Ember.computed('passedNames', 'model.type', function() {
// .. return names array for that exact supplier based on possibleNames and model.type
}),
versions: Ember.computed('passedVersions', 'model.type', 'model.name', function() {
// .. return versions array for that exact supplier based on possibleVersions and model.type and model.name
}),
actions: {
saveClicked() {
this.sendAction('save', this.get('model'));
}
}
});
The template would basically look similiary to what you have currently in your {{#each}} helper. It would be rendered something like this:
{{#each suppliers as |supplier|}}
{{supplier-row model=supplier possibleTypes=types possibleNames=names possibleVersions=versions save="save"}}
{{/each}}

Seems like you are using an old version of Ember wich allows context switching in {{#each}} helpers. Assuming that, you can set itemController for each iteration and handle selectable values for each row separately:
{{#each suppliers itemController="supplierController"}}
// this == supplierController, this.model == supplier
{{/each}}
So inside the supplierController you can calculate select content for each single supplier. You can also access main controller from item controller by this.parentController property.

Related

Add local controller property to store models

I want to add a simple 'selected' property to store objects, just localised to the particular route/controller.
So in my controller I'm loading the 'groups' from the store. For each 'group' I want to add in a 'selected' property. Tried a few different approaches but just can't get it working.
Advice on "the ember way" would be much appreciated.
If what you're trying to do is display something different for each group that isSelected. I'd take the following approach:
I'd create an array on your controller of selectedGroups, which can either be an array of group objects from the store or just simply an array of ids. As a group is selected/unselected, it can be added to/removed from the array. You can then use a computed function or pass selectedGroups into components to do whatever you need with selectedGroups.
So, if you had your groups template like so:
{{#each model as |group|}}
{{my-item-component item=group selectedItems=selectedGroups}}
{{/each}}
Then in your my-item-component.js, you would have a computed function like:
isSelected: computed('item', 'selectedItems.[]', function () {
// This assumes selectedItems contains item/group objects rather
// than ids. You will need to tweak a little for ids.
return (this.get('selectedItems').indexOf(this.get('item')) !== -1);
})

ember: computed property on ember-data store

I am setting up a "saved item" feature for logged-in users. I have the modeling worked out already (it relates the saved item list to both the user and products). But I am having trouble with how to have a computed property on my saveditem model. The model:
// saveditem.js model
export default DS.Model.extend({
user: belongsTo('user'),
product: belongsTo('product'),
dateAdded: attr('string')
});
I am currently using the product id as the id of that model.
I need a computed property on that model because anytime a product is shown on the site, the UI needs to reflect whether the item is already in the saveditem ember-data store or not.
Example of if the item is not on the list (but can be added):
Example of an item that is on the list (but can be removed):
I was thinking that on my userprofile service which manages the user's data across the app, I could have a computed property that outputs an array of ids from the saveditem model:
savedItemsList: computed('WHAT.IS.DEPENDENT.KEY?.[]', function() {
return this.get('store').peekAll('saveditem').map(item => item.id);
}),
And then in the template I could use a composable helper to see if the item being displayed is in the list or not:
{{#if (contains product.id userprofile.savedItemsList)}}
...show already-saved button...
{{else}}
...show save button...
{{/if}}
The question: what would the dependent key be for this computed property? OR...is this method stinky and and there's a better way to do it?
I have tried:
savedItemsList: computed('store.saveditem.[]', function() {
(after of course injecting the store). Seems like the obvious one but it doesn't update when records are added or removed from the saveditem store. Also tried 'store.saveditems.[]', permutations with and without array brackets, and NO dependent key - all no worky.
Makes me wonder whether this is not possible for a good reason ;) I might be "fighting the framework" here. Any help appreciated!
You can introduce computed property which will return all the items in the store and use that property for your savedItemsList computed property.
allSavedItemsList: computed(function() {
return this.get('store').findAll('saveditem');
}),
savedItemsList: computed('allSavedItemsList.[]',function() {
return this.get('store').peekAll('saveditem').map(item => item.id);
}),

In an ember component template how can I use 'if' to check for at least one of an array having a property equal to X?

My component is passed a notification.
A notification has an account, and an account has many contacts.
So I could easily do something like
{{#if (is-greater-than notification.account.contacts.length 0)}}
<p>stuff</p>
{{/if}}
To check for the existence of a contact, but what if I wanted to only display if at least one contact has firstName 'John', how could I do this in the component template only? Or if not possible in the template what is the next best approach?
I tried having a isHidden property in my component and then in the didInsertElement hook looping through contacts and then communications with the aim of checking myself - however I find contact.get('communications').forEach doesn't get hit because that level of data hasn't been pulled from the server at that point.
For the first question, you could write a helper to do that:
// helpers/contains-john.js
export default function(array) {
return array.any((item) => item.get('firstName') === 'John');
};
Then use it as a subexpression just like you have above:
{{#if (contains-john notification.account.contacts)}}
Stuff
{{/if}}

Load and unload multiple components in a sidebar using Ember

I have a page where a user is building up an order for a customer. At various stages of the order they may get to a point where they need to add something else. For example, when selecting an address for the customer the customer may have a new address that needs adding, or an existing address may need editing.
I want to be able to load small components on the fly in a right hand sidebar, but there could be any number of them, so I can't just have something like
{{outlet 'right-hand-bar'}}
For example the user may have clicked to add an address, but then may also click a product in the order and I would want to show another component with details on the product, below the add address component. I guess it is kind of like master detail concept, but detail can contain multiple distinct details.
It doesn't feel right to just have a number of outlet's and use the next one available i.e.
{{outlet 'right-hand-bar1'}}
{{outlet 'right-hand-bar2'}}
...
Is there a way to do this in Ember CLI? X number of components in a single outlet?
Edit
I am considering a concept involving creating a sideBar component with a main div
<div class='side-bar-load-area'></div>
and then having a loadComponent action in the sideBar component that takes the name of the component to load.
Not sure how I could load a component by name? I've seen this for a controller:
var comp = App.FooBarComponent.create();
comp.appendTo('#someArea');
and
this.controllerFor('someName');
So ideally would want to combine this and get it working for my sub components?
And also give the sidebar a closeComponent action that a given component could call to unload itself, for an outlet you would do
closeOutlet: function() {
return this.disconnectOutlet({
outlet: 'modal',
parentView: 'application'
});
}
So again just looking to translate this to work for unloading a component in the DOM, not in a outlet?
Assuming you are running Ember 1.11+, you can use the new component helper
Basically, you could do something along the lines of:
templates/main_view.hbs
<div class="sidebar">
{{#each componentNames as |componentName|}}
{{component componentName}}
{{/each}}
</div>
and your buttons to create said components in:
templates/main_view.hbs
<button {{action "addAddress"}}>New address</button>
and the actions themselves in your controller:
controllers/main_view.js
actions: {
addAddress: function() {
var controller = this;
var componentNames = controller.get("componentNames");
componentNames.pushObject("address");
}
}

emberjs add code on the fly, after template render

In emberjs, you can add code to your template file:
{{input type="text" value=color}}
the template then renders. But the question is, how can you add this dynamically after the template renders? For example, I want to add a button, to generate new input fields (colors), and the user can continue to add new colors as needed? How would one go about doing this?
First of all, if you want to let user add another input for color, I am sure that you want to somehow access the value user inputs after all - e.g. in some action. Therefore, you will have to make some bindings that will store that values.
Let's say you need to store them in some kind of an array - e.g. colors. This array will be initially containing only one object, automatically added when user enters the route. This setup (e.g. in setupController hook in route) may look like this:
setupController: function(controller, model) {
controller.set("colors", []);
controller.get("colors").pushObject({ value: "" });
}
And let's handle the click on the button by an action in controller:
actions: {
handleClick: function() {
this.get("colors").pushObject({ value: "" });
}
}
Then, your template can look like this:
{{#each color in colors}}
{{input type="text" value=color.value}}
{{/each}}
Using pushObject method make pushing binding-compliant. Every time you push anything to colors array, the template will automatically rerender and inject another input field with properly binded value to color.value. Thanks to that, in some other action (like submit) you can access all the values provided by the user and process them as you want.