CakePHP - Render views using elements vs ajax - ajax

If views from different controllers use the same element, requiring different controllers to pass the same datas (hence maybe doing the same processing) to the views, wouldn't it be better to make an AJAX call to a single controller?
Let's say we have this:
Model/Post.php
Model/User.php
Model/Service.php
Controllers/UsersController.php
Controllers/ServicesController.php
Views/Users/view.ctp
Views/Services/view.ctp
Views/Elements/list_users_post.ctp
A user belongs to a service, and a user has many posts.
In Views/Services/view.ctp, I want to display a list of each user of a particular service, and for each user, some related infos and a list of his 10 last posts.
In Views/Users/view.ctp, I want to display user's related infos and a list of his 10 lasts posts.
The element Views/Elements/list_users_post.ctp allows me to factor the code displaying a table of a user's posts. It needs the var $userPostList to be set, and to be structured the same as the result of $this->Post->find('all', array('conditions' => array('user_id' => $userId))).
So in my UsersController::view($userId) and ServicesController::view($serviceId) actions, I end up with some duplicated code retrieving users' posts.
I thought to refactor the code so the action ServicesController::view($serviceId) don't make any find on Post model, but instead, the view Services/index.ctp makes AJAX calls to the action UsersController::view($userId) for each user. That way, no more duplicated code, but with the overhead of AJAX calls.
Any thoughts?

A good way to avoid duplicate code in your case is to move it to the model layer. So instead of this in the controller:
$this->Post->find('all', array('conditions' => array('user_id' => $userId)))
You would have this in the controllers:
$this->Post->getUserPosts($userId);
And this in the Post model:
function getUserPosts($userId){
return $this->find('all', array('conditions' => array('user_id' => $userId)));
}
AJAX calls are also a good solution but even with them it would be best to keep the business logic in the models if possible.
Other ways to avoid duplicate code include(but are not limited to):
Using Helpers in Views
Using Components in Controllers
Using Behaviors in Models
That should get you started on keeping your code DRY.

Related

Convention for a controller that generates a view from several models

In Sails.js, a route is set up against a controller method which can render a view. For the most part, this is straightforward, i.e. you could set up a GET /users route that points to UserController.find (which is usually set up automatically anyway).
However, say the home page of a blog renders the 10 most recent posts in the main section and a column with a list of authors and categories. The controller method has to fetch posts, authors, and categories before rendering the view and sending it back to the client. Clearly, a method like this doesn't really belong in PostController, AuthorController, or CategoryController.
What's the best thing to do in this situation? Create a controller for rendering views that rely on data from multiple models? Is there a good name for such a controller?
Thanks!
What I would do (this is purely opinion-based) is creating a PageController and create an action for each page you'd want.
For your home page example you can create a home action, get whatever you need and then render it with res.ok() (if everything is fine).
Another option would be to use Sails as a pure API and use HTTP requests (Ajax) or sockets to get your data in JSON. If you want to do so, I'd advise you to use a front end framework such as Angular, Ember, React...
By the way you could also create actions rendering HTML in your existing controllers and create a route to hit them through Ajax requests and just print them in your page. I'd prefer the 2nd solution because it takes full advantage of the Blueprint API (you don't need new controller or action whatsoever).
As Yann pointed out, this answer has to be a little opinionated. It seems that you are using the views system and not building a single page application. For the home page, I would go for an IndexController.js file with a home(req, res) action.
// api/controllers/IndexController.js
module.exports = {
home: function (req, res) {
// Retrieve all the information you need
// Take care about managing the asynchronous calls before rendering the view
return res.view('homepage');
}
};
Declare the route
// config/routes.js
module.exports.routes = {
'get /': 'IndexController.home'
}
Create the view in views/homepage.ejs.

How to pass route values to controllers in Laravel 4?

I am struggling to understand something that I am sure one of you will be able to easily explain. I am somewhat new to MVC so please bear with me.
I have created a controller that handles all of the work involved with connecting to the Twitter API and processing the returned JSON into HTML.
Route::get('/about', 'TwitterController#getTweets');
I then use:
return View::make('templates.about', array('twitter_html' => $twitter_html ))
Within my controller to pass the generated HTML to my view and everything works well.
My issue is that I have multiple pages that I use to display a different Twitter user's tweets on each page. What I would like to do is pass my controller an array of values (twitter handles) which it would then use in the API call. What I do not want to have to do is have a different Controller for each user group. If I set $twitter_user_ids within my Controller I can use that array to pull the tweets, but I want to set the array and pass it into the Controller somehow. I would think there would be something like
Route::get('/about', 'TwitterController#getTweets('twitter_id')');
But that last doesn't work.
I believe that my issue is related to variable scope somehow, but I could be way off.
Am I going down the wrong track here? How do I pass my Controllers different sets of data to produce different results?
EDIT - More Info
Markus suggested using Route Parameters, but I'm not sure that will work with what I am going for. Here is my specific use case.
I have an about page that will pull my tweets from Twitters API and display them on the page.
I also have a "Tweets" page that will pull the most recent tweets from several developers accounts and display them.
In both cases I have $twitter_user_ids = array() with different values in the array.
The controller that I have built takes that array of usernames and accesses the API and generates HTML which is passed to my view.
Because I am working with an array (the second of which is a large array), I don't think that Route Parameters will work.
Thanks again for the help. I couldn't do it without you all!
First of all, here's a quick tip:
Instead of
return View::make('templates.about', array('twitter_html' => $twitter_html ))
...use
return View::make('templates.about', compact('twitter_html'))
This creates the $twitter_html automatically for you. Check it out in the PHP Manual.
 
Now to your problem:
You did the route part wrong. Try:
Route::get('/about/{twitter_id}', 'TwitterController#getTweets');
This passes the twitter_id param to your getTweets function.
Check out the Laravel Docs: http://laravel.com/docs/routing#route-parameters

Loading external data into Ember template using ember-data/DataStore

Here is what I'm tyrnig to do:
Make ajax request to retrieve JSON data from a PHP script
Insert that information into DataStore Models
Store those models within a controller
Display the information using {{#each}} with a handlebars template
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
What is the best way to insert the JSON data into the DS model?
What is the best way to then sync the models up with a Controller?
Any examples that implement all of the 4 steps would also be very helpful, since I can't seem to find any.
<edit>
Like I said in the comments, this questions asks a lot at once, so to follow up, here's a work in progress fiddle: http://jsfiddle.net/schawaska/dWcUp/
This is not 100%, but covers some of your questions. It uses the FixtureAdapter.
I'll be updating it as I find time.
</edit>
1 Make ajax request to retrieve JSON data from a PHP script
Ember-Data will take care of that for you. Consider the following:
window.App = Em.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.RESTAdapter'
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
imageUrl: DS.attr('string')
})
The code above defines a data store (almost like an ORM) within your client app. Ember uses convention over configuration (heavily), so as per configuration this code expects your backend to have a resource in the same domain as /products which talks to GET, POST, PUT and DELETE.
2 Insert that information into DataStore Models
Considering what I said above, by calling one of the following:
App.store.find(App.Product) or App.Product.find()
EmberData will fire a GET request through AJAX targeting your resource /products, and if you say App.Product.find(1), it will target /products/1.
Your app store will use its adapter and the adapter's serializer to translate the JSON result and materialize its data into your App.Product model.
3 Store those models within a controller
This is done when defining your application router. Regardless of what you do, Ember will run its own workflow, but it provides you several hooks, giving you the control of that specific action. Consider the following:
App.Router.map(function() {
this.resource('products');
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return App.Product.find();
}
});
The code above populates the model used in the products route (which you would access at http://yourdomain.com/#/products). Internally it will generate the code for your ProductsController, or you can define your own, which should extend ArrayController.
Controllers will have a content property which is an alias to the model or model collection. Again, convention over configuration.
4 Display the information using {{#each}} with a handlebars template
Providing you're following the conventions, in your handlebars template, you should iterate through your collection like this:
{{#each product in controller}}
{{#linkTo 'product' product}}
{{product.name}}
{{/linkTo}}
{{/each}}
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
Yes, simply call App.Product.find() for a product model and it return you a blank ModelArray while firing the AJAX request to the products resource in your backend, then materialize/populate your data into each model once it receives the data back from the server.
What is the best way to insert the JSON data into the DS model?
You shouldn't be concerned about this if you're using ember-data. The framework does that for you in most cases. That's why we love it. You might, however, have to configure mapping, namespace and plurals depending on your backend.
What is the best way to then sync the models up with a Controller?
Something similar to this:
var product = App.Product.createRecord({
name: 'laptop',
imageUrl: 'path/to/image.png'
});
product.save();
This should fire a POST or PUT request to your backend API.
You should definitely check:
http://emberjs.com/guides/
https://peepcode.com/products/emberjs
http://toranbillups.com/blog/archive/2013/01/03/Intro-to-ember-js-and-the-new-router-api/
Making the AJAX request
// Find all pets.
var pets = App.Pet.find();
// Find pet with ID of 1.
var pet = App.Pet.find(1);
Into DataStore Models
pets above will be a DS.RecordArray containing many App.Pet models, whereas pet is just one App.Pet.
Store in Controller
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Pet.find(4);
}
});
The router is used to setup the controller, and so we specify here that the IndexController should hold one App.Pet with the ID of 4. This can of course be dynamic. Since your controller represents only one model, it should be of the type ObjectController, but if it was used to store many pets, then it should be of the type ArrayController.
By specify the model, you will have access to it in your IndexController and index view (data-template-name="index"). This is because when you move into your index route, the IndexController is located/instantiated, IndexView is instantiated and placed into the DOM, all after consulting the IndexRoute for setting up the controller.
You can now do something like this in your template (although model. is not necessary): {{model.name}}, which will get you your pet's name.
Display using #each
Find all your pets using a modified version of the above code, but returning all of the pets. Remember, this is done by specifying no arguments to your find method:
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Pet.find();
}
});
Now we can do loop through all of the pets in the index template. Whilst there are many ways to loop, such as including content./model., excluding .content/model, using this, controller, et cetera..., it's not necessary, but that's for another day. What matters at the moment is that this will work for you, and will be the most self-intuitive:
{{#each pet in controller}}
{{pet.name}}
{{/each}}
I'll put together a jsFiddle for this if you would like. Please let me know.
Questions
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
Yes, that's Ember Data module which has some good guides on EmberJS.com.
What is the best way to insert the JSON data into the DS model?
Using Ember Data as per the examples up above.
What is the best way to then sync the models up with a Controller?
Using the model hook in the appropriate route to specify which model(s) your controller represents.

How disable getting data from related model in Yii

I don't need to get data from related model.
I have model Post
Also i have model Comment.
Every Post has comments.
I make related between models so:
class Post
public function relations()
{
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'post_id')
);
}
public function scopes()
{
return array(
'orderDesc'=>array(
'order' => 'post_id DESC',
),
);
}
public function findAllPosts()
{
return $this->orderDesc()->findAll();
}
If i get post from db I need comments - no problem.
Post::model()->findByPk()
But if i get all Posts - I don't need comments
Post::model()->findAllPosts()
But i get posts with comments. I think - it's not good for database - use additional joins and it's interesting fo me how disable getting data from related model.
I tried make so through scenario and change behaviour in method relations, but in method relations i got always - $this->scenario is empty.
In Yii the defined relations are by default loaded lazy loading.
It means that Yii will fetch the related models only when you call them in your code.
So if you do
Post::model()->findAll();
The related models (ie: comments) won't be loaded. But if your a calling
Post::model()->findAll();
CVarDumper::dump($posts[0]->comments);
Then a second DB request will be performed to fetch the related comments. This is why the code display the comments.
If you know that you are going to need a related model, the best solution is to use eager loading: it consists in loading the related models in the same request that load the initial model. To do it you need to specify the with method in your code.
Example:
Post::model()->with('comments')->findAll();
This method with can also be put in a defined scope in your model or in the default scope. If it's in the default scope then every time a model is loaded, his related models will be loaded in the same request.
A last note:
When you are using eager loading, one request is performed to fetch the related models, but this technique could not be perfect for every relation.
For example if you have a post and you want to load the author profile, since there is only one author, the request will be fast, returning only one line so the eager loading is good.
But then you want to load the comments. Since it's performing only one request, you'll have severals lines containing a lot of similar informations (all the informations about the post). In this case the pure eager loading is not the best solution.
The best way to handle those relations is to specify in the relations array the params together to false.
If you do so, 2 request will be performed: a first one to fetch the post and a second one to fetch the related comments.
Example:
Post::model()->with('comments' => array('together' => false))->findAll();
Source: Yii Guide

MVC in yii: how to build pages with several actions belonging to different models

I would like to know how you should write website pages that use for example 3 models and several actions on them.
Because there is usually only a controller involved with a page call and only a special action.
For example:
there should be a page which displays a group of people, and on that page I can edit the peoples names and assign new people to the group and i can add people as new managers of a group.
Does this page need its own controller or how do I program such pages?
Using your scenario, here is how I would set things up:
The controller you'd use for all related actions would be 'Group' (in Yii, 'ControllerGroup')
For your main page that displays the group of people, you could make an action in your Group controller called 'manage' (in Yii, the method name would be actionManage). Assuming you aren't going an Ajax route, for each person on the manage page you may have a link to edit that person. The links would point to the 'update' action. For adding, you'd want an action 'add'.
Your models involved would likely be User, Group, and UserGroup and you'd use them as necessary in any controller you have.
Actions can belong only to controllers. In each action you can work with any models of your application. In your case you must create a UserController and a list of actions (e.g. actionViewList, actionEdit, actionAssignToGroup).
Check this for more information: Yii Controller

Resources