How can I repeat a resource in Ember.js - javascript

I have a page resource that uses the page title in the url.
App.Router.map(function () {
this.resource('page', {
path: '/:page_id'
});
});
App.PageRoute = Ember.Route.extend({
serialize: function(model) {
return { page_id: model.title};
}
});
That is working fine in this jsbin. However, I would like to have subpages nested in the url like this:
localhost/#/main_page/sub_page
I tried to make a sub resource (jsbin), but I'm not sure if it is the right approach.
App.Router.map(function () {
this.resource('page', {path: '/:page_id'},
this.resource('subpage', {path: '/:page_id/:subpage_id'}));
});
There are two main problems in my attempt: I have to repeat my page view and it doesn't retain the parent page in the url. I'm getting:
localhost/#/undefined/sub_page
Am I heading in the right direction? Can this be accomplished with just one resource?
Thanks in advance!

Have you considered nesting resources?
App.Router.map(function () {
this.resource('page', {path: '/:page_id'}, function(){
this.resource('subpage', {path: '/:subpage_id'});
 });
});
This would enable at least the URL structure you asked for, but i am not really sure about your requirements.

Most of the modifications I've made are in your html and template. Please, don't mash up the Handlebars' link-to helper with classic anchors () and don't change the link-to tagname attribute, or atleast think twice before doing so.
First - move the testData to a globally accessible object, so that you can use it in the menu:
Javascript:
App.CustomRoutes = [{
id: 1,
title: 'single'
}, {
id: 2,
title: 'subpages',
pages: [{
id: 3,
title: 'subpage1'
}, {
id: 4,
title: 'subpage2'
}, {
id: 5,
title: 'subpage3'
}]
}];
Html:
<ul class="navbar-nav nav">
{{#each page in App.CustomRoutes}}
<li class='menu-item'>
{{#link-to 'page' page.title}}
{{page.title}}
{{#if page.pages}}
<b class="caret"></b>
{{/if}}
{{/link-to}}
<span class='subpages'>
<ul>
{{#each subpage in page.pages}}
<li>
{{#link-to 'page.subpage' page.title subpage.title}}
{{subpage.title}}
{{/link-to}}
</li>
{{/each}}
</ul>
</span>
</li>
{{/each}}
</ul>
Then I fixed your router declaration. When you define nested routes, the third parameter to this.resource() is a function, much alike the function you pass to App.Router.map().
App.Router.map(function () {
this.resource('page', {path: '/:page_id'},
function() { this.route('subpage', {path: '/:subpage_id'});});
});
Finally, here are the definitions of your routes. Because Subpage is nested in Page, the route must be called PageSubpageRoute. If it was nested in Foo, it would have been FooSubpageRoute.
NOTE: The this.render() call inside a router, has the following parameters: (, ).
App.PageSubpageRoute = Ember.Route.extend({
model: function(params) {
var parentModel = this.modelFor('page');
var res = $.grep(parentModel.pages, function (e) {
if (e.title == params.subpage_id) {
return e;
}
});
return res[0];
},
serialize: function(model) {
return { subpage_id: model.title};
},
renderTemplate: function() {
this.render('subpage');
}
});
App.PageRoute = Ember.Route.extend({
serialize: function(model) {
return { page_id: model.title};
},
model: function (params) {
return $.grep(App.CustomRoutes, function (e) {
if (e.title == params.page_id) {
return e;
}
})[0];
}
});
Here is the code: jsbin

Related

How do I pass a variable from a v-bind:class in the template HTML to the component method?

I want my navbar to apply a different style to the current page.
I've built a component that loops through each link. Within the loop I want to pass the current link's url to a method that will check if the current page url is the same as the url of the link. If it is the new style will be applied.
component.js
module.exports = {
data: function() {
return {
links: [
{name: 'Projects', url: "/admin"},
{name: 'Archives', url: "/archives"},
{name: 'Information', url: "/information/1/edit"},
{name: 'Footer', url: "/footer/edit"},
]
}
},
computed: {
currentPage: {
get: function() {
return window.location.pathname
}
},
activePage: {
get: function() {
this.links.forEach((item) => {
item.currentpage = this.currentPage
item.active = false
if (item.url === item.currentpage) { item.active = true}
})
}
}
}
}
component.html
<ul v-for="link in this.links">
<li>
<a :href="link.url" :class="{active: link.active}">{{link.name}}</a>
</li>
</ul>
1) I can't pass a variable from :class to the component computed property or method.
2) In the current scheme, the component renders the array with only one link with the property {active: true}. Yet still the :class won't respond to this.

Refreshing vue-router view after ajax request

I have a vue router with two components,a list and a detail view, with a link back to the list view. On the same page I have a list of the same data that is shown in the list-view component.
The data is fetched via ajax, and as such is not ready when the router-view is drawn. When the data is ready however, the non-router list is updated, but the router view is not.
How do I communicate to the router view that it should redraw with new data?
If I click on an item in the non-router list, the router-view changes to the details component as expected, and if I then click the "show list" it changes back to the list-component, but this time it is populated with the right data.
I have created a js-fiddle that contains the relevant code: https://jsfiddle.net/pengman/jkwvphf9/2/
(It fakes the ajax by using setTimeout, but the effect is the same)
Html code:
<div id="app">
Router-view:
<router-view class="view"></router-view>
<br>
Unrouted list:
<div class="list-group">
<router-link v-for="plante in planter" class="list-group-item" :to="{ name: 'plante', params: { nummer: plante.Nummer }}">{{ plante.Navn }} | </router-link>
</div>
</div>
<template id="plante-listing-template">
<ul>
<li v-for="plante in planter">
{{ plante.Navn }} <router-link :to="{ name: 'plante', params: { nummer: plante.Nummer }}">Vis</router-link>
</li>
</ul>
</template>
<template id="plante-detail-template">
<div>
plante detail template:
<h3>{{ plante.Navn }}</h3>
<router-link to="/">Show List</router-link>
</div>
<br>
</template>
Javascript code:
var PlanteListing = {
template: '#plante-listing-template',
data: function () {
return {
planter: this.$parent.planter
}
},
watch: {
'$route'(to, from) {
// vi skal opdatere data, saa vi skal beregne igen
this.planter = this.$parent.planter;
},
'dataloaded'() {
this.planter = this.$parent.planter;
}
}
};
var PlanteDetail = {
template: '#plante-detail-template',
data: function () {
var parent = this.$parent;
var nummerFromRoute = this.$route.params.nummer;
//console.log(nummerFromRoute);
var filtered = this.$parent.planter.filter(function (item) {
return (item.Nummer == nummerFromRoute) ? item : false;
});
return {
plante: filtered[0]
}
},
watch: {
'$route'(to, from) {
// vi skal opdatere data, saa vi skal beregne igen
var nummerFromRoute = this.$route.params.nummer;
var filtered = this.$parent.planter.filter(function (item) {
return (item.Nummer == nummerFromRoute) ? item : false;
});
this.plante = filtered[0];
},
}
};
var router = new VueRouter({
mode: 'hash',
base: window.location.href,
routes: [
{ path: '/', component: PlanteListing },
{ name: 'plante', path: '/:nummer', component: PlanteDetail }
]
});
var app = new Vue({
router,
data: {
planter: []
},
components: { PlanteListing: PlanteListing },
methods: {
getJson: function () {
var self = this;
/* Real code:
$.getJSON("content/planter.json", function (param) {
this.planter = param;
}.bind(this));
*/
/* Simulation code: */
setTimeout(function(){self.planter = [ { "Nummer": "0", "Navn": "Bertha Winters" }, { "Nummer": "1", "Navn": "Jeannie Small" }, { "Nummer": "2", "Navn": "Mckay Joyner" }, { "Nummer": "3", "Navn": "Janelle Banks" }, { "Nummer": "4", "Navn": "Bray Moran" }, { "Nummer": "5", "Navn": "Hooper Schwartz" }]; console.log('data loaded')}, 500);
}
},
created: function () {
this.getJson();
}
}).$mount('#app');
Typically you do not want to have a component reach out of itself to get data (as you are doing with this.$parent.planter). Instead, you want to pass it props. To that end, I've modified your code a bit.
The first thing is I upgraded your vue-router to the latest version. This allows you to use the props argument on routes.
var router = new VueRouter({
mode: 'hash',
base: window.location.href,
routes: [
{ path: '/', component: PlanteListing },
{ name: 'plante', path: '/:nummer', component: PlanteDetail, props: true }
]
});
Secondly, you are using planter in all your routes, so I have provided it as a property on the router-view.
<router-view class="view" :planter="planter"></router-view>
This allows us to clean up your component routes and add the data they need as props.
var PlanteListing = {
template: '#plante-listing-template',
props:["planter"]
};
var PlanteDetail = {
template: '#plante-detail-template',
props:["planter", "nummer"],
data: function () {
var filtered = this.planter.filter(item => item.Nummer == this.nummer);
return {
plante: filtered[0]
}
}
};
There is no need to tell the router to redraw; because the data are props, Vue just takes care of that for us.
Here is your updated fiddle.

Rendering a component directive in Vue 2 without params

I have an app that holds the student list data. The component is supposed to take in that list and render a select dropdown (with select2).
In the fiddles console, it's displaying jQuery is not defined. I thought all fiddles now included jQuery?
I'm really not sure why this is breaking the all together. Is there something wrong with my directive? I know with Vue 2.0 they removed params, but this should suffice. Any eyes on my code would be greatly appreciated.
// Define component
var studentsComponent = Vue.extend({
props: ['students'],
data(): {
return {}
},
methods:{},
directives: {
select: {
bind: function () {
var self = this;
var select = $('#select-student');
select.select2();
select.on('change', function () {
console.log('hey on select works!');
});
},
update: function (oldVal, newVal) {
var select = $('#select-student');
select.val(newVal).trigger('change');
}
},
},
template: `
<div>
<select
ref=""
id="select-student"
v-select>
<option value="0">Select Student</option>
<option
v-for="(student, index) in students"
:value="student.id">
{{ student.name }}
</option>
</select>
</div>
`,
});
// Register component
Vue.component('students-component', studentsComponent);
// App
new Vue({
el: '#app',
data: {
students: [
{ name: 'Jack', id: 0 },
{ name: 'Kate', id: 1 },
{ name: 'Sawyer', id: 2 },
{ name: 'John', id: 3 },
{ name: 'Desmond', id: 4 },
]
},
});
I made a fiddle https://jsfiddle.net/hts8nrjd/4/ for reference. Thank you for helping a noob out!
First, as I mentioned in comments, I would suggest you do this with a component. If you had to stick with a directive, however, you can't initialize select2 in the bind hook. You've defined your options in the DOM, so you need to wait until the component is inserted to initialize it.
directives: {
select: {
inserted: function (el, binding, vnode) {
var select = $(el);
select.select2();
select.on('change', function () {
console.log('hey on select works!');
});
},
},
},
Here is an update of your fiddle.

Retrieving a display results from a search

I'm relatively new to meteor.js and I'm trying to get a search form to work. So far I'm not even trying to get the params to work, but it will come later.
I'm basically trying to get a bunch of lifts to display.
lib/router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return Meteor.subscribe('lifts');
}
});
Router.route('/', { name: 'liftsList' });
Router.route('/lifts/search/:from-:to-:when', {
name: 'liftsSearch',
waitOn: function() {
return Meteor.subscribe('liftsSearch');
}
});
server/publications.js
Meteor.publish('liftsSearch', function() {
var query = { fromLoc: { $near : {
$geometry: {
type : "Point" ,
coordinates: [ 6.11667, 45.9 ]
} },
$maxDistance : 50
}};
return Lifts.find(query);
});
If I try to display the results with Lifts.find(query).fetch(), it returns actual results.
client/lifts_search.html
<template name="liftsSearch">
<div class="container">
<h3>Lifts search results {{hi}}</h3>
<div class="lifts">
{{#each lifts}}
hi
{{> liftItem}}
{{/each}}
</div>
</div>
</template>
Here I simply got no lifts displaying, not even the little "hi" string.
Thanks
Unless there's code that you haven't included, {{#each lifts}} isn't rendering because you're not defining lifts anywhere. Just because you're populating the Lifts collection, the template doesn't automatically known that lifts refers to it (largely because that would be totally arbitrary - what exact query would it refer to?).
So, you need to define lifts in either a router data function:
Router.route('/lifts/search/:from-:to-:when', {
name: 'liftsSearch',
waitOn: function() {
return Meteor.subscribe('liftsSearch');
},
data: function() {
return {
lifts: Lifts.find() // add a query if you want
}
}
});
Or in a template helper:
Template.liftsSearch.helpers({
lifts: function() {
return Lifts.find(); // add a query if you want
}
});

Refresh view on Ember Data update

I’m doing a very basic application with Ember and Ember Data.
For some reason I always have the same problem. My application renders and displays the data correctly, but if I remove and search, it doesn't update the view.
I’ve already asked this here—the link has more code examples—but with not much luck. Here is how I’m trying to do it:
App = Ember.Application.create({
LOG_TRANSITIONS: true, LOG_VIEW_LOOKUPS: true
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Sample = DS.Model.extend({ name: DS.attr('string') });
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('sample');
}
});
App.IndexController = Ember.ArrayController.extend({
actions: {
remove: function(sample) {
sample.destroyRecord();
}
}
});
App.Sample.FIXTURES = [
{ id: 1, name: 'Learn Ember.js'},
{ id: 2, name: 'Record 2' },
{ id: 3, name: 'Test Delete' }
];
App.ApplicationRoute = Ember.Route.extend({
actions: {
showModal: function(name, content) {
this.controllerFor(name).set('content', content);
this.render(name, {
into: 'application',
outlet: 'modal'
});
},
removeModal: function() {
this.disconnectOutlet({
outlet: 'modal',
parentView: 'application'
});
}
}
});
App.MyModalComponent = Ember.Component.extend({
actions: {
ok: function() {
this.$('.modal').modal('hide');
this.sendAction('ok');
}
},
show: function() {
this.$('.modal').modal().on('hidden.bs.modal', function() {
this.sendAction('close');
}.bind(this));
}.on('didInsertElement')
});
From your code I have tried to come up with a reasonable solution for your problem
Before I get into the solution I think the controller should be IndexController rather than sampleDeleteModalController because ember expects controller to have same name as the route.
App.SampleDeleteModalController = Ember.ObjectController.extend({
actions: {
remove: function() {
// Two ways
this.get('model').destroyRecord();
this.transitionToRoute('index');
}
}
});
transitionToRoute from the same route will not refresh a view.This will work only when you want to redirect to another route.
Solution to refresh view
option 1 : you can capture the same action inside index route after removing the record you can do this.refesh() which refreshes the model.
option 2 : You have to explicitly update the binded model inside the controller.
actions: {
remove: function() {
// Two ways
var localCopy = this.get('model');
localCopy.destroyRecord();
this.set('model',localCopy);
}
}
option 3 : After you set your model your model and then do this.rerender().Which is almost equivalent to window.reload()

Categories

Resources