ember set component data from application controller - javascript

I am new to Ember and trying to figure out how the data routing works. I have a 'page-notices' component and template thats included in my application.hbs file. It handles showing error or other notifications to users. I cannot figure out how to set the data inside the component from the application controller.
When a user triggers a logout action in application controller, I send it to the server via ajax json request and then need to update the page-notices component if there was an error returned. What does the page-notices controller need to look like to get this done? Am I asking the wrong question and shouldn't be using the controller for this?
//app/templates/application.hbs
{{app-header}}
<div id="pagecontent">
{{page-notices}}
<div id="wrapper">
{{outlet}}
<div class="push"></div>
</div>
</div>
{{app-footer}}
//app/controllers/application.js
import Ember from 'ember';
import ENV from '/config/environment';
var $ = Ember.$;
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
pagenotices: Ember.inject.controller("page-notices")
actions: {
logout: function() {
var self = this;
$.ajax({
dataType: "json",
method: 'GET',
url: ENV.APP.apiHost,
data: {p: 'logout'},
success: function( response ){
if( response.success || (response.loggedin == false) ){
self.get('session').invalidate();
self.transitionToLoginRoute();
} else {
self.get('pagenotices').set('pageerrors', response.error);
self.get('pagenotices').set('pageerrorsview', '');
}
}
});
},
},
transitionToLoginRoute: function() {
this.transitionToRoute('login');
},
});
//app/templates/components/page-notices.js
<div id="pagenotices" class="{{pagenoticeview}}">
<div id="pageerrors" class="error centered {{pageerrorsview}}">{{pageerrors}}</div>
<div id="pagemessages" class="notice centered {{pagemessagesview}}">{{pagemessages}}</div>
</div>
//app/components/page-notices.js
import Ember from 'ember';
import ENV from '/config/environment';
const { inject: { service }, Component } = Ember;
export default Component.extend({
pagenoticeview: 'hide',
pageerrors: '',
pageerrorsview: 'hide',
pagemessages: '',
pagemessagesview: 'hide',
});

I did not quite understand why you injected page-notices controller to application controller; because you have already put page-notices component directly to application.hbs. I might be wrong but I got the sense that you are confusing a controller and a component.
Anyway,the following should work.
Remove pagenotices: Ember.inject.controller("page-notices") this; since we have no work with pagenotices controller as I explained above.
Change the else part in ajax handler of logout action within application.js as follows:
self.set('pageerrors', response.error);
self.set('pageerrorsview', '');
So that the corresponding attributes are directly saved to application controller itself.
Pass the corresponding attributes to page-notices component from within application.hbs with
{{page-notices pageerrors=pageerrors pageerrorsview=pageerrorsview}}
Declare the initial values of pageerrors and pageerorsview within application.js and remove them from page-notices component if you want. I mean declarations of pageerrors: '', pageerrorsview: 'hide'
This should work if I got what you are asking right, best regards.

Related

Content is undefined when trying to work with a record in Ember

I am trying to update a record in the Ember store. When I try to do this, it returns the following error:
Uncaught Error: Assertion Failed: Cannot delegate set('name', test) to the 'content' property of object proxy : its 'content' is undefined.
The controller looks like this:
import Ember from 'ember';
export default Ember.Controller.extend({
model: null,
event: {
name: "test",
id: "adfg8943224xcvsdf"
},
actions: {
editEvent (event) {
var Event = this.store.find('event', event.id);
Event.set('name', event.name);
Event.save()
}
}
});
The route looks like this:
import Ember from 'ember';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model () {
return {
event: this.store.find('event')
}
},
setupController (controller, model) {
controller.set('model', model);
}
});
The template triggers the action, sending along a object called event, which has properties like name and id. The values of the event object come from the controller and have been set before triggering the editEvent action:
<form {{action 'editEvent' event on="submit"}}>
I believe what is happening is that your model hook is returning a POJO that contains a promise that will resolve. If you want to pass that to your action then you need to do
<form {{action 'editEvent' model.event on="submit"}}>
That being said you should really just return a promise from your model hook so that Ember will wait for your data to load before rendering the template. With the way you have it setup now, if your data takes a long time to load, someone could submit the form before the model is loaded and you'll get an error.
I think you want your route to look like this (no need to override setupController):
import Ember from 'ember';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model () {
return this.store.find('event');
}
});
Then in your template:
<form {{action 'editEvent' model on="submit"}}>
If you need to load multiple models then you should use Ember.RSVP.hash.
See this answer: EmberJS: How to load multiple models on the same route?
Also, I'm not quite sure what your action is trying to do but you don't need to find the record again. The code you posted for your action doesn't actually do anything. It gets the event and then sets the event's name to its own name.
actions: {
editEvent (event) {
// you already have the event, you passed it in as a parameter
// you don't need to query the store again for it.
var Event = this.store.find('event', event.id);
// This doesn't do anything as it just sets the event.name to itself
Event.set('name', event.name);
Event.save()
}
}
I think you mean to do this:
actions: {
editEvent (event) {
event.set('name', 'updated name');
event.save();
}
}

Ember 2.0 router does not load model data?

I have in my router.js:
Router.map(function() {
this.route('portfolio', function() {
this.route('company', { path:'/company/:id' });
});
}
And in my routes/portfolio/company.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
var companyId = params.id;
return new Ember.RSVP.hash({
company: Ember.$.ajax({ url: '/api/company/'+companyId, dataType: "json", type: 'GET' })
}).then(function(message) {
return message;
}, function(error) {
console.log( error );
});
}
});
My route and template is loading fine, when I navigate to app/portfolio/company/1, but for some reason when I navigate to that route, Ember wont load the model (no error, but the {{model}} variable does not get populated in template). Only when I refresh the page, Ember loads the model?! I am a bit confused now...
Edit: added missing param and added better description
I think in your template or in controller you are using model like so
model.company replace it with model, and remove extraneous RSVP.hash
because Ember.$.ajax already returns promise which model hooks can handle
so in ES6 (ember-cli supports it) your model hook should look like this
model({ id }) {
return Ember.$.ajax('/api/company/' + id);
}
with above things everything should work, what was happening I think you were passing just model to {{link-to}} while your controller or template expecting model.company so was breaking things

Get data attributes from v-link

Sure it's a easy thing but at the moment i just can't get it to work. I have a component called posts.vue which includes this link in the template:
<template>
<a v-link="{ name: 'post', params: { slug: post.post_name }}" v-postid="{ post.ID }"><h2>{{ post.post_title }}</h2></a>
</template>
My Routes are set up like this:
'/post/:slug': {
name: 'post',
component: require('./components/post.vue')
}
Now my question is how can i get the postid data attribut in my post.vue component which gets loaded after the route matches.
In my old jquery days i would do it like that:
$('a.link').click(function() {
var attr = $(this).data('post-id');
}
(Just to showcase what i meant)
Thanks for any help guys! Vue.js is great so far, enjoy it alot.
Assuming you have a post object inside your component that you set with ajax after the route is loaded
data: function() {
return {
post: {}
}
}
and
route: function() {
data: function() {
var resource = fetchPost(this.$route.params.slug)
this.$set('post', resource)
}
}
You can access the post.id everywhere in your component like this this.$data.post.id
The v-postid directive is not required for that.

Vue.js - Global Data from AJAX Call

I'm giving Vue.js a try and so far I'm loving it because it's much simpler than angular. I'm currently using vue-router and vue-resource in my single page app, which connects to an API on the back end. I think I've got things mostly working with a the primary app.js, which loads vue-router and vue-resource, and several separate components for each route.
Here's my question: How do I use props to pass global data to the child components when the data is fetched using an asynchronous AJAX call? For example, the list of users can be used in just about any child component, so I would like the primary app.js to fetch the list of users and then allow each child component to have access to that list of users. The reason I would like to have the app.js fetch the list of users is so I only have to make one AJAX call for the entire app. Is there something else I should be considering?
When I use the props in the child components right now, I only get the empty array that the users variable was initialized as, not the data that gets fetched after the AJAX call. Here is some sample code:
Simplified App.js
var Vue = require('vue');
var VueRouter = require('vue-router')
Vue.use(VueRouter);
var router = new VueRouter({
// Options
});
router.map({
'*': {
component: {
template: '<p>Not found!</p>'
}
},
'/' : require('./components/dashboard.js'),
});
Vue.use(require('vue-resource'));
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
users: [],
};
},
methods: {
fetchUsers: function() {
this.$http.get('/api/v1/users/list', function(data, status, response) {
this.users = data;
}).error(function (data, status, request) {
// handle error
});
}
}
});
router.start(App, '#app')
Simplified app.html
<div id="app" v-cloak>
<router-view users = "{{ users }}">
</router-view>
</div>
Simplified dashboard.js
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
},
};
When dashboard.js gets run, it prints an empty array to the console because that's what app.js initializes the users variable as. How can I allow dashboard.js to have access to the users variable from app.js? Thanks in advance for your help!
p.s. I don't want to use the inherit: true option because I don't want ALL the app.js variables to be made available in the child components.
I believe this is actually working and you are being misled by the asynchronous behavior of $http. Because your $http call does not complete immediately, your console.log is executing before the $http call is complete.
Try putting a watch on the component against users and put a console.log in that handler.
Like this:
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
watch: {
users: {
handler: function (newValue, oldValue) {
console.log("users is now", this.users);
},
deep: true
}
}
}
};
In the new version of Vue 1.0.0+ you can simply do the following, users inside your component is automatically updated:
<div id="app" v-cloak>
<router-view :users="users"></router-view>
</div>

Error while processing route: Cannot read property 'connectOutlet' in Ember.js

I'm getting the following error after upgrading Ember to the latest version:
Error while processing route: portfolio Cannot read property 'connectOutlet'
The error takes place whenever I navigate for example from:
http://localhost:8080/#/portfolio
to:
http://localhost:8080/#/publications
The weird thing is that if I refresh the pages many times sometimes it works and some times it's doesn't so feels like some file is loaded too late or maybe loaded twice.
aplication.hbs
The application view renders the header, footer and main container, which contains the application {{outlet}}.
<!-- ... -->
<div class="container" id="maincontainer">
<div class="maincontainer">
{{outlet}}
</div>
</div>
<!-- ... -->
index.hbs
My index view renders a couple of subviews:
<div class="jumbotron fadeInUp animated">
<div class="row">
<div id="summary_content">
{{view view.CvSummaryView}}
</div>
</div>
</div>
routes
In all my routes I'm only adding the model() function. I'm not overriding renderTemplate() or anything else.
define([
'Ember'
],
function (Ember) {
"use strict";
return Ember.Route.extend({
model: function()
{
var result = {};
$.ajax({
async: false,
dataType: "json",
url: './website/js/models/portfolio.json',
success: function(data){
result.portfolio = data;
}
});
return result;
}
});
}
);
I tried the following with no luck:
renderTemplate: function(){
this.render({
outlet: "main",
into: "application"
});
}
Do you have any ideas about what can be the root cause of this issue?
The entire app source code can be found at https://github.com/remojansen/remojansen.github.io/tree/master/website/js
UPDATE 1
I've been reading the Ember documentation and I added {{outlet "main"}} into my application template and tried with:
renderTemplate: function() {
this.render('blog', { // the template to render
into: 'application', // the template to render into
outlet: 'main' // the name of the outlet in that template
});
}
The I've been debugging the Ember code and I reached this function:
function appendView(route, view, options) {
if (options.into) {
var parentView = route.router._lookupActiveView(options.into);
var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
parentView.connectOutlet(options.outlet, view);
} else {
var rootElement = get(route.router, 'namespace.rootElement');
// tear down view if one is already rendered
if (route.teardownTopLevelView) {
route.teardownTopLevelView();
}
route.router._connectActiveView(options.name, view);
route.teardownTopLevelView = generateTopLevelTeardown(view);
view.appendTo(rootElement);
}
}
In the function above, in the line:
var parentView = route.router._lookupActiveView(options.into);
The variable parentView is null and options.into is "application". So the line below throws an exception:
parentView.connectOutlet(options.outlet, view);
I have defined the application template and view but not an application route I don't know if that could be the problem.
Thanks!
After some time debugging I noticed that the ember router._activeViews element didn't always contain the application view:
Works
Doesn't work
I tried to analyse why was this happening and because as I said in the question:
The weird thing is that if I refresh the pages many times sometimes it
works and some times it's doesn't so feels like some file is loaded
too late or maybe loaded twice.
I was almost sure that is was related with the usage of require.js and loading application components asynchronously.
The solution was use deferReadiness() and advanceReadiness(). Here is what I did in case it can help somebody in the future...
app.js
define(['Ember'], function (Ember) {
"use strict";
window.app = Ember.Application.create({
LOG_TRANSITIONS: false, // basic logging of successful transitions
LOG_TRANSITIONS_INTERNAL: false, // detailed logging of all routing steps
LOG_VIEW_LOOKUPS: false // detailed logging of view resolution
});
// Delay the app's initialization . We will invoke advanceReadiness()
// when are ready for the app to be initialized
window.app.deferReadiness();
return window.app;
});
main.js
require([
'website/js/app',
/* routes, views... */
], function (
app,
/* routes, views... */
){
"use strict";
// Configure Routes
app.Router.map(routes);
// Set Routes
app.IndexRoute = indexRoute;
// ...
// Set Views
app.IndexView = indexView;
// ...
// We're ready to launch the app!
app.advanceReadiness();
});

Categories

Resources