I have just started experimenting with backbone.js and have hit a roadblock while designing my very first front-end module with it. Here's what I have:
I followed the basic todos tutorial and created a commenting system upon my custom REST API. All good there.
I have a vote up or vote down button for each comment, just like SO. I want to call the REST API which is at
POST /api/comments/vote/:id
A parameter "direction" (up or down) is also passed.
In my comments view I have a function that calls another function it the comments model like this:
vote_up: function() {
this.model.voteUp();
return false;
}
the function in model:
voteUp: function() {
var up_votes = this.get('up_votes') - 0;
up_votes++;
this.save({up_votes: up_votes});
}
I am pretty sure I don't need this.save here which actually calls the comments API and the sends all the parameters as POST.
I am not sure how to do this in a backbonejs-way (I am looking for something standard).
You should do a manual $.ajax call to /api/comments/vote/:id and tell it if you have an upvote or downvote. Then /api/comments/vote/:id would send an atomic increment or decrement into the database, read out the new value, and send that back. Then the success handler for your $.ajax can do a set to update the client-side value.
Your voteUp implementation is subject to some obvious timing problems: what happens if you have 6 upvotes in your JavaScript, then 11 other people upvote, and your voteUp tries to tell the server that a new upvote has occurred so there should be 7 upvotes? You shouldn't try to maintain counters like this, not even in server-side code.
I guess I'm trying to say that there isn't really a Backboney way to do this, there is a correct way and all sorts of incorrect ways. Do it the correct way and declare, by fiat, that that is the Backboney way.
I think the standard way would be to take advantage of Backbone's urlRoot property.
var Vote = Backbone.Model.extend({urlRoot : '/api/comments/vote'});
// ....in comments view:
vote_up: function() {
// send HTTP Post '/api/comments/vote/' -- server will create ID
new Vote({direction: 1}).save()
return false;
}
If you need the Id right away, or to otherwise react to the successful insert, you can pass a success callback to save.
See: http://backbonejs.org/#Model-save
Related
One of the requirements of the project I am working on is that I log the connections made to the site, due to the amount of processing being done to get as much information as possible I would like to process this after the page has been sent back to the user.
At the moment I am running my code in the afterFilter of my appController:
public function afterFilter(Event $event){
$log_request = new RequestsController;
$log_request->log_request();
}
I am attempting to run this in afterRender of my appController:
public function afterRender(Event $event, $viewFile){
$log_request = new RequestsController;
$log_request->log_request();
}
But I can not seem to get the code to execute or if it does then I do not know how to find out what the error being thrown is.
If somebody can point me towards an example of this being done or a concurrent method of doing this (it needs to be logged within a second of the request) I would appreciate it.
$log_request = new RequestsController; you don't instantiate controllers inside controllers. You want to learn the MVC design pattern first when using a MVC based framework or you'll end up with a non maintainable piece of horrible spaghetti code. I recommend you to do the blog tutorial to get a basic understanding.
If somebody can point me towards an example of this being done or a concurrent method of doing this (it needs to be logged within a second of the request) I would appreciate it.
Read this chapter: CakePHP Logging Taken from there:
Logging data in CakePHP is easy - the log() function is provided by the LogTrait, which is the common ancestor for many CakePHP classes. If the context is a CakePHP class (Controller, Component, View,...), you can log your data. You can also use Log::write() directly.
Add the log trait to the AppController, pass the request to the log() method and configure the logging to log these requests to whatever you prefer either in afterRender() or if you want to do it really late, do it in __destruct().
I'm trying to find a generic solution to commit/rollback a model alongside ng-resource.
When to commit/save model:
Successful http write methods (PUT, POST, PATCH).
When to rollback/reset model:
Failing http write methods.
User deciding to cancel their local changes (before PUT).
I've found pieces of this solution scattered, but no comprehensive strategy.
My initial strategy was to make use of the default ng-resource cache. Controller starts with GETting an item. If a subsequent PUT/POST/PATCH failed (caught either in the controller's promise, or in the $resource.<write_method>'s interceptor.responseError, re-run the GET that first got the object, and replace my model with that, as on initial load. However, this doesn't help if there's a successful PUT, followed by a failed one, because I'd be replacing my model with a stale object. The same problem occurs when a user tries to cancel their form changes before submitting... the last successful GET may be stale.
I've got a model with ~10 properties, and multiple <form>s (for html/design's sake) that all interact with this one object that gets PUT upon any of the forms being submitted:
{'location_id': 1234,
'address': {
'line1': '123 Fake St'}
...
},
'phone': '707-123-4567',
...
}
Models already have nice rollback features, but getting ng-resource to touch them seems tricky, and I really want to reset the entire object/model. Using a custom cache alongside my $resource instance seems like a decent way to go, but is equally tricky to give user-interaction a way to rollback. Another idea was to store a separate object when loading into scope: $scope.location = respData; $scope._location = angular.copy(respData); that I could load from when wanting to roll back, by way of $scope.location = $scope._location;, but I really don't want to clutter my controllers site-wide with these shadow objects/functions (DRY).
I'm posting a specific value through a POST form to my CakePHP app. Within the AppController I handle this specific POST data and afterwards I unset $this->request->data, e.g.
if(!empty($this->request->data['Keyboard'])) {
$this->Session->write('Keyboard.active', $this->request->data['Keyboard']['active']);
unset($this->request->data);
}
Afterwards I want ALL request data to be unset (hence the $this->request->data). Within my child controllers I call parent::beforefilter(); as the first line of code in its respective beforeFilter function, making sure the beforeFilter of my appController is initiated. However, in my childController the following line of code will still evaluate to true:
if($this-request->is('post'))
How do I 'unset' the is('post') call? I've also tried $this->request->is('post') = false in the if-statement above, with no success however.
The "problem" with $this->request->is($anything) (in your case at least) is that it is just a function to compare variables, not a setter.
Here's the API code in case you want to check it out.
So, basically, this code compares the variable env('REQUEST_METHOD') to POST (that's in the detectors part of the code).
There's no way a $this->request->is('post') = false is going to work. Something like
$_ENV["REQUEST_METHOD"] = null;
//or
unset($_ENV["REQUEST_METHOD"]);
after you unset the request data might work, but I don't guarantee anything because I haven't tried it.
I feel this approach is very dirty, I don't like to mess with those variables at all if there's no function already available for it, but I expect you to have considered all possible choices to avoid messing with the request already and see this as the only solution. Otherwise you should post another question addressing this subject and see if better approaches come up.
Hopes it helps in your case.
What technique shall one use to implement batch insert/update for Backbone.sync?
I guess it depends on your usage scenarios, and how much you want to change the calling code. I think you have two options:
Option 1: No Change to client (calling) code
Oddly enough the annotated source for Backbone.sync gives 'batching' as a possible reason for overriding the sync method:
Use setTimeout to batch rapid-fire updates into a single request.
Instead of actually saving on sync, add the request to a queue, and only batch-save every so often. _.throttle or _.delay might help you here.
Option 2: Change client code
Alternatively, instead of calling save on your models, you could add some sort of save method to collections. You'd have to track which models were actually modified and hence in need of update, since as far as I can tell, Backbone only knows whether they're new or not (but I could be wrong about that).
Here's how I did it
Backbone.originalSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
//
// code to extend sync
//
// calling original sync
Backbone.originalSync(method, model, options);
}
works fine for me , and I use it to control every ajax request coming out of any model or collection
How can i call a requestAction method from my view, to specific controller, which returns me results on basis of conditions i mention?
Thanks !
Generally speaking, using requestAction is not very performant, because it begins a whole new dispatch process -- in essence your application is handling two requests for every one request made by a user. For this reason, you want to avoid using it as much as possible. That being said, requestAction has its uses, and you can mitigate the performance hit using view caching.
It's hard to gauge precisely what you want to do with requestAction, but the fundamental concept is you're asking CakePHP to process another request, so you can simply pass requestAction any URL your app would handle if it were typed into a browser's address bar (excluding the protocol and hostname). If you wanted to retrieve a collection of blogs managed by your application:
$blogs = $this->requestAction('/blogs/index');
You can also invoke requestAction by passing it an array of route components, in the same way as you might pass them into HtmlHelper::link. So you might retrieve the collection of blogs thus:
$blogs = $this->requestAction('controller'=>'blogs', 'action'=>'index');
Insofar as filtering the result-set returned by requestAction, it again is done by passing the conditions as part of the URL or route components:
$blogs = $this->requestAction('/blogs/index/author_id:1234');
// or
$blogs = $this->requestAction('controller'=>'blogs', 'action'=>'index', 'author_id' => 1234);
Note that if you want your requested action to return values, you need the action to handle requests differently than standard action requests. For the BlogsController::index action I've been referring to above, it might look something like this:
class BlogsController extends AppController{
function index(){
$conditions = array();
if ( isset($this->params['author_id'])){
$conditions['Blog.author_id'] = $this->params['author_id'];
}
$blogs = $this->Blog->find('all', array('conditions'=>$conditions);
if ( isset($this->params['requested']) && $this->params['requested'] == 1){
return $blogs;
}
else{
$this->set(compact('blogs'));
}
}
}
The if statement checking the presence and value of $this->params['requested'] is the key part. It checks whether the action was invoked by requestAction or not. If it was, it returns the collection of blogs returned by Blog::find; otherwise, it makes the collection available to the view, and allows the controller to continue onto rendering the view.
There are many nuances to using requestAction to arrive at the specific results you require, but the above should provide you with the basics. Check out the links posted by dogmatic69 for additional documentation, and of course there are numerous stacko questions on the subject.
Feel free to comment with any follow-ups! Hope this helped.
$comments = $this->requestAction('/comments/latest');
foreach($comments as $comment) {
echo $comment['Comment']['title'];
}
put this code in your ctp file