I gave up finally. I have struggling to get this one to work but no luck. I simply have a collection.create call like this:
var createData = {
full_name : full_name,
email : email,
role_id : role_id
};
var that = this;
app.collections.teamMembers.create(createData,{
wait: true,
success : function(){
log("in success")
},
error : function(a,b,c){
log("in error")
}
})
The server is PHP and it returns the result like this:
header('Content-type: application/json');
echo json_encode(array(
"data" => $data,
"meta" => $meta
));
In the above, the $data is actually the array("attr"=>"val", ...) which matches exactly how the model for this collection is defined.
The problem is that since I am not returning directly a JSON object similar to the original model, but using namespacing (data/meta), I use model.parse on the model like this:
parse : function(response){
log(response, "inside model parse, this is the response from server")
return response.data;
},
ISSUE: The model doesn't get created on the client end. No 'add' event is fired. I am also using the wait:true option.
However, the model gets created on the local if:
- I don't use wait:true
- I use wait true but return the exact JSON model from server, with no name spacing.
I WANT to use wait:true as well as namespacing. Please help :(
Finally I was able to fix it, I was overriding backbone collections and models in my bootstrap to have a loading state which I am not using anyway. So I commented out that whole code. Now it works fine. this was the code in my bootstrap that I commented out:
// OVERRIDINGS AND SETTINGS
//----------------------
// Adding close method to all views
Backbone.View.prototype.close = function() {
if (this.onClose) {
this.onClose();
}
_.each(this.childViews, function(childView){
childView.close();
delete childView;
})
this.remove();
this.unbind();
};
// Adding loading state to every model and collection
Backbone.Collection.prototype.loading = false;
Backbone.Model.prototype.isLoading = false;
// Set isLoading to true when fetch starts
var oldFetch = Backbone.Collection.prototype.fetch;
Backbone.Collection.prototype.fetch = function(options) {
this.isLoading = true;
oldFetch.call(this, options);
}
Backbone.Model.prototype.fetch = function(options) {
this.isLoading = true;
oldFetch.call(this, options);
}
// Turn off isLoading when reset
Backbone.Collection.prototype.on('reset', function(){
this.isLoading = false;
})
Backbone.Model.prototype.on('reset', function(){
this.isLoading = false;
})
Related
Folks: Creating an app in angular and node webkit - where users queue up files for downloading, navigate to their dashboard view and this initiates the downloads.
I've created a service which holds an object of the files data:
..
var downloadObj = {};
// fileObj = {'name':'The file name'; 'download_progress' : dlProgress}
showcaseFactory.myDownloads = function(eventId, fileObj) {
if(eventId){
console.log('update the object');
downloadObj['event_'+eventId] = fileObj;
}
console.log(downloadObj);
};
showcaseFactory.getDownloads = function() {
return downloadObj;
};
..
When the dashboard view loads - ng-repeat loops over $scope.downloadFiles which references this object returning the data.
<div ng-repeat="file in downloadFiles">
<div><span>{{file.name}}</span> [{{file.download_progress}}%]</div>
</div>
I've created a custom module which utilises node_modules to perform the download of the files:
nwjsDownloadFactory.commenceDownload = function(event_id, url, dest, cb) {
var http = require('http');
var fs = require('fs');
var statusBar = require('status-bar');
var path = require('path');
// THIS UPDATES THE OBJECT AND DISPLAYS FINE --------- >>
var id = 7;
var testFileObj = {
'name' : 'This is the file name prior to the download...',
'download_progress' : 10
};
ShowCase.myDownloads(id, testFileObj);
// <<< THIS UPDATES THE OBJECT AND DISPLAYS FINE ---------
var file = fs.createWriteStream(dest);
var request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() is async, call cb after close completes.
});
bar = statusBar.create({ total: response.headers['content-length'] })
.on('render', function (stats) {
// var percentage = this.format.percentage(stats.percentage);
// console.log(event_id + '....' + percentage);
var id = 7;
var testFileObj = {
'name' : 'This is the new file name during the download...',
'download_progress' : 35 // this will be replaced with percentage
};
ShowCase.myDownloads(id, testFileObj);
});
response.pipe(bar);
}).on('error', function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
}
QUESTION: Prior to the line var request = http.get(url, function(response) the object gets updated, and the changes are reflected in the UI. However, I need to constantly update the object with download complete % so I can create a progress bar.. However, as this asynchronous function executes, the object
appears to be updating - see the attached screen shot - but the UI is not reflecting this.
Can somebody please steer me in the right direction - I need the object to update during the function bar = statusBar.create({ and for the changes to reflect in the UI..
Call $scope.$apply() after making changes to your model to notify Angular that it has to update the UI.
showcaseFactory.myDownloads = function(eventId, fileObj) {
if(eventId){
console.log('update the object');
downloadObj['event_'+eventId] = fileObj;
$scope.$apply();
}
console.log(downloadObj);
};
If you use Angular's $http object, this is handled automatically for you, but if you update your model from other asynchronous callbacks, you have to take care of it yourself.
See this blog post and this documentation page for more in-depth explanations about what's going on.
Backbone documentation says,
parse is called whenever a model's data is returned by the server, in
fetch, and save. The function is passed the raw response object, and
should return the attributes hash to be set on the model.
But i have customized parse function for my model. I want to execute it only when i fetch data not when i save data.
Is there a way to do it? I can check my response inside parse function. But is there any built-in option to do it?
This is from the backbone source file regarding saving a model:
var model = this;
var success = options.success;
options.success = function(resp) {
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
You could pass a custom option on your save like: model.save(null, { saved: true }), then in your custom parse:
parse: function(response, options) {
if ( options.saved ) return this.attributes;
// do what you're already doing
}
I haven't tested this at all, but it should at least get you started.
Just pass a parse:false into the save method as an option.
m = new MyModel()
s.save(null, {parse: false})
I have a model which keeps some other models in an attribute array. When these models are stored, however, I don't want to keep the sub-modules around--instead, I want to store the primary keys, and then when the model is fetched from the server, its parse will "reconstitute" them by fetching the related models.
What is the best approach to accomplishing this? The closest I've come to getting it to work is overriding the sync method:
sync : function(method, model, options) {
var topics = this.get('topics');
model.attributes.topics = _.pluck(topics, 'id');
var ret = Backbone.Model.prototype.sync.call(this, method, model, options);
this.attributes.topics = topics;
return ret;
},
but this regularly fails, leaving the keys in the attributes instead of the full models & consequently crashing.
Parse function (slightly paraphrased):
parse : function(response) {
response.topics = _.map(response.topics, function(item) {
return app.topics.getByPK(item);
}
return response;
}
What I would do would be something more along these lines:
parse : function(response) {
this.topics = _.map(response.topics, function(item) {
return app.topics.getByPK(item);
}
return response;
}
Which keeps your array of ids intact at all times, and you have access by using this.topics instead of this.get('topics') or this.attributes.topics
Disk is my collections object. I need to stop fetching collections
customPoll: function(time){
var set_time = 0;
if(time === undefined){
set_time = 4000;
}
var route = Backbone.history.fragment.split('/');
var self = this;
if(route[0] === "disks"){
setTimeout(function() {
Disks.fetch({update:true,success: function(){
self.customPoll();
}, error: function(){
self.customPoll();
}
});
}, set_time); //TODO Need to handle efficiently...
}
}
Am trying to call this fetching in every 4 second if some condition exist other wise i need to stop calling this fetching.
var route = Backbone.history.fragment.split('/');
var smart = new Smart.model({
"id" : route[1]
});
var self = this;
smart.save(null,{
success: function(model,event,response){
model = Disks.get(route[1]).toJSON();
$('#smart-confirm-dialog').modal('hide');
self.showStatusMsg(1,"<b> S.M.A.R.T. Test : </b>S.M.A.R.T Test started succesfully");
if(model.smart.progress === "100%"){
self.clearAllTimeout();
alert("please stop fetching....pleaseeee");
// Stop polling here . then fetch information from smart.fetch api.
Smart.fetch({update: true}); //this is another api i need to call this api now.
}else{
self.customPoll();
}
});
But it seems to be not working... Its keep on fetching collection.. How can i stop this Disk collection fetching.
My answer maybe is funny, I want to add comment, but I couldn't. can you add new field to your model and
customPoll: function(time){
var disks = this.model.toJSON();
if(disks.yourField){
// here your code
}
}
but before saving the model need to do delete disks.yourField;
I have this script in a view:
<script type="text/javascript">
$(document).ready(function() {
$("#addbrand").click(function() {
$.ajax({
url : '../brands/add',
data : {
name : "test",
shortname : "tst"
},
dataType : 'json',
success : function(html, textStatus) {
alert('Success ' + textStatus + html);
},
error : function(xhr, textStatus, errorThrown) {
alert('An error occurred! ' + errorThrown);
}
});
});
});</script>
And in add controller I have these lines:
... else if($this->request->is('ajax')){
if ($this->Brand->save($this->request->query)) {
// How to send feedback!?
}else{
// How to send feedback!?
}
$this->autoRender = false;
exit();
}
When I click addbrand, Ajax operation runs successfully and a I can see the added row in database, but I don't know how to send an error or success message to the user. I've read several tutorials but none of them were about cakephp2.0 while Everything is changed in 2.x.
I've also read JSON and XML views but unfortunately I didn't understand anything!!!
I need to send a status code. If the status was OK then I ought to to send an array of Strings (brand names actually) and if status is not OK I should send a string that explains why the operation is not completed successfully.
I'd be most grateful if anybody can help me. Thanks
Update:
I changed the code. I used CakeResponse() and now my action is like this:
if($this->RequestHandler->isAjax()){
if ($this->Brand->save($this->request->query)) {
return new CakeResponse(array('body'=> json_encode(array('val'=>'test ok')),'status'=>200));
}else{
return new CakeResponse(array('body'=> json_encode(array('val'=>'test not ok')),'status'=>500));
}
}
Using CakeResponse I can handle the possible responses in Jquery well.
$("#addbrand").click(function() {
$.ajax({
url : '../brands/add',
data : {
name : "test",
shortname : "tst"
},
dataType : 'json',
success : function(data) {
alert("The brand has been saved");
},
error : function(data) {
alert("Eorror occured");
},
complete : function(data) {
alert($.parseJSON(data.responseText).val);
}
});
});
Although it seems to me that everything is working now and I can send several variables through the Ajax between client and server in JSON format, I need to know if it's a standard way of sending Ajax responses in CakePHP or not? Is there any other simpler way for doing this?
The following lines of code do exactly whatever return new CakeResponse(array('body'=> json_encode(array('val'=>'test ok')),'status'=>200)); does in my question:
$this->set('val','test ok');
$this->set('_serialize',array('val'));
$this->response->statusCode(200);
Remember that you need to do two important things:
Add Router::parseExtensions('json'); to App/Config/routs.php.
Add var $components = array("RequestHandler"); to your controller.
I think this way is better because you don't need to return anything. In previous solution we had to return cakeresponse object and this, sits uneasy with the nature of actions.
You should use the JSON views with route extensions:
Firstly you need to set up route extensions. This is generally done with:
Router::parseExtensions('json'); // In App/Config/routes.php
This will enable Router to handle the 'json' extension and to know how to handle a request like:
www.example.com/people/index.json
if($this->RequestHandler->isAjax()){
if ($this->Brand->save($this->request->query)) {
//Logic for success
} else {
//Logic for save failure
}
}
At this point you have the ability to choose between using the data views with the serialize key or using a data view with view files (copyed from the CakeBook):
<?php
// Controller code
class PostsController extends AppController {
public function index() {
$this->set(compact('posts', 'comments'));
}
}
// View code - app/View/Posts/json/index.ctp
foreach ($posts as &$post) {
unset($post['Post']['generated_html']);
}
echo json_encode(compact('posts', 'comments'));
Notice that the view is located under .../Views/Posts/json/...
You can have multiple extensions in the router so you can return and handle all kinds of contents - after all it is all just data representation.
Cheers!