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?
Related
I am passing data from a blade file to a Vue component. I am passing a collection of programs to Vue and in the collection of programs is an array and within the array are attributes. One of the attributes is a route attribute. Since the route is coming from Laravel and not vue router, (I am not using vue-router) the route has the blade file extension appended to it as it would be used in a controller when returning a view: so the way the routes are being returned right now in my Vue Component resembles:
route: program1.index,
program2.index
program3.index
In my index.blade.php file I bind the programs and pass it to vue:
<programs :programs="{{App\Programs::all()}}">
</programs>
In Programs.vue I loop through the programs with v-for and am properly accessing the route like so:
<template>
<div>
<a :href="program.route"></a>
</div>
</template>
I am using this computed property which properly strips off the .index from the routes, but when I check the value of formattedRoutes in the vue console it is undefined.
computed: {
formattedRoutes() {
this.programs.filter(obj => {
return obj.route.replace(/\.[^/.]+$/, "");
});
}
}
This is me calling the computed property in the html:
<template>
<div>
<a :href="program.formattedRoutes"></a>
</div>
</template>
The program.formattedRoutes returns undefined I can't figure out why.
Two problems here...
Your computed property doesn't return anything
Computed properties should be pure functions. They should not manipulate any data, otherwise you can end up in an infinite loop since computed property functions execute when data changes
I would instead create a computed property to return programs with your transformed routes. For example
computed: {
linkedPrograms: vm => vm.programs.map(program => ({
...program,
route: program.route.replace(/\.\w+$/, "")
}))
}
Now iterate linkedPrograms instead of programs and use the transformed route property in your links. For example (and this is purely guess work because you didn't show how you were iterating programs in your template)...
<!-- I'm just guessing with the "id" and "name" properties -->
<div v-for="program in linkedPrograms" :key="program.id">
<a :href="program.route">{{ program.name }}</a>
</div>
I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.
I'm new to Ember and I would like to know which is the best practice to handle this common scenario.
I would like to display inside the index page a form for creating some model, and after that show 4 nodes with little-info about the last 4 of that model store.
So, my first idea was something like:
app/index/template.hbs (im using pod's)
...
<div>
{{form-creator-component o=model}}}
</div>
<div>
<h3>Last 4</h3>
{{#each model2 as |element| }}
{{litle-info o=element }}
{{\each}}
</div>
.....
But for doing that, I would need to have 2 models in the same template (and route), which looks kind of weird and I've seen nowhere in docs nor in guides. So, I assume I'm doing something in the wrong way :)
Thanks in advance.
If you need two different sets of Async data from a single model hook, the best approach is to use Ember.RSVP.hash.
This will await multiple promises and then resolve to an object.
For instance if you had blogs and comments you needed to load, your route would look like this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
blogs: this.store.findAll(`blog`),
comments: this.store.findAll(`comment`),
});
}
});
Then in your template, you can use this data as model.blogs and model.comments.
I am trying to pass a second model to a component. I have several books that are ordered by categories. The categories are chosen by a Multi-Select-Box and I need to pass the categories (which are stored in my db) to the component. I dont need to change the categories I just need an array. Where and how do I request them and pass them on, if i dont want to inject the store into the component? (There is no way to build a many-to-many relationship, the books only have a string-array for the categories).
Right now I am trying to do this:
books/route.js
model() {
this.store.findAll('book');
}
afterModel() {
this.set('categories', this.store.findAll('category');
}
books/template.hbs
{{#each books as |book|}}
{{book-details book=book categories=categories}}
{{/each}}
components/book-detail/template.hbs
<h2>{{book.title}}</h2>
{{#each categories as |category|}}
<p>{{category.name}}</p>
{{/each}}
It doesn't work for the categories like this, so I need to find a way to get them from the store and pass them on to my component.
If it's not necessary for you to fetch categories afterModel, I recommend considering this approach and dropping your afterModel implementation.
model() {
return {
books: this.store.findAll('book'),
categories: this.store.findAll('category')
}
}
Personally, I've moved towards defining a route's model only if there's a single model associated with the route in question (i.e. /books/thug-kitchen). In the case where I have multiple models that apply for a given route I favor using properties.
A better approach would be to use Ember.RSVP.hash, as follows:
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
categories: this.store.findAll('category')
});
},
setupController: function(controller, models) {
controller.setProperties(models);
}
This code will work most correctly if the response from the server is delayed. Tyler's approach works, but the transition to the route happens too soon, before the find is complete. I think his approach works because you're using Ember Data, but it doesn't work if it's a more generic Promise. I wish I could explain better, but my own understanding is still very basic.
After that you shouldn't have any problem passing both models on to the component.
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'));
}
})