Ember cli pagination - unable to receive model? - ember.js

Working with Ember 3.19 and trying to use ember-cli-pagination. I have all my posts from JSONplaceholder in the data-store under model type 'post'. I was able to view all the posts from the data-store without pagination but have been unsuccessful in implementing ember-cli-pagination. Console shows currentPage and totalPages as undefined. The Articles element shows in the ember inspector but blank in chrome. The PageNumbers element appears but it is rendered as <<< ... NaN >>>
Controller - articles.js
import Controller from "#ember/controller";
import { tracked } from "#glimmer/tracking";
import { alias, oneWay } from "#ember/object/computed";
import pagedArray from "ember-cli-pagination/computed/paged-array";
import { inject as service } from '#ember/service'
export default class ArticlesController extends Controller {
// setup our query params
queryParams: ["page", "perPage"];
// set default values, can cause problems if left out
// if value matches default, it won't display in the URL
#tracked page = 1;
#tracked perPage = 10;
// can be called anything, I've called it pagedContent
// remember to iterate over pagedContent in your template
#pagedArray('model', {
page: alias("parent.page"),
perPage: alias("parent.perPage"),
})
pagedContent;
// binding the property on the paged array
// to a property on the controller
#oneWay("pagedContent.totalPages") totalPages;
}
Handlebar - articles.hbs
<h2>Posts</h2>
<div>
<ul>
{{#each #pagedContent as |post|}}
<li>User: {{post.user}} Title: {{post.title}} - {{post.body}}</li>
{{/each}}
</ul>
</div>
<PageNumbers #content={{#pagedContent}} />
Model - post.js
import Model, { attr } from '#ember-data/model';
export default class ArticleModel extends Model {
#attr title;
#attr body;
#attr userId;
}

The issue is in the articles.hbs file:
Since the pagedContent is defined in the corresponding controller and not any kind of argument, the property has to be used with this and not with #. Hence change this template code should work.
<h2>Posts</h2>
<div>
<ul>
{{#each this.pagedContent as |post|}}
<li>User: {{post.user}} Title: {{post.title}} - {{post.body}}</li>
{{/each}}
</ul>
</div>
<PageNumbers #content={{this.pagedContent}} />
Also, there is a typo in the controller file. Since this is a class component, the qps has to defined like:
queryParams = ["page", "perPage"];

Related

Add Extra Actions to LinkTo in Octane

I have a dropdown menu with links, when the links are clicked I'd like the menu to close.
Something like (a11y/i18n truncated):
<ul class="menu">
<li>
<LinkTo #route="myprofile">
Profile
</LinkTo>
</li>
<li>
<LinkTo #route="logout">
Logout
</LinkTo>
</li>
</ul>
I'd like to add an additional click handler to the link to, something like:
<ul class="menu">
<li>
<LinkTo #route="myprofile" {{on "click" this.closeMenu}}>
Profile
</LinkTo>
</li>
<li>
<LinkTo #route="logout" {{on "click" this.closeMenu}}>
Logout
</LinkTo>
</li>
</ul>
However this makes the LinkTo useless as it reloads the page as if following a link instead of transitioning to a new route. We're currently doing this using hember-link-action, but I'd love to find a more idiomatic way to approach this problem.
If you need to perform additional logic, you may implement redirect in an action instead of using the LinkTo helper. To do so, you need to inject RouterService into your component and then call its transitionTo method. Something like:
export default class ExampleComponent extends Component {
#service router;
#action
navigate(route) {
this.menuExpanded = false;
this.router.transitionTo(route);
}
}
Note that there also exist the transitionTo() method from Route and transitionToRoute() from Controller that behave like the LinkTo helper. But those methods are deprecated now, and using RouterService is a recommended idiomatic way of doing transitions in js code.
I've written a component to mostly handle this, but I'm quite certain there are more edge cases in LinkTo then I've covered (for example it doesn't cover a passed model or list of models). I called this <LinkToWithAction /> and it looks like:
<a href={{this.href}} class={{if this.isActive "active"}} {{on "click" this.navigate}} >
{{yield}}
</a>
import Component from '#glimmer/component';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LinkToWithActionComponent extends Component {
#service router;
get href() {
return this.router.urlFor(this.args.route, {
queryParams: this.queryParams,
});
}
get isActive() {
return this.router.isActive(this.args.route);
}
get queryParams() {
return this.args.queryParams ?? {};
}
#action
navigate(evt) {
evt.preventDefault();
this.args.action();
this.router.transitionTo(this.args.route, {
queryParams: this.queryParams,
});
}
}
and it is called as:
<LinkToWithAction
#route="mymaterials"
#action={{set this.isOpen false}}
#queryParams={{hash course=null}}
>
{{t "general.myprofile"}}
</LinkToWithAction>
This is made more annoying by this issue with transitionTo that adds unset queryParams to the URL when called which effects the public router service. The built in component uses the private internal router where this behavior doesn't exist, and it may be worth using that private service, but for now we're going to live with passing the query params.

EmberJS show router model in application.hbs

I am working with Ember.js, and I am trying to make my pages display the title of the page just below the navbar. To make this work I have tried to use model hook, and show it in the application.hbs file.
So far I have tried variations of this:
routes/contact.js
import Route from '#ember/routing/route';
export default class ContactRoute extends Route {
model() {
return 'Title of page';
}
}
templates/application.hbs
<div>
<NavBar />
<div class="pageTitle">
<h2>
<p>{{#model}}</p>
</h2>
</div>
<div class="body">
{{outlet}}
</div>
</div>
I've mostly tried to mess around with #model in application.hbs things like outlet.#model. But all of these attempts have resulted in empty titles or Template Compiler Errors.
Does anyone have a solution for this? Preferably one that does not involve jquery?
If I understand correctly what you are trying to accomplish, it is a good use case for services.
You need a couple parts. A service to keep track of the page title, and then you need to inject that service in the application controller so the template has access to the service, and also to inject the page title service in the routes so you can update the page title in the respective hooks.
Page service
import Service from '#ember/service';
import { tracked } from '#glimmer/tracking';
export default class extends Service {
#tracked title = "Your App"
}
Application controller and template
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
export default class ApplicationController extends Controller {
#service pageTitle;
}
<h1>Welcome to {{this.pageTitle.title}}</h1>
<br>
<br>
{{outlet}}
<LinkTo #route="my-route">my route</LinkTo>
<br>
<br>
MyRoute route updating the page title value in a model hook
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class extends Route {
#service pageTitle;
model() {
this.pageTitle.title = "My Route"
}
}
I have put it all together in an interactive Ember Twiddle demo.
Hope this helps!
Since you have created a new page (route) named contact, the UI part of the page has to be in the corresponding template file, i.e., templates/contact.hbs and not templates/application.hbs as the templates/contact.hbs file can only access the #model of routes/contact.js
ie., the below markup has to in templates/contact.hbs file and will be displayed when accessing the page at http://localhost:4200/contact
<div class="pageTitle">
<h2>
<p>{{#model}}</p>
</h2>
</div>
Also, note that the markup present in the templates/contact.hbs file will be rendered in the place of application template's {{outlet}} (see here)
For a detailed reference, check this twiddle

Filtering an array in ember

Ok so I'm fairly new to programing, I know how to run a filter on a JSON Array but I cant seem to figure it out when I'm pulling the data from firebase and viewing it in an Ember app.
this is my route.js code:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('user');
}
});
This is my template.hbs code the href="#!" is the generic from materialize for the dropdown buttons:
<div class="list-wrapper col-xs-10 col-xs-offset-1">
<div class="col-xs-12 button-div">
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li>Female</li>
<li>Male</li>
<li>All</li>
{{/md-btn-dropdown}}
</div>
{{#each model as |info|}}
<div class="col-xs-3 user-card">
<div class="card-info">
<ul>
<li>Last Name- {{info.lastName}}</li>
<li>First Name- {{info.firstName}}</li>
<li>Gender- {{info.gender}}</li>
<li>{{info.email}} </li>
</ul>
</div>
</div>
{{/each}}
</div>
{{outlet}}
This is my controller.js code which I no is all wrong:
import Ember from 'ember';
export default Ember.Controller.extend({
customFilter: function(gender) {
return function(el) {
var r = el.user;
return r.gender === gender;
};
}
});
and this is my model:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
lastName: DS.attr('string'),
firstName: DS.attr('string'),
gender: DS.attr('string'),
email: DS.attr('string')
});
I've searched high and low and I'm sure I'm just missing something basic and stupid. What I want is for the dropdown menu to be able to filter and display only female, male or all. Again I'm new to this stuff so I apologize if this is a pretty basic thing. Thank You
What your missing is an action that updates your controller when an item in the dropdown is actually selected.
Some helpful reading:
Triggering changes with actions
Computed Properties
Here's how to put actions in your dropdown component
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li><a {{action "filterUpdated" "female"}}>Female</a></li>
<li><a {{action "filterUpdated" "male"}}>Male</a></li>
<li><a {{action "filterUpdated"}}>All</a></li>
{{/md-btn-dropdown}}
In your controller you then need to handle this action like so:
import Ember from 'ember';
export default Ember.Controller.extend({
// the people property is an alias of the model object
// which essentially makes people a synonym for model
// read more http://emberjs.com/api/classes/Ember.computed.html#method_alias
people: Ember.computed.alias('model'),
// holds the currently selected gender, e.g., "female". A null value indicates there is no filter.
currentFilter: null,
/*
filteredPeople is a computed array containing people models.
The array is recomputed every time the model changes or the currentFilter changes,
see the .property() bit at the end.
read more: http://emberjs.com/api/classes/Ember.computed.html#method_filter
*/
filteredPeople: Ember.computed.filter('people', function(person/*, index, array*/) {
// this function is passed each item in the model array, i.e., the person argument
// there's no need to use the index nor array arguments, so I've commented them out
if(this.get('currentFilter') === null) {
// when the filter is null, we don't filter any items from the array
return true;
} else {
// otherwise only return true if the gender matches the current filter
return person.gender === this.get('currentFilter');
}
}).property('people', 'currentFilter'),
actions: {
filterUpdated: function (value) {
if (Ember.isEmpty(value)) {
// an empty value means we should clear the filter
this.set('currentFilter', null);
}
else {
this.set('currentFilter', value);
}
}
}
});
Finally, edit your template to change
{{#each model as |info|}}
to
{{#each filteredPeople as |info|}}
Also at a meta level, don't apologize for asking questions! Everyone is new at something at somepoint, and often asking is the best way to learn. That's what stackoverflow is all about :)
Something like this would work:
gender: 'All',
filteredModel: Ember.computed.filter('model', function(person) {
return person.gender === this.get('gender');
}).property('gender'),
this assumes that it starts on all, and then when the dropdown changes the value of gender, then the filteredModel will get updated. You can then in your hbs file change the result to:
{{#each filteredModel as |info|}}

replace deprecated Ember.ObjectController used in View in ember.js

I am using Ember.js to create a one page map editing software.
In my app, I use a model to represent the layer's state of the map and to associate it with an actual openlayers' layer.
There is a summary of my work:
in my entry point map.hbs, I call the mapLayers view:
{{view "mapLayers"}}
Here is the mapLayers view definition:
export default Ember.View.extend({
templateName: "mapLayers",
classNames: ["map-layers"]
});
The mapLayers template :
<ul>
{{#each layer in tileLayers itemController="mapLayer"}}
<li {{bind-attr id=layer.identifier}}>
<a>
<label class="hint--top" {{bind-attr data-hint=layer.title}}>
{{str-sub layer.title 20}}
</label>
{{input class="range" type="range" name="range" min="0" max="100" value=layer.opacity}}
</a>
</li>
{{/each}}
</ul>
And the mapLayer controller:
export default Ember.ObjectController.extend({
opacity: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
console.log("get layer opacity: " + model.get('opacity'));
return model.get('opacity') * 100;
} else {
// property being used as a setter
model.set('opacity', value / 100);
model.get('layer').setOpacity(value / 100);
model.save();
return value;
}
}.property('model.opacity')
});
As you see, I am using the proxy ObjectController to modify on the fly the values set and get in the view.
I'm trying to understand how to remove the ObjectController without success.
I tried to change to Ember.Controller but how can I proxy my model properties then??
I read this without help:
OBJECTCONTROLLER
Experienced Ember users have enjoyed the use of proxying behavior in
the Ember.ObjectController class since 1.0. However, this behavior
will be removed in Ember 2.0 as the framework migrates to routable
components.
New users hit three roadbumps when learning about the object
controller pattern.
Given a certain model, which of the three controller options should I
be using? Which controller is generated by the framework if I do not
specify one? When using an object controller, why should the this
context not be passed to actions if it has the properties of my model?
For these reasons, the Road to Ember 2.0 RFC listed object controllers
as a concept to be removed from the framework.
To migrate from an explicitly defined object controller, first convert
the class definition to inherit from Ember.Controller. For example:
import Ember from "ember";
// Change: export default Ember.ObjectController.extend({ // To:
export default Ember.Controller.extend({
// ...
Next update any use of {{modelPropertyName}} in templates with
{{model.modelPropertyName}}. You should also review any computed
property dependent keys, observer keys, and get and set statements on
the route and controller.
Instead of proxying you just have to fully qualify you are getting the property off of the model, instead of the controller, which is what is in scope in your template and controller.
Template
<ul>
{{#each layer in tileLayers itemController="mapLayer"}}
<li id={{layer.model.identifier}}>
<a>
<label class="hint--top" data-hint={{layer.model.title}}>
{{str-sub layer.model.title 20}}
</label>
{{input class="range" type="range" name="range" min="0" max="100" value=layer.opacity}}
</a>
</li>
{{/each}}
</ul>
Controller
export default Ember.Controller.extend({
opacity: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
console.log("get layer opacity: " + model.get('opacity'));
return model.get('opacity') * 100;
} else {
// property being used as a setter
model.set('opacity', value / 100);
model.get('layer').setOpacity(value / 100);
model.save();
return value;
}
}.property('model.opacity')
});

How to iterate over class variables from Handlebars in ember-cli?

I'd like to iterate over a class variable array in Handlebars in ember-cli to generate a list of checkboxes (categories), with the appropriate ones checked off based on which categories the model belongs to.
I have several issues:
- I can't figure out how to access the class variable in ember-cli. I've seen tutorials showing that in ember.js, it's just App.Listing.CATEGORIES, but I'm not getting any passes through my each loop.
- How to check off the appropriate box? I have some janky code below that probably doesn't work.
listing.js:
import DS from "ember-data";
var Listing = DS.Model.extend({
categories: DS.attr(), // string array
});
Listing.reopenClass({
CATEGORIES: ['park', 'outdoors']
});
export default Listing;
show.hbs:
<ul>
{{#each category in CATEGORIES}}
<li>{{input type="checkbox" name=category checked=categories.contains(category)}} {{category}}</li>
{{/each}}
</ul>
Handlebars templates can’t look up classes like that, nor does complex logic like categories.contains(category) work. You’ll need to add a computed property to the controller or component to supply proxy objects to the template. Assuming it’s a controller, here’s a rough example:
export default Ember.Controller.extend({
selectableCategories: function() {
var model = this.get('model');
return model.constructor.CATEGORIES.map(function(category) {
var categoryProxy = Ember.Object.create({
model: model,
name: category,
checked: function(key, value) {
var model = this.get('model');
// setter; the checkbox value has changed
if (arguments.length > 1) {
if (model.get('categories').contains(this.get('name'))) {
model.get('categories').removeObject(this.get('name'));
}
else {
model.get('categories').addObject(this.get('name'));
}
}
// getter; the template is checking whether the checkbox should be checked
return model.get('categories').contains(this.get('name'));
}.property('model.categories')
});
return categoryProxy;
});
}.property('model.categories')
});
The selectableCategories computed property returns an array of objects that observe the model’s categories attribute and represent whether or not each category is found within it.
Then in your template, you can use the proxy objects like this:
{{#each category in selectableCategories}}
{{input type="checkbox" name=category.name checked=category.checked}} {{category.name}}
{{/each}}