I have the following code:
var Panel = React.createClass({
getInitialState: function () {
return {
user_id: null,
blogs: null,
error: false,
error_code: '',
error_code: ''
};
},
shouldComponentUpdate: function(nextProps, nextState) {
if (nextState.error !== this.state.error ||
nextState.blogs !== this.state.blogs ||
nextState.error_code !== this.state.error_code
) {
return true;
}
},
componentDidMount: function() {
var self = this;
var pollingInterval = setInterval(function() {
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}.bind(self)).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
}.bind(self));
}, 1000);
},
render: function() { ... }
});
The important part to focus on is the componentDidMount This will fetch every second, regardless if there is an error or not. The render function, assuming theres an error, will display the appropriate method. So for all intense and purpose, this code does exactly what I want it to do, it fetches, if it fails, it fetches again until it succeeds.
But I need to make some changes, and this is where I am lost. I want to say: Fetch once, pass or fail - it doesn't matter. THEN every 15 seconds after that initial fetch, try again - regardless of pass or fail
I would normally spin up a backbone collection and router along with a poll helper to do all this, but in this specific case there is no need for the extra overhead. So thats where I am stumped. How do I accomplish what I am trying to achieve?
You should be able to just refactor your code a bit to be able to call your polling function a few different ways (like manually for example and then at a specified interval):
componentDidMount: function() {
this.startPolling();
},
componentWillUnmount: function() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
},
startPolling: function() {
var self = this;
setTimeout(function() {
if (!self.isMounted()) { return; } // abandon
self.poll(); // do it once and then start it up ...
self._timer = setInterval(self.poll.bind(self), 15000);
}, 1000);
},
poll: function() {
var self = this;
$.get(self.props.source, function(result) {
if (self.isMounted()) {
self.setState({
error: false,
error_code: '',
error_message: '',
blogs: result.user.blogs,
user_id: result.user.id
});
}
}).fail(function(response) {
self.setState({
error: true,
error_code: response.status,
error_message: response.statusText
});
});
}
Related
I am trying to pass an argument down the tree to the successResponse errorResponse functions and display the value in the console before I do any work with it.
Currently I am getting an empty value in the console so there must be something missing in my code. I am thinking its a return statement but when I attempt this I get no result.
Any help would be greatly appreciated.
The console.log is below.
successResponse: function (getSel) {
requestResponses.errorCode = false;
requestResponses.redLight.removeClass(requestResponses.redBright);
requestResponses.greenLight.addClass(requestResponses.greenBright);
console.log(getSel);
},
Here is the full version of my code
var requestResponses = {
greenLight: $('.cp_trafficLight_Light--greenDimmed'),
redLight: $('.cp_trafficLight_Light--redDimmed'),
greenBright: 'cp_trafficLight_Light--greenBright',
redBright: 'cp_trafficLight_Light--redBright',
settings: {
flashError: 400,
requestTime: 10000
},
init: function (url, getSel) {
requestResponses.url = url;
requestResponses.getResponse(requestResponses.url, getSel);
setInterval(function () {
if (requestResponses.errorCode === true) {
requestResponses.redLight.toggleClass(requestResponses.redBright);
}
}, requestResponses.settings.flashError);
},
successResponse: function (getSel) {
requestResponses.errorCode = false;
requestResponses.redLight.removeClass(requestResponses.redBright);
requestResponses.greenLight.addClass(requestResponses.greenBright);
console.log(getSel);
},
errorResponse: function () {
requestResponses.greenLight.removeClass(requestResponses.greenBright);
},
getResponse: function (serverURL, getSel) {
$.ajax(serverURL, {
success: function (getSel) {
requestResponses.errorCode = false;
requestResponses.successResponse(getSel);
},
error: function () {
requestResponses.errorCode = true;
requestResponses.errorResponse();
},
complete: function () {
setTimeout(function () {
requestResponses.getResponse(requestResponses.url);
}, requestResponses.settings.requestTime);
}
});
},
errorCode: false
}
requestResponses.init('/status');
Appreciate any help.
Your code looks fine. Make sure that the server actually responds with data. The problem is most likely on back-end.
I have some specific problem.
I use MeteorJS and installed yogiben:admin. I tried to build some schema, but I have an error after updating something.
I want to add that I have subpages in page, maybe that's the problem?
That's what I get after adding items to my invoice:
http://s7.postimg.org/l0q52l27v/error.png
As I can see in the picture, the problem is with some modifier and with "After.Update.sum". I use function that use "sum".
In my "server/collections/invoices_item.js"
I have:
InvoicesItem.after.update(function(userId, doc, fieldNames, modifier, options) {
var sum = 0; InvoicesItem.find({ invoiceId: doc.invoiceId }).map(function(item) { sum += item.amount; }); Invoices.update({ _id: doc.invoiceId }, { $set: { totalAmount: sum }});
});
Than I saw that problem could be with "totalAmount:sum". I use Chrome, so I tried "console.log()" to see if the page takes my collection.
And it doesn't.
I use Chrome, so I tried to see what the console will give me. I have something like this: http://s4.postimg.org/rusm4wx9p/fakturka.png
I did sth like that in my code on server side:
Meteor.publish("fakturka", function(invoiceId) {
return Invoices.find({_id:invoiceId,ownerId:this.userId}, {});
});
And did that on client side:
this.InvoicesNewInsertController = RouteController.extend({
template: "InvoicesNew",
yieldTemplates: {
'InvoicesNewInsert': { to: 'InvoicesNewSubcontent'}
},
onBeforeAction: function() {
/*BEFORE_FUNCTION*/
this.next();
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("InvoicesNew"); this.render("loading", { to: "InvoicesNewSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("invoices_item"),
Meteor.subscribe("invoiceeeee"),
Meteor.subscribe("customers"),
Meteor.subscribe("fakturka", this.params.invoiceId),
Meteor.subscribe("invoices_item_empty_faktura"),
Meteor.subscribe("invoices_itemss_faktura", this.params.invoiceId)
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {},
invoices_item: InvoicesItem.find({}, {}),
invoiceeeee: Invoices.find({}, {}),
customers: Customers.find({}, {}),
fakturka: Invoices.findOne({_id:this.params.invoiceId}, {}),
invoices_item_empty_faktura: InvoicesItem.findOne({_id:null}, {}),
invoices_itemss_faktura: InvoicesItem.find({invoiceId:this.params.invoiceId}, {})
};
/*DATA_FUNCTION*/
},
onAfterAction: function() {
}
});
I'm sorry for so much code, but I really want to solve that problem and I want to give so much info as I could. Please, help me to solve my problem.
After removing that code from: both/collections/invoices.js
Schemas={};
Schemas.Invoicess = new SimpleSchema({
invoiceNumber:{
type:Number
},
date_issued:{
type:Date
},
date_due:{
type:Date
},
customerId:{
type:String
},
totalAmount:{
type:String
}
});
Invoices.attachSchema(Schemas.Invoicess);
"fakturka" is visible. After adding that code - "fakturka" in undefined.
Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}
Which interface or component do you suggest to display the state of parallel async calls? (The language is not so important for me, just the pattern, I can rewrite the same class / interface in javascript...)
I load model data from REST service, and I want to display pending label before the real content, and error messages if something went wrong... I think this is a common problem, and there must be an already written component, or best practices, or a pattern for this. Do you know something like that?
Here is a spaghetti code - Backbone.syncParallel is not an existing function yet - which has 2 main states: updateForm, updated. Before every main state the page displays the "Please wait!" label, and by error the page displays an error message. I think this kind of code is highly reusable, so I think I can create a container which automatically displays the current state, but I cannot decide what kind of interface this component should have...
var content = new Backbone.View({
appendTo: "body"
});
content.render();
var role = new Role({id: id});
var userSet = new UserSet();
Backbone.syncParallel({
models: [role, userSet],
run: function (){
role.fetch();
userSet.fetch();
},
listeners: {
request: function (){
content.$el.html("Please wait!");
},
error: function (){
content.$el.html("Sorry, we could not reach the data on the server!");
},
sync: function (){
var form = new RoleUpdateForm({
model: role,
userSet: userSet
});
form.on("submit", function (){
content.$el.html("Please wait!");
role.save({
error: function (){
content.$el.html("Sorry, we could not save your modifications, please try again!");
content.$el.append(new Backbone.UI.Button({
content: "Back to the form.",
onClick: function (){
content.$el.html(form.$el);
}
}));
},
success: function (){
content.$el.html("You data is saved successfully! Please wait until we redirect you to the page of the saved role!");
setTimeout(function (){
controller.read(role.id);
}, 2000);
}
});
}, this);
form.render();
content.$el.html(form.$el);
}
}
});
I created a custom View to solve this problem. (It is in beta version now.)
Usage: (Form is a theoretical form generator)
var content = new SyncLabelDecorator({
appendTo: "body",
});
content.load(function (){
this.$el.append("normal html without asnyc calls");
});
var User = Backbone.Model.extend({
urlRoot: "/users"
});
var UserSet = Backbone.Collection.extend({
url: "/users",
model: User
});
var Role = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'members',
relatedModel: User
}]
});
var administrator = new Role({id :1});
var users = new UserSet();
content.load({
fetch: [role, users],
sync: function (){
var form = new Form({
title: "Update role",
model: role,
fields: {
id: {
type: "HiddenInput"
},
name: {
type: "TextInput"
},
members: {
type: "TwoListSelection",
alternatives: users
}
},
submit: function (){
content.load({
tasks: {
save: role
},
sync: function (){
this.$el.html("Role is successfully saved.");
}
});
}
});
this.$el.append(form.render().$el);
}
});
Code:
var SyncLabelDecorator = Backbone.View.extend({
options: {
pendingMessage: "Sending request. Please wait ...",
errorMessage: "An unexpected error occured, we could not process your request!",
load: null
},
supported: ["fetch", "save", "destroy"],
render: function () {
if (this.options.load)
this.load();
},
load: function (load) {
if (load)
this.options.load = load;
this._reset();
if (_.isFunction(this.options.load)) {
this.$el.html("");
this.options.load.call(this);
return;
}
_(this.options.load.tasks).each(function (models, method) {
if (_.isArray(models))
_(models).each(function (model) {
this._addTask(model, method);
}, this);
else
this._addTask(models, method);
}, this);
this._onRun();
_(this.tasks).each(function (task) {
var model = task.model;
var method = task.method;
var options = {
beforeSend: function (xhr, options) {
this._onRequest(task, xhr);
}.bind(this),
error: function (xhr, statusText, error) {
this._onError(task, xhr);
}.bind(this),
success: function (data, statusText, xhr) {
this._onSync(task, xhr);
}.bind(this)
};
if (model instanceof Backbone.Model) {
if (method == "save")
model[method](null, options);
else
model[method](options);
}
else {
if (method in model)
model[method](options);
else
model.sync(method == "fetch" ? "read" : (method == "save" ? "update" : "delete"), model, options);
}
}, this);
},
_addTask: function (model, method) {
if (!_(this.supported).contains(method))
throw new Error("Method " + method + " is not supported!");
this.tasks.push({
method: method,
model: model
});
},
_onRun: function () {
this.$el.html(this.options.pendingMessage);
if (this.options.load.request)
this.options.load.request.call(this);
},
_onRequest: function (task, xhr) {
task.abort = function () {
xhr.abort();
};
},
_onError: function (task, xhr) {
this._abort();
this.$el.html(this.options.errorMessage);
if (this.options.load.error)
this.options.load.error.call(this);
},
_onSync: function (task, xhr) {
++this.complete;
if (this.complete == this.tasks.length)
this._onEnd();
},
_onEnd: function () {
this.$el.html("");
if (this.options.load.sync)
this.options.load.sync.call(this);
},
_reset: function () {
this._abort();
this.tasks = [];
this.complete = 0;
},
_abort: function () {
_(this.tasks).each(function (task) {
if (task.abort)
task.abort();
});
}
});
I'm building small one page application with rails 3.1 mongodb and backbonejs.
I have two resources available through json api. I created two models and collections in backbone which look like this
https://gist.github.com/1522131
also I have two seprate routers
projects router - https://gist.github.com/1522134
notes router - https://gist.github.com/1522137
I generated them with backbonejs-rails gem from github so code inside is just template. I initialize my basic router inside index.haml file
#projects
:javascript
$(function() {
window.router = new JsonApi.Routers.ProjectsRouter({projects: #{#projects.to_json.html_safe}});
new JsonApi.Routers.NotesRouter();
Backbone.history.start();
});
I don't want fetch notes when application is starting, because there is big chance that user will never look inside notes. So there isn't good reason to fetch it on start. Inside NotesRouter in all action I rely on #notes variable but without .fetch() method this variable is empty. Also I should can reproduce notes view from url like
/1/notes/5
project_id = 1
note_id = 5
What is best practices in backbonejs to solve this kind of problem ?
Why don't you lazy load the notes when it's requested? Here's an example:
var State = Backbone.Model.extend({
defaults: {
ready: false,
error: null
}
});
var Note = Backbone.Model.extend({
initialize: function () {
this.state = new State();
}
});
var Notes = Backbone.Collection.extend({
model: Note,
initialize: function () {
this.state = new State();
}
});
var NoteCache = Backbone.Model.extend({
initialize: function () {
this._loading = false;
this._loaded = false;
this._list = new Notes();
},
_createDeferred: function (id) {
var note = new Note({ id: id });
this._list.add(note);
this._load();
return note;
},
getNote: function (id) {
return this._list.get(id) || this._createDeferred(id);
},
getNotes: function () {
if (!this._loaded)
this._load();
return this._list;
},
_load: function () {
var that = this;
if (!this._loading) {
this._list.state.set({ ready: false, error: null });
this._loading = true;
$.ajax({
url: '/api/notes',
dataType: 'json',
cache: false,
type: 'GET',
success: function (response, textStatus, jqXHR) {
_.each(response.notes, function (note) {
var n = that._list.get(note.id);
if (n) {
n.set(note);
} else {
that._list.add(note, { silent: true });
n = that._list.get(note.id);
}
n.state.set({ ready: true, error: null });
});
that._list.state.set({ ready: true, error: null });
that._list.trigger('reset', that._list);
that._loaded = true;
},
error: function (jqXHR, textStatus, errorThrown) {
that._list.state.set({ error: 'Error retrieving notes.' });
that._list.each(function (note) {
note.state.set({ error: 'Error retrieving note.' });
});
},
complete: function (jqXHR, textStatus) {
that._loading = false;
}
});
}
}
});
In this example, I'm defining a NoteCache object that manages the lazy loading. I also add a "state" property to the Note model and Notes collection.
You'll probably want to initialize NoteCache somewhere (probably inside your route) and whenever you want a note or notes, just do this:
var note = noteCache.getNote(5);
var notes = noteCache.getNotes();
Now inside your view, you'll want to listen for state changes in case the note/notes is not loaded yet:
var NoteView = Backbone.View.extend({
initialize: function(){
this.note.state.bind('change', this.render, this);
},
render: function(){
if (this.note.state.get('error') {
// todo: show error message
return this;
}
if (!this.note.state.get('ready') {
// todo: show loader animation
return this;
}
// todo: render view
return this;
}
});
I haven't tested this, so there may be some bugs, but I hope you get the idea.