I've run into a problem in my CakePHP app as a result of adding some ajax actions.
My table is called orders so obviously my controller is called OrdersController and the model is called Order
It is my understanding of CakePHP's best practices that if I am going to run any logic on the Order model, that it should be done in the OrdersController. This is fine for the basic CRUD stuff but now that some of my views need to send ajax requests to manipulate Order data I have a problem.
The problem is that for ajax to work properly, I have to put this at the beginning of the OrdersController
var $layout = 'ajax'; // uses an empty layout
var $autoRender=false; // renders nothing by default
Then, to stop the security component interfering with my Ajax form submissions, I also need this:
public function beforeFilter() {
parent::beforeFilter();
$this->Security->csrfUseOnce = false;
$this->Security->csrfExpires = '+1 hour';
}
None of this would be a problem if the controller was only being used for Ajax requests, but the problem is that it's being used for regular Cake actions too.
Is the answer that I should have two controllers? One for regular actions and one for ajax actions? This doesn't seem to be mentioned in the Cake docs and it doesn't seem like a very efficient way of doing things.
I know I can change the layout and possibly the auto-render setting on a per-action basis, but I don't see how it's possible to do this with the csrf settings, which need to be in the beforeFilter.
No need for a separate controller. Use cakes request handler. In your controller method, you can test if it's an Ajax request.
if ($this->request->is('ajax')) {
//set to Ajax layout and security settings, etc
You'll need to include the request handler component at the top of your controller:
public $components = array('RequestHandler');
See this page in the cook book For more info: http://book.cakephp.org/2.0/en/core-libraries/components/request-handling.html
Related
Most of the examples of AngularJS controllers that I have seen, usually have a single action method that wires everything up for the view. On the other hand, in controllers that use the MVC pattern, rather than AngularJS's MVW, there are usually multiple action methods per controller, but this does not appear to be the case with AngularJS.
Granted one can wire into the $scope (or some other object) any number of methods that execute behavior, still this does seem to be the same as MVC's action methods, since they do not automatically accept direct route input.
I'm interested because I'm trying to convert an existing Asp.net MVC app to angular and I'm trying to decide the best organizational breakdown for the controllers.
Are my various assumptions correct?
Do AngularJS Controllers ever use more than one action/setup method?
Are angular controllers ever broken down into individual actions?
Or does an angular controller have more or less one action, though the routing and view might be different?
Update:
Requested Example - AngularJS controller:
myApp.controller('DoubleController', ['$scope', function($scope) {
$scope.double = function(value) { return value * 2; };
}]);
Asp.Net Controller MVC example:
public class CardController : Controller
{
private readonly IService _service;
public CardController(IService service)
{
_service = service;
}
public ActionResult Index(Guid gameId)
{
var model = _service.GenerateCardBuyDisplayModel(gameId);
return View(model);
}
public ActionResult BuyCards(ExecuteBuyModel input)
{
_service.ExecuteBuy(input.GameId, input.CardsToBuy);
return RedirectToAction("Index", "Game", new { id = input.GameId});
}
}
Ruby on Rails controller example:
class ClientsController < ApplicationController
# This action uses query string parameters because it gets run
# by an HTTP GET request, but this does not make any difference
# to the way in which the parameters are accessed. The URL for
# this action would look like this in order to list activated
# clients: /clients?status=activated
def index
if params[:status] == "activated"
#clients = Client.activated
else
#clients = Client.inactivated
end
end
# This action uses POST parameters. They are most likely coming
# from an HTML form which the user has submitted. The URL for
# this RESTful request will be "/clients", and the data will be
# sent as part of the request body.
def create
#client = Client.new(params[:client])
if #client.save
redirect_to #client
else
# This line overrides the default rendering behavior, which
# would have been to render the "create" view.
render "new"
end
end
end
If you look at these three examples, the AngularJS has only a single constructor/setup method, while the Asp.net MVC example has a constructor and two action methods The Ruby of rails example does not even have a visible constructor, simply action methods. The Asp.net MVC example (or the Ruby on Rails example) is similar to how many actions work in other MVC implementations. In AngularJS I guess there is only one action/constructor method, where in one would attach any additional behavior. The Asp.net MVC example on the other hand has both a constructor and two action methods that can both be routed to in different ways. similar to the single AngularJS contrustor/action.
From what I can tell AngularJS controllers do not have traditional action methods, like MVC controllers. Instead whatever a controller can do must be defined in the single constructor method or configured using the routing in the application configuration. If you need a setup that is different from the constructor then its probably a good place to use a new controller.
Later I asked in the angular IRC chatroom:
Angular controllers don't really have action methods like they do in
other MVC implementations like Ruby on Rails or Asp.net MVC? right?
to which wafflejock responded:
nothing is required or given to you basically
and
it's just the barebones structure with no "base" objects to extend
from or interface to implement
robdubya also mentioned:
unless you want to get all future-sexy
https://gist.github.com/robwormald/bc87cb187e8f96c4e5f0
I'm trying to re-use a plugin's controller method for adding a user (the plugin is usermgmt, a purchased plugin). The plugin's method does everything I need, however it finishes by doing a redirect to another page.
I don't want to modify the plugin code, and I am wondering if it is possible to temporarily disable the Controller:redirect method
class MyController extends AppController{
function signUpUser(){
//populate $this->request->data to be what plugin controller method is looking for
//temporarily disable Controller::redirect
//make call to plugin's controller method
$usersController = new UsersController();
$usersController->constructClasses();
$usersController->addUser();
//re-enable Controller::redirect
//Do my own redirect
$this->redirect('/welcome');
}
}
You are not supposed to use controllers that way, there's probably a model or component which you could use instead. Extending the users controller may also be an option. Anyways, since you said you've bought it, why don't you contact the support, they should know best how their stuff works?
That being said, let me answer the actual question. In order to be able to disable redirection via your controller, you would have to override the Controller::redirect() method and implement some logic that allows you to disable the functionality.
Here's an untested example, it uses a property which defines whether redirects are enabled:
public $redirectEnabled = true;
public function redirect($url, $status = null, $exit = true) {
if($this->redirectEnabled) {
parent::redirect($url, $status, $exit);
}
}
This should be pretty much self-explantory.
In your controller you could set $this->redirectEnabled to true/false to enable/disable the redirect functionality.
I'm stating to learn AngularJS, coming from a lot of different MV* frameworks.
I like the framework, however I'm having trouble with passing data between Controllers.
Suppose I have a screen with some input (input.html) and a controller, let's say InputCtrl.
There's a button on this view which takes you to another screen, let's say approve (approve.html) with a controller ApproveCtrl.
This ApproveCtrl needs data from the InputCtrl. This seems like a very common scenario in bigger applications.
In my previous MV* frameworks, this would be handled like (pseudo-code):
var self = this;
onClick = function() {
var approveCtrl = DI.resolve(ApproveCtrl);
approveCtrl.property1 = self.property1;
approveCtrl.property1 = self.property2;
self.router.show(approveCtrl);
}
It would work like Controller- first.
You create the controller first, having a chance to put it in the right state; afterwards the View gets created.
Now, in AngularJS, I'm handling this like:
var self = this;
onClick = function(){
self.$locationService.path('approve');
}
This works like View-first.
You say to which view / route to navigate, the Controller gets created by the framework.
I find it hard to control the state of the created Controller and pass data to it.
I've seen and tried following approaches, but all have it's own issues in my opinion:
Inject a shared service into InputCtrl & ApproveCtrl and put all data to be shared on this service
This looks like a dirty work-around; the state in the shared service becomes global state, while I just need it to pass data to the ApproveCtrl
The lifetime of this shared service is way longer than what I need it for - just to pass data to the ApproveCtrl
Pass the data in $routeParams
This gets quite messy when having the pass a lot of parameters
Use $scope events
Conceptually, this is not something I would use events for - I just need to pass data to the ApproveCtrl, nothing event-ish
This is quite cumbersome; I have to send an event to the parent first, that would then broadcast it to it's children
Am I missing something here? Am I creating too many small Controllers?
Am I trying to hold on to habits from other frameworks too much here?
In terms of structure AngularJS is more Modular than MVC one.
Classic MVC describes 3 simple layers which interact with each other in such way that Controller stitches Model with View (and Model shouldn't rather work with View directly or vice versa).
In Angular you can have multiple, some completely optional, entities which can interact between each other in multiple ways, for example:
That's why there are multiple ways of communicating your data between different entities. You can:
Send messages directly between controllers using difference between this and $scope
Send messages using events
Send messages using shared system (Note: same link as above, answer shows both techniques)
or
Send messages using AJAX backend
Send messages using external system (such as MQ)
...and a lot more. Due to its diversity Angular allows developer/designer to choose way they are most comfortable with and carry on. I recommend reading AngularJS Developer Guide where you can find blessed solutions to some common problems.
If your intent is to simply share data between two views, a service is probably the way to go. If you are interested in persisting to a data store, you may want to consider some sort of back-end service such as a REST API. Take a look at the $http service for this.
Even if XLII gave a complete response, I found this tutorial using a service. It's very interesting and a simple way for sharing data between controlers using the 2 ways binding property : https://egghead.io/lessons/angularjs-sharing-data-between-controllers
I still havn't used it for now.
Otherwise there is also this other way, based on events : http://www.objectpartners.com/2013/08/21/using-services-and-messages-to-share-data-between-controllers-in-angularjs/
If you wish to pass simple string data from one page (page1) to another page (page2), one solution is to use traditional url parameters. Invoke the page2 route url with parameter like "/page2/param1/param2". The controller of page2 will receive the parameters in "routeParams". You will be able to access parameteres as routeParams.param1 and routeParams.param2. The code below is adopted from: How to get the url parameters using angular js
Invoke the page2 route from page1's controller(js) or a url in its html with parameters as:
"/page2/param1/param2"
Page2 route:
$routeProvider.when('/page2/:param1/:param2', {
templateUrl: 'pages/page2.html',
controller: 'Page2Ctrl'
});
And the controller:
.controller('Page2Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
$scope.param1 = $routeParams.param1;
$scope.param2 = $routeParams.param2;
...
}]);
Now you can access the parameters (param1 and param2) values in your page2's html/template as well.
I have a shopping cart site and on successful purchase I need to send a mail with all product details in the order as attachment. I have used fat model skinny controller approach and all my functions are in model. I have a controller action which will give the order details by passing order id along with view. Using dompdf I can convert this html to pdf and can create a file. So for creating attachment I can use the same function by passing some parameter. My mail sending code is in model. From here I need to call the controller action and need to get the pdf file name that just created. I know calling controller action from model is against MVC architecture. But how can I achieve this functionality ?
'fat' models is a good thing to do, however, try not to put things in a Model that should not be in a Model. In MVC, Models should handle all things related to data.
Fat models
The 'fat' Model concept is to reduce the amount of code in your Controller, by moving data related code to the Model. for example:
In stead of this; (in your Controller):
public function view($id)
{
$this->request->data = $this->SomeModel->find('first', array(
'fields' => array(
// list of fields to retrieve
),
'conditions' => array(
// conditions
),
// etc.
);
}
Move the find instructions to a method inside your model and use this:
public function view($id)
{
$this->request->data = $this->SomeModel->someMethod($id);
}
Other locations to put your code
Code that is not related to data, can also be moved outside your Controller (to make it 'skinny'). CakePHP offers other locations to move your code to, for example inside a Component
Then inside your Controller;
this:
public function view($id)
{
$this->request->data = $this->SomeModel->someMethod($id);
// use functionality of a component
$this->SomeComponent->doSomething();
}
Triggering functionality via Events
To keep code and logic outside your controller, CakePHP 2.x now offers an 'Event' system. This allows you to execute code if a certain event happens. You can pass additional information through the events (the event will become a 'communication channel' that passes through your application).
Sending e-mails for certain events is a good example. The CakePHP also uses sending mails to illustrate the Event system in CakePHP; this is probably what you are looking for:
Dispatching Events - send emails
Make your model method return the data so you have it in the controller and pass it to the other model function together with your pdf related data.
I am using auth component and it works ok.
But in my default layout before the content I have some menu which is different if user is logged in. So I want to determine if user is logged in or not - normally I use $this->Auth->user('id') but $this->Auth doesnt work in layout (it only works in view which controller is using Auth component).
How to do it?
In beforeRender() just call
$this->set('userData', $this->Auth->user());
and set the data to the view and do your checks in the view.
In order to get data in layout, you should call beforeRender() method in AppController.
Passing it through session is not a good idea IMHO. It might be not the usual case but at least I prefer to do things solid: If you're using the session for that your code will fail in a system that is not using a session (stateless auth). Overall I'm not a big fan of accessing the session in a view at all. Session is for me more like a datasource.
You can read Auth data from session. Something like:
$user = $session->read('Auth');
Don`t forget to add Session helper in your AppController.
var $helpers = array('Session');