Ember loop through an array of checkboxes and get checked items? - javascript

I have an Ember.js application (using the Ember CLI). I have a component called sideMenu with the files sideMenu.js and sideMenu.hbs. Within the .js file, I have an array of vehicle names like this:
export default Ember.Component.extend({
vehicles: ['Audi', 'Chrysler', 'Harley Davidson'],
vehicleCheckboxes: { 'Audi': true, 'Chrysler': true, 'Harley Davidson': true }
});
In my template for this component, I am looping through these vehicles and creating a checkbox for each item like this:
{{#each vehicles as |vehicle|}}
{{input type='checkbox' checked='true'}} {{vehicle}}
{{/each}}
This gives me what I want - a few checkboxes with the vehicle names next to it. I want to be able to know what the user unchecked / checked in this list. I tried to do this by creating some Ember properties dynamically and in the template:
{{input type='checkbox' checked="{{vehicleCheckboxes}}.{{vehicle}}"}}
This didn't seem to work though. How can I accomplish this? There doesn't seem to be any indication on the Ember documentation that it is possible from within the framework in any way.

checked expects boolean value (true/false)
you can either use this addon like :
{{multiselect-checkboxes options=vehicles selection=selectedVehicles}}
or you can have your loop like this:
{{#each vehicles as |vehicle|}}
{{input type='checkbox' checked=(get vehicleCheckboxes vehicle)}} {{vehicle}}
{{/each}}
and you need your vehicleCheckboxes converted to Ember.Object I think if it does not work as it is
vehicleCheckboxes: Ember.Object.create({ 'Audi': true, 'Chrysler': true, 'Harley Davidson': true })

Related

How Can i order a model?

I'm a newbie in ember and I don't know how to sort a list of movies by title.
I have a route index with a model hook:
export default Route.extend(RealtimeRouteMixin,{
model() {
return this.store.findAll('movie');
}
});
I render a component when this route is loaded. I set the model in the template
{{landing-page
add=(action 'addMovie')
movies=model
}}
Inside this component, there are other child's components. In one of them is a list of movie where I want to show the list of the movies sort by title.
//landing-page template
{{movie-list-header}}
{{movie-list
movies=this.movies
}}
{{add-movie-form add=this.add
}}
//movive-list template
{{#each movies as |movie|}}
{{movie-list/movie-list-item
movie=movie
}}
<hr />
{{/each}}
I don't know what the best approach to do it. I've thought to sort the model in the route but I don't know If I have to do it something like this
export default Route.extend(RealtimeRouteMixin,{
model() {
return this.store.findAll('movie').then(funcion(data){
// sort here ???
});
}
});
Your approach would work. Then sortBy would do the trick.
The other approach is to put it somewhere in your component hierarch starting with the controller. A simple computed property will do the trick. There is also a computed property macro.
I prefer to handle simple ordering in the template using ember-composable-helpers as it sorts strings well with the least amount of boilerplate. After installing that addon and restarting your ember server you can do this in your template with the sort-by helper.
//movie-list template
{{#each (sort-by "title" movies) as |movie|}}
{{movie-list/movie-list-item
movie=movie
}}
<hr />
{{/each}}
In the controller/component where your model you want to sort with property, you can mention following computed property:
sortedModel: sort('model', 'title:desc'),
you can import sort as follows:
import { sort } from '#ember/object/computed';
Now use sortedModel instead of model in templates/handlebars. I hope that make sense. Please let me know if you have any query?

How do I pass an item in a Vuex list to a component?

So, I have a Vuex state that looks like so:
state: {
keylist: ['key1', 'key2', 'key3'],
items: {
key1: {title: "First Item"},
key2: {title: "Second Item"},
key3: {title: "Third Item"}
}
}
And I have a list component, referenced from root like so:
<event-list :list="this.$store.state.keylist"></event-list>
The components is defined like so:
Vue.component('event-list', {
template: "<ul><li v-for='key in list'>{{ key }}</li></ul>",
props: {
list: {
type: Array,
required: true
}
}
})
Now, this all displays the key just fine.
But, of course, what I really want to do is use a component on each item, found by it's key. And that's where I am stuck. I have an item component like so:
Vue.component('event-list-item', {
template: "<h4>{{ item.title }}</h4>",
props: {
item: {
type: Object,
required: true
}
}
})
But I cannot figure out how to translate the key in the parent component into an item in the child component. This template barfs on the first curly brace:
<ul><li v-for='key in list'><event-list-item :item="this.$store.state.items.{{key}}"</li></ul>
And in any case, that doesn't look like the right solution! so What is the right one?
To me the first thing that comes to mind is that's your nested item has no place to be inserted anyway. You might want to have a look at slots for nesting components like that:
https://v2.vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
Anyway, a few considerations:
In your use case you are better off having the list in the event list item and repeat just the element that you actually need, rather than retrieving it in a useless component that only wraps around another component. I mean:
Vue.component('event-list-item', { template: "<li v-for="item in list"><h4>{{ item.title }}</h4></li>
The store is considered the single source of truth also because it should be easier to access it from the directly impacted components, sparing you from having to hand down multiple props for several layers. Like you are doing. This base is kinda brittle for deeply nested components.
Keep in mind that each component has a different scope and they don't share a thing that's not explicitly passed.
You should access the state items fields like key :
<ul><li v-for='key in list'><event-list-item :item='key' /></li></ul>
and in tht child component :
template: "<h4>{{ store.state.items[item].title }}</h4>"
There is no problem of iterating through properties of items object. It will work the same way as iterating through an array.
<ul>
<li
v-for="item in items"
:key="item.title"
>
<event-list-item :item="item" />
</li>
</ul>
For better practice, you can format data inside a getter, to assign keys and return a list that is ready for rendering so no additional logic is delegated to the component.
Note that key used in code example is for internal use of Vue, as it is a reserved special attribute and is suggested to be present when using v-for loops.

Ember: filter model by text field

I want to filter a model by a text field, the filter should be applied as the user types. I am coming from angular (not a pro but I managed to create such a filter there), so I expected this to be easy. Heh.
list.hbs:
{{input type='text' placeholder='Filter' size='50' valueBinding='searchKeyword'}}
<ol>
{{#each model.articles as |article|}}
<li>{{{article.title}}</li>
{{/each}}
</ol>
I know that this question is asked a lot and I did a lot of research before I decided that the amout of searching is inappropriate for such a problem and that I do not understand some core ideas of ember. It seems like that with the transition from 1.x to 2.x most examples, questions and guides are invalid. My question has been asked multiple times already:
EmberJS filter array of items as the user types
text field filtering a list using ember + ember data
Emberjs - Connecting an {{ input }} filter bar with my list of Objects. As I type, the list filters
but all of the answers use controllers. The docs say that "controllers will be replaced with components". So... how to filter a model the new way?
--
UPDATE
I used Remi's example to create this component:
export default Ember.Component.extend({
filteredArticles: Ember.computed('articles', 'filter', function() {
var keyword = this.get('filter');
var filtered = this.get('articles');
if (keyword) {
keyword = keyword.toLowerCase().trim();
filtered = this.get('articles').filter((item) => item.get('title').toLowerCase().includes(keyword));
}
return filtered;
})
});
the computed property is used as the {{#each filteredArticles... argument and setting a property beforehand (which raised a deprecation warning) is not required anymore.
Components act pretty similar to controllers in some ways. You can think of them as a combination of controller+view in older ember versions. They still work in current ember. But if you want to make your issue work via components, I would suggest the following:
Lest say, you have a /templates/components/list.hbs:
{{input type='text' placeholder='Filter' size='50' valueBinding='searchKeyword'}}
<ol>
{{#each filteredArticles as |article|}}
<li>{{{article.title}}</li>
{{/each}}
</ol>
In one of your templates that should use the components, e.g. /templates/my-list.hbs:
{{list articles=model.articles}}
Then for your component to work, add a /components/list.js:
Ember.Component.extend({
searchKeyword: null,
articles: null,
filteredArticles: null,
updateList: Ember.computed('searchKeyword', function(){
var keyword = this.get('searchKeyword');
var filtered = this.get('articles').filterBy('title', searchKeyword);
this.set('filteredArticles', filtered);
}),
didInsertElement(){
this.set('filteredArticles', this.get('articles'));
}
})

Ember sort data from another controller from an ember-data model association

I have a jsbin: http://jsbin.com/watab/6/
Which is a simplified version of my end goal. Basically I need sort data that is in the hasMany association.
If a User hasMany Books and I want to sort the books for displaying with {{#each book in books}} then how do I sort them?
What I am doing is in the user route during setupController I set the content for the books controller.
this.controllerFor('books').set('content', model.get('books'));
And now in the Users controller I set the needs of course
needs: ['books']
And set an alias
books: Ember.computed.alias('controllers.books.content')
Now I have books controller
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortAscending: false
});
However when I do my {{#each book in books}} it is never sorted.
The jsbin http://jsbin.com/watab/6/ isn't exactly what I described but it employs the basic ideas I have described here.
This is the same jsbin http://jsbin.com/watab/7/ except I changed the sortAscending to true. And I get the same results...
Thanks!
Ok, here is the thing, taking the example of the JSBin here http://jsbin.com/watab/6/, when you're in the 'index' template, your current controller is 'IndexController', that's fine. In that controller you have a property 'users', that's fine; however, that property's content is not sorted, even if you defined the sorting options in the UsersController, that property won't be sorted, why ?, controllers are decorators for your models, so, that code you put for sorting will work whenever you go to the 'users' route, then, the users will be sorted there, that's why it doesn't work in your 'IndexController', because the data is not sorted. To sort it, there are a few ways, but I like this one, in your 'IndexController', add these properties:
userSortProperties: ['name:desc'],
sortedUsers: Ember.computed.sort('users', 'userSortProperties')
and change your template to this:
{{#each user in sortedUsers}}
<div>{{user.name}}</div>
{{/each}}
I hope you get the idea.

Using Knockout.js bindings to display errors on a form

I'd like to use Knockout.js to highlight errors on a form. Some of these errors might be generated through client-side validation, and some of them might come from the server when the form is saved. Ideally, I'd like the template to look like this:
<label data-bind="css: { error: Errors.ProjectName }">Project Name<input data-bind="value: ProjectName" /></label>
If Errors.ProjectName was true-ish, then the above <label> would have a CSS class of error.
However, to do this I think I'd have to make Errors something like:
this.Errors = {
ProjectName: ko.observable(false),
FieldA: ko.observable(false),
FieldB: ko.observable(false),
// ... Every single field
};
Which is a maintenance nightmare, as this form has many, many fields. So, rather than do that, I'd like the model to somehow contain a list of error fields. More like:
this.Errors = ko.observableArray( [] );
When my code becomes aware of an error, I can simply set that array to a list of fields that contain errors:
model.Errors( ['ProjectName'] ); // ProjectName is invalid
The template would then become:
<label data-bind="css: { error: Errors.indexOf('ProjectName') >= 0 }">Project Name<input data-bind="value: ProjectName" /></label>
This works, however it seems rather messy to me having to check observable array indexes in the template. The part of me that's trying to master Knockout demands a cleaner, easier to read method.
Some might argue that Knockout.js is not the right tool to use to display error messages and validate the UI. This is probably a valid opinion. However, I like the idea of having a single model to store errors, and as errors are added or removed from that model, error messages and highlighted fields on the UI automatically reflect these changes, and the state of the data can easily be queried at any time.
Question: What is the cleanest way of implementing error highlighting where the model contains a list of fields in error?
My preference has been to use something like an isValid or hasError sub-observable on an observable to track its state. So, your view model would look like:
this.ProjectName = ko.observable();
this.ProjectName.hasError = ko.observable(); //or can be a computed, if it will handle keeping itself updated
Then, you can bind like:
<label data-bind="css: { error: ProjectName.hasError }">Project Name<input data-bind="value: ProjectName" /></label>
The other nice thing about the "sub-observables" is that they will drop off when converting your data back into JSON to send to the server.
We have an example in the KO docs of using extenders to add the sub-observables: http://knockoutjs.com/documentation/extenders.html#live_example_2_adding_validation_to_an_observable
Also, you may want to look at Knockout-Validation, as it uses a similar approach.

Categories

Resources