I am using a radialProgress as a jQuery plugins (homemade), and I need to implement it for ember but I have some issue to do that.
Quick explanation for the plugins :
var chart = $(yourElement).pieChart(options); // initialise the object to an element
chart.setCompleteProgress( complete, false ); // set how many item you have to complete the task
chart.incrementProgress(); // increment + 1 every time you call it
It's a very simple progress pie.
In my case my task are located inside my controller, but the chart as to select a dom element so I need to initialise it inside my view.
My task in the controller are called from the router from the setupController to reload the model over time.
Here is a small sample of what I would like to do :
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
var promise = controller.getModel();
this._super(controller, promise);
}
})
App.ApplicationController = Ember.ArrayController.extend({
getModel: function() {
// chart.setcompleteProgress();
// A lot of code are here to get some data
// chart.incrementProgress();
return newModel;
}
})
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
var chart = $(element).pieChart(opts);
}
})
I don't know how to pass the chart object from the view to the controller to be able to have access to my plugin function.
Che chart won't be inserted into the DOM until the didInsertElement therefore you can't attempt to manipulate it in the route during setupController etc. I'd suggest creating a method in the controller setupChart and calling that on didInsertElement.
App.ApplicationView = Ember.View.extend({
prepPieChart: function() {
var chart = $(element).pieChart(opts);
this.get('controller').setupPieChart(chart);
}.on('didInsertElement')
})
App.ApplicationController = Ember.ArrayController.extend({
setupPieChart: function(chart) {
chart.setcompleteProgress();
// A lot of code are here to get some data
chart.incrementProgress();
}
})
All that being said, maybe it belongs in the view, but I'm not sure of what you're completely doing.
Related
I'm trying to create a reusable generated element that can react to changing outside data. I'm doing this in an included view and using computed.alias, but this may be the wrong approach, because I can't seem to access the generic controller object at all.
http://emberjs.jsbin.com/nibuwevu/1/edit
App = Ember.Application.create();
App.AwesomeChartController = Ember.Object.extend({
data: [],
init: function() {
this.setData();
},
setData: function() {
var self = this;
// Get data from the server
self.set('data', [
{
id: 1,
complete: 50,
totoal: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
}
});
App.IndexController = Ember.Controller.extend({
needs: ['awesome_chart']
});
App.ChartView = Ember.View.extend({
tagName: 'svg',
attributeBindings: 'width height'.w(),
content: Ember.computed.alias('awesome_chart.data'),
render: function() {
var width = this.get('width'),
height = this.get('height');
var svg = d3.select('#'+this.get('elementId'));
svg.append('text')
.text('Got content, and it is ' + typeof(content))
.attr('width', width)
.attr('height', height)
.attr('x', 20)
.attr('y', 20);
}.on('didInsertElement')
});
And the HTML
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Awesome chart</h2>
{{view App.ChartView width=400 height=100}}
</script>
For what it's worth, this didn't seem to work as a component, either. Is the ApplicationController the only place for code that will be used on multiple pages? The 'needs' seems to work, but the nested view can't access it. If I make a proper Ember.Controller instance to decorate the view, that doesn't seem to work either.
Any help is much appreciated.
Update:
I can't edit my comment below, but I found a good answer on how to use related, and unrelated, models in a single route.
How to use multiple models with a single route in EmberJS / Ember Data?
Firstly, your controllers should extend ObjectController/ArrayController/Controller
App.AwesomeChartController = Ember.Controller.extend({...});
Secondly when you create a view the view takes the controller of the parent, unless explicitly defined.
{{view App.ChartView width=400 height=100 controller=controllers.awesomeChart}}
Thirdly you already had set up the needs (needed a minor tweak), but just as a reminder for those reading this, in order to access a different controller from a controller you need to specify the controller name in the needs property of that controller.
App.IndexController = Ember.Controller.extend({
needs: ['awesomeChart']
});
Fourthly from inside the view your computed alias changes to controller.data. Inside the view it no longer knows it as AwesomeChart, just as controller
content: Ember.computed.alias('controller.data')
Fifthly inside your on('init') method you need to actually get('content') before you attempt to display what it is. content doesn't live in the scope of that method.
var content = this.get('content');
http://emberjs.jsbin.com/nibuwevu/2/edit
First, AwesomeChart does sound like it's gonna be a reusable self-contained component. In which case you should better user Ember.Component instead of Ember.View (as a bonus, you get a nice helper: {{awesome-chart}}).
App.AwesomeChartComponent = Ember.Component.extend({ /* ... */ });
// instead of App.ChartView...
Second, for AwesomeChart to be truly reusable, it shouldn't be concerned with getting data or anything. Instead, it should assume that it gets its data explicitly.
To do this, you basically need to remove the "content:" line from the awesome chart component and then pass the data in the template:
{{awesome-chart content=controllers.awesomeChart.data}}
Already, it's more reusable than it was before. http://emberjs.jsbin.com/minucuqa/2/edit
But why stop there? Having a separate controller for pulling chart data is odd. This belongs to model:
App.ChartData = Ember.Object.extend();
App.ChartData.reopenClass({
fetch: function() {
return new Ember.RSVP.Promise(function(resolve) {
resolve([
{
id: 1,
complete: 50,
total: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
// or, in case of http request:
$.ajax({
url: 'someURL',
success: function(data) { resolve(data); }
});
});
}
});
And wiring up the model with the controller belongs to route:
App.IndexController = Ember.ObjectController.extend();
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.ChartData.fetch();
}
});
Finally, render it this way:
{{awesome-chart content=model}}
http://emberjs.jsbin.com/minucuqa/3/edit
I'm trying to develop a small app using EmberJS and HighchartJS, and I have some problem to re-render the the HighChartJS after the Model property has changed. This is http://www.loancomparison.com.s3-website-us-east-1.amazonaws.com/
App.Loan = DS.Model.extend({
name : DS.attr('string'),
principal : DS.attr('number'),
interest_rate : DS.attr('number'),
months_to_pay : DS.attr('number')
});
App.LoansView = Ember.View.extend({
templateName: "loans",
loansChanged: function() {
//this.rerender();
Ember.run.scheduleOnce('afterRender', this, 'propertyChanged');
}.observes('controller.#each.principal', 'controller.#each.name', 'controller.#each.interest_rate', 'controller.#each.months_to_pay'),
propertyChanged : function() {
console.log("property changed");
this.loadHighChart(); // This will load the highchart function.
},
});
What I want is to notify the view whenever the model property finishes their change. However, when using observes, it notify the view when the model starts to change. This causes the scheduleOnce to run at the initial state of model property change only.
Edit: Resolved
The solution for this turned out to be very simple that I just need to create a "modified" property under the model loan. Then whenever the edit is made, I update this model. Now the view just need to observe the change of this "modified" property.
You'll want to hook into the save action within your LoanController: https://github.com/pmkhoa/loan-comparison/blob/master/source/assets/js/app/controllers/loanController.js#L7
You can communicate really easily between views and controllers by using Ember.Evented:
App.LoanController = Ember.ObjectController.extend(Ember.Evented, { <--PASS IN EMBER.EVENTED
//...
save: function () {
this.set('isEditing', false);
this.get('model').save().then(function () {
this.trigger('highChartReload');
}.bind(this));
},
//...
});
Notice that I've passed Ember.Evented into the controller (just like you would with any mixin...) and I've added a trigger to the save action.
Now, we'll want to listen for that event within the view: https://github.com/pmkhoa/loan-comparison/blob/master/source/assets/js/app/views/loansView.js#L3
App.LoansView = Ember.View.extend({
//...
didInsertElement: function () {
this.get('controller.controllers.loan').on('highChartReload', $.proxy(this.loadHighChart, this));
},
//...
});
Now the view will listen to LoanController for the event to trigger, then fire off the loadHighChart method.
The last thing to do will be to tell the LoansController to need 'loan':
App.LoansController = Ember.ArrayController.extend({
needs: ['loan'],
//...
});
That should do it. Hope that helps!
I have an application that uses masonry and Ember JS I attempt to search DOM an element by selector, but it retrieves null It seems I do it early than template was rendered. Please, help me resolve it.
#GJK answer is correct, I just want to provide a working example: http://jsbin.com/enijad/3/edit
App.IndexView = Ember.View.extend({
didInsertElement: function() {
var $container = $('#container');
$container.masonry({
columnWidth: 150,
itemSelector: '.item'
});
}
});
The didInsertElement function will be called when the view was inserted into the DOM, so it will be safe to initialize additionally libraries.
Also worth mentioning is that if you need some clearing up after the view was removed from the DOM you would do this in didInsertElement's counterpart hook willDestroyElement.
Example:
App.IndexView = Ember.View.extend({
didInsertElement: function() {
// do initialization here
},
willDestroyElement: function() {
// and here you can remove stuff safely
}
});
Hope it helps.
Create a corresponding View for your Route and Template, and then override the didInsertElement method.
I have two controllers which both load to the same outlet, so only one can be active at one time. Both observe a property on a third controller like this:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
updateResults: function () {
console.log('load search data');
}.observes('controllers.navigation.search')
});
Full sample
http://jsfiddle.net/FMk7R/1/
When the property changes some data is fetched. If I click on both links so that both are loaded, then when the property changes, both controllers receive the observes event and load the data. I'd like to load the data only in the one which is visible.
How can I figure out which controller is currently active and load the data only in the active one?
Ideally your controllers should not know that they are active. One alternative is to invert the relationship, so that NavController is responsible for changing a query property of the "active" controller.
** UPDATE - Adding example based on comment **
App.SearchRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.ImagesRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.SearchController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading web search data for: ', query);
}
});
App.ImagesController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading image search data for: ', query);
}
});
App.NavigationController = Ember.ObjectController.extend({
search: '',
active: null,
searchDidChange: function() {
this.get('active').loadResults(this.get('search'));
}.observes('search', 'active')
});
See http://jsfiddle.net/F3uFp/1/
Another alternative is to use computed properties instead. Ember will only refresh computed properties that are actually required to render the active view. For example:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
results: function () {
console.log('loading web search data');
return("web search results");
}.property('controllers.navigation.search')
});
See updated fiddle here: http://jsfiddle.net/ZTnmp/
http://jsfiddle.net/FMk7R/1/
hi i have the following route:
MB3.PlaylistRoute = Ember.Route.extend({
model: function(params) {
return MB3.Playlist.find(params.playlist_id);
}
});
The playlist has a hasMany realtion with tracks. in the playlist view i want do do some logic with an attribute of the first track of the playlist.
so i added this code:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
console.log(this.get("controller.tracks").objectAt(0).get("title"));
}
});
The problem is title is undefined (i think because it is not yet loaded. the second thing i tried is waiting for the didLoad event:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
var self=this;
this.get("controller.tracks").on("didLoad", function() {
console.log(self.get("controller.tracks").objectAt(0).get("title"));
});
}
});
but this logges null as well. How do i accomplish that?
Like Adrien said in the comments, it seems you are running into issue 587. That said, I don't think you actually need the "didLoad" callback in this case. Instead, try using a computed property to get the video_id or track title. For example:
MB3.PlaylistView = Ember.View.extend({
firstTrackTitle: function() {
return this.get('controller.tracks.firstObject.title');
}.property('controller.tracks.firstObject.title')
});
Then in your template, embed the player if this property is defined:
{{#if view.firstTrackTitle}}
embed code here
{{/if}}
FWIW I would put this logic in controller instead of view, but same idea.