I have a template, chartEditPreview, that runs a D3.js chart-drawing function once the rendered callback fires. It looks like this:
Template.chartEditPreview.rendered = function() {
drawChart(".chart-container", this.data);
}
Where .chart-container is the target div and this.data is the data for the object in the DB currently being accessed. Problem is, this.data often returns null, seemingly at random. It looks like this has something to do with how the publish/subscribe pattern works — Iron Router (which I'm using) lets the templates render and then hot-pushes the data into those templates.
My question is (hopefully) pretty simple: how can I make sure this.data is actually full of DB data before drawChart is run? Should I be doing this in some other way, instead of calling it on the rendered callback?
I'm thinking of storing the DB data in a Session variable during the routing and calling that from rendered, but it seems like an extra step, and I'm not certain it'll fix this problem. The chart's also not rendered only once on the page — it's interactive, so it needs to be redrawn every time the database object is updated via one of the inputs on screen.
Any help would be much appreciated. Thanks!
For reference, here's what my routes.js looks like:
Router.route('/chart/edit/:_id', {
name: 'chart.edit',
layoutTemplate: 'applicationLayout',
yieldRegions: {
'chartEditType': { to: 'type' },
'chartEditPreview': { to: 'preview' },
'chartEditOutput': { to: 'output' },
'chartEditAside': { to: 'aside' },
'chartEditEmbed': { to: 'embed' }
},
data: function () {
return Charts.findOne({_id: this.params._id});
},
waitOn: function () {
return Meteor.subscribe('chart', this.params._id);
}
});
And my publications.js:
Meteor.publish("chart", function (id) {
return Charts.find({ _id: id });
});
This is a common problem with Meteor. While the subscription might be ready (you should check for it like Ethaan shows) , that does not mean the find() function actually had time to return something.
Usually I solve it with some defensive code, i.e:
Template.chartEditPreview.rendered = function () {
if(this.data)
drawChart(".chart-container", this.data);
// else do nothing until the next deps change
}
Of course this is not as clean as it should be, but as far as I know the only way to solve problems like this properly.
Updated answer
In this case we need a dependency to trigger rerun on data change. Iron router solves this for us:
Template.chartEditPreview.rendered = function () {
var data = Router.current() && Router.current().data();
if(data)
drawChart(".chart-container", data);
// else do nothing until the next deps change
}
add this.ready() into the data:function
data: function () {
if(this.ready()){
return Charts.findOne({_id: this.params._id});
}else{
this.render('loading')
}
},
Something using data and waitOn could be a little bit tricky
Template.chartEditPreview.rendered = function() {
Meteor.setTimeout(function(){
drawChart(".chart-container", this.data);
},1000)
}
Related
Maybe this was already discussed somewhere, I myself could not find an exact answer how to approach my problem:
I have a mutliniear «story» which executes code in each segment. I wrote a state machine, which initiates a new segment, whenever another segment calls that one. Each segment has an onEnter-, an onCheck and an onLeave function. As the name already says, the onEnter executes when the segment is called, the onCheck checks some input until some conditions are fullfilled (if yes they will lead to another segment) and onLeave executes just before the next segment is called.
Currently I just wrote a javascript object kinda like this:
var flow = {
seg1: {
onEnter: function() {
this.say('Seg1');
},
onCheck: function(data) {
if (data.condition) {
machine.callNextSeg('seg2');
} else if (data.condition2) {
machine.callNextSeg('seg3');
}
},
onLeave: function() {
}
},
seg2: {
onEnter: function() {
this.say('Seg2');
},
onGestureCheck: function(data) {
},
onLeave: function() {
}
},
seg3: {
onEnter: function() {
this.say('Seg3');
},
onGestureCheck: function(data) {
},
onLeave: function() {
}
}
};
The example is a bit simplified for understanding, but the code inside would be a little more complex.
I would rather like to have a JSON file, which is loaded, parsed and creates such an object. The JSON File should use a more simple and more abstract syntax to write. Especially to make it quicker.
I imagine something like this:
{
"seg1": {
"onEnter": {
"say": 'Seg1'
},
"onCheck": {
"condition1": "seg2",
"condition2": "seg3"
},
"onLeave": {
}
},
"seg2": {
"onEnter": {
"say": 'Seg2'
},
"onCheck": {
},
"onLeave": {
}
}
}
The conditions are booleans and if true the described segment should be called.
Would that be easy to parse the json, create an object, and create functions inside it for each onEnter, onCheck and onLeave? And also write the necessary if clauses?
Thanks for hints into the right direction.
cheers
J.
var flow = {...} is an object literal and is actually best suited for what you need to do, but I do understand the desire to have all of this "defined" in a JSON file. The issue there becomes the fact that the application processing your JSON file MUST understand/be Javascript (which defies the idea of having JSON as language agnostic - its name notwithstanding).
This might be what you need:
JavaScript Function Serialization
Also check the voted answer here:
What is the correct way to "serialize" functions in javascript for later use
I'm new to meteor and I'm trying to get a hang of the whole reactivity thing.
There isn't a specifc reason why I want this function to re-run, in fact, it not re-running is actually the desired behavior for my use case. I just want to know why this is happening so I can better understand the concepts.
If I add a function as a property on a template instance, like this:
Template.services.onCreated( function() {
this.templates = [
"web_design",
"painting",
"gardening"
];
this.current_index = new ReactiveVar(0);
this.determineSlideDirection = function() {
console.log(this.current_index.get());
};
});
And then I update the reactive var in response to some event.
Template.services.events({
'click .nav-slider .slider-item': function(event, template) {
var new_selection = event.currentTarget;
template.current_index.set($(new_selection).index());
}
});
The function is not re-run upon the invocation of the set() call.
However, If I have a helper that utilizes the variable, it will be re-run.
Template.services.helpers({
currentTemplate: function() {
var self = Template.instance();
return self.templates[self.current_index.get()];
}
});
Why is this?
Reactive data sources only cause some functions to automatically re-run. These functions are:
Tracker.autorun
Template.myTemplate.helpers({})
Blaze.render and Blaze.renderWithData
In your code above you would want to use Tracker.autorun
Template.services.onCreated( function() {
this.templates = [
"web_design",
"painting",
"gardening"
];
this.current_index = new ReactiveVar(0);
Tracker.autorun(function(){
// actually, this might not work because the context of
// 'this' might be changed when inside of Tracker.
this.determineSlideDirection = function() {
console.log(this.current_index.get());
};
});
});
I am struggling with when to destroy backbone views. I know I need to destroy the view somewhere, but I am not sure where.
I have the following code in router.js
routes: {
"names/search": "nameSearch",
"companies/search": "companySearch"
},
initialize: function(){
Backbone.history.start();
this.navigate("#/", true);
}
nameSearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var obj = {};
obj.Status = [utils.xlate("On Assignment"), utils.xlate("Candidate")];
var view = new RecordSearchView({ model: obj, el: $(".content") }, { "modelName": "Candidate" });
view.delegateEvents();
});
},
companySearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var view = new RecordSearchView({ model: {}, el: $(".content") }, { "modelName": "Company" });
view.delegateEvents();
});
}
And then in RecordSearchView.js I have the following function that is called when a user clicks the search button
doSearch: function () {
require(["app/utils/SearchHelper", "app/models/" + modelName, "app/views/SearchResults"], function (SearchHelper, Model, SearchResultsView) {
var obj = $("#searchForm").serializeArray();
var params = SearchHelper.getQuery(obj);
params["page"] = 1;
params["resultsPerPage"] = 25;
var collection = new Model[modelName + "Collection"]({}, { searchParams: params });
params["Fields"] = collection.getSearchFields();
collection.getPage(params["page"], function (data) {
require(["app/views/SearchResults"], function (SearchResultsView) {
App.Router.navigate(modelName + "/search/results");
var view = new SearchResultsView({ collection: data, el: $(".content") });
view.delegateEvents();
});
});
return false;
});
And SearchResults.js
return BaseView.extend({
init: function () {
this.render();
},
render: function () {
var data = this.collection.convertToSearchResults();
this.$el.html(template(data));
return this;
}
});
The problem is the second time I perform any search (calling the doSearch function from RecordSearch.js). As soon as I perform the second search, the data shown is that belonging to the previous search I performed. (For example I do a name search and it works, then do a company search but the screen shows company search results but then is quickly replaced with name search results).
My questions are
I suspect I need to call some cleanup code on the view before it is re-used. Where is the proper place within a backbone application to run this.
Is there anything wrong with the way I load SearchResults view from within RecordSearch view? SearchResults does not have a path on my router, but it is basically a form post, so I assume it shouldn't?
Any help is appreciated.
This problem is quite common and is known as Zombie Views. Derick Bailey explains this issue very well here: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
However unfortunately you can't simply solve it without changing the way you are loading your views.
Because you are loading them inside RequireJS modules that will keep it in the local var scope, you are losing the reference to the views once the route has been fully processed.
In order to solve this problem, you would need to keep the reference of the current view somewhere, and then properly dispose it before calling another view, something like this:
showView: function(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.currentView.render();
$('#content').html(this.currentView.el);
}
More about this solution here: http://tiagorg.com/talk-backbone-tricks-or-treats-html5devconf/#/6
I personally suggest you adopting a solution that will take care of this for you, like Marionette.js
It will handle this and quite many other issues, by providing the missing gaps of every Backbone-based architecture.
I have a scenario where I have to populate attribute of model with its id. For eg..
In User model:
module.exports = {
attributes: {
activation_link: "string"
},
afterCreate: function(value, cb) {
value.activation_link = "localhost:1337/user/action/"+ value.id;
cb();
}
The activation_link's modified value has to saved in the database too. How can that be achieved?
According to this and this your code should actually work: your manipulations in afterCreate are supposed to mutate the resulting object.
UPDATE
Hmm... Seems like the first parameter is not a Waterline object, despite of what the documentation says. Technically, you can refetch the record from DB by id, update and save without a lot of overhead (since it's supposed to be only called once upon creation). But I would really avoid putting in the DB a field that depends on a record id: such DB becomes untransportable, since you can't guarantee that the records will have the same ids. So, the solution would be either use some tokens for these activation links (the clean way) or just make activation_link a function without putting it in the DB (the simple way):
module.exports = {
attributes: {
},
activation_link: function() {
if (!this.id)
return false;
return 'localhost:1337/user/action/' + this.id;
}
}
Change from afterCreate to beforeCreate (sails -v 0.12.14)
beforeCreate: function (attrs, cb) {
attrs.createdAt = moment().format()
attrs.updatedAt = attrs.createdAt
return cb();
},
beforeUpdate: function (attrs, cb) {
attrs.updatedAt = moment().format()
return cb();
}
document here offical sails model lifecycle
I have the following controller in ExtJs:
Ext.define('FileBrowser.controller.BrowserController', {
extend: 'Ext.app.Controller',
views: ['browser.tree_dir', 'browser.grid_file'],
stores: ['store_dir', 'store_file'],
init: function () {
this.control({
'window > tree_dir': {
itemclick: {
fn: function (view, record, item, index, event) {
if (record.isLeaf() == false) {
Ext.getStore('store_file').load({
params: {
dir: record.data.id
}
});
var parentOfCurrentFiles = record.data.id
nodeId = record.data.id;
htmlId = item.id;
var grid_view = this.getView('browser.grid_file');
var grid_view_v = grid_view.getView();
grid_view_v.refresh();
}
}
}
}
});
},
onPanelRendered: function () {
console.log('The panel was rendered');
}
});
If you notice under 'itemclick' I am trying to refresh one of my views, my approach is not working. Can anyone explain to me how I can refresh the view? Thank you.
Replace var grid_view= this.getView('browser.grid_file'); with var grid_view= this.getView('browser.grid_file').create(); to get a real instance (as I already told you, getView() only return the view config, not a instance!) or if you have already created that grid and only one instance exist use the xtype along with a component query to receive it var grid_view=Ext.ComponentQuery('grid_file')[0]
Now to the refresh()
Basically you never need to call this method cause your grid is bound to a store and any change made on this store is directly reflected to your grid.
I would also recommend you to store view instances when creating them instead of using queries or directly use the ref property and let ExtJS do the work for you. The last one will the best solution you I guess... Take a look at ref's within the API examples and give it a try.
So what you are trying to do is, load the store and have the data reflect once you refresh the grid_view...?
In that case, you haven't done a setStore() to the grid, or if you have done that elsewhere, you are't doing a setData() to the store. Also you should call the refresh on the grid.