AJAX request to Laravel resource controller, best practice - javascript

I have set up a resource controller in Laravel with the following index function:
public function index()
{
if (!Auth::check()) {
return redirect()->route('login');
}
$decks = Auth::user()->decks->sortByDesc('name');
return view('decks.index')->with('decks', $decks);
}
On a different page I also need the $decks variable via a AJAX call. Right now I have set up an additional route to my controller, from which I can retrieve the decks via a GET request:
public function getDecks()
{
if (!Auth::check()) {
return;
}
$decks = Auth::user()->decks->sortByDesc('name');
return response()->json($decks);
}
My question: Is there a way to get the $decks variable via a request directly to index or is my solution the way to go?
If I make a get request to index I get the HTML of the decks.index view, but how can I access (if possible) the $decks variable?
I guess what I don't really grasp is this: What happens to $decks in the ->with('decks', $decks) statement? I know I can then access $decks using blade syntax on that page, but from where does it access the data and can I also access it via AJAX?

Yes! You can return different results depending on what kind of request was done. There is no need for 2 routes:
public function index(Request $request)
{
$decks = Auth::user()->decks->sortByDesc('name');
if ($request->ajax()) {
return response()->json(['decks' => $decks]);
} else {
return view('decks.index')->with('decks', $decks);
}
}
#Sven Hakvoort is right, you should check for authentication in the route definition:
Route::group(['middleware' => 'auth'], function () {
Route::get('/decks', 'DecksController#index');
// ... some other routes which requires authentication
}

You can not retrieve the $decks from the index route directly. When you call view(..)->with(...) it is internally passed to the blade template processor which also receives the $decks variable, the HTML is then build on the server-side and the 'compiled' HTMl is then returned to the browser. So once the server returns the result the $decks variable is not present anymore. Because of this behavior it is not possible to do what you want.
So yes, your solution is the way to go, although you might consider wrapping the Auth::check() in a middleware and move the call to $decks to a separate function in order to simplify your code.
I hope this answers your question, if anything is unclear feel free to ask!

Your solution is the proper way. It is not typically a good idea to handle any business logic or data retrieval using your routes - using a controller is preferred, which is what you are doing. If you want to avoid the if (Auth::check()) statement in your controller, you could add the auth middleware directly to that route like this.
Route::get('example', 'YourController#index')->middleware('auth');
Regarding your question about ->with('decks', $decks):
That function is sending your $decks variable to the view to be rendered in the blade template. That data is used by the server to render the page, and then the variable is discarded. If you want to also be able to work with that data on your page using javascript, you could do something like this in your blade template.
<script>
var decks = {!!$decks->toJSON()!!};
</script>

Related

Why move my $http calls into a service?

Currently I have calls like this all over my three controllers:
$scope.getCurrentUser = function () {
$http.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I see lots of advice to encapsulate the $http calls into an HttpService, or some such, but that it is much better practice to return the promise than return the data. Yet if I return the promise, all but one line in my controller $http call changes, and all the logic of dealing with the response remains in my controllers, e.g:
$scope.getCurrentUser = function() {
RestService.post("/Account/CurrentUser", {}, postOptions)
.then(function(data) {
var result = angular.fromJson(data.data);
if (result != null) {
$scope.currentUser = result.id;
}
},
function(data) {
alert("Browser failed to get current user.");
});
};
I could create a RestService for each server side controller, but that would only end up calling a core service and passing the URL anyway.
There are a few reasons why it is good practice in non-trivial applications.
Using a single generic service and passing in the url and parameters doesn't add so much value as you noticed. Instead you would have one method for each type of fetch that you need to do.
Some benefits of using services:
Re-usability. In a simple app, there might be one data fetch for each controller. But that can soon change. For example, you might have a product list page with getProducts, and a detail page with getProductDetail. But then you want to add a sale page, a category page, or show related products on the detail page. These might all use the original getProducts (with appropriate parameters).
Testing. You want to be able to test the controller, in isolation from an external data source. Baking the data fetch in to the controller doesn't make that easy. With a service, you just mock the service and you can test the controller with stable, known data.
Maintainability. You may decide that with simple services, it's a similar amount of code to just put it all in the controller, even if you're reusing it. What happens if the back-end path changes? Now you need to update it everywhere it's used. What happens if some extra logic is needed to process the data, or you need to get some supplementary data with another call? With a service, you make the change in one place. With it baked in to controllers, you have more work to do.
Code clarity. You want your methods to do clear, specific things. The controller is responsible for the logic around a specific part of the application. Adding in the mechanics of fetching data confuses that. With a simple example the only extra logic you need is to decode the json. That's not bad if your back-end returns exactly the data your controllers need in exactly the right format, but that may not be the case. By splitting the code out, each method can do one thing well. Let the service get data and pass it on to the controller in exactly the right format, then let the controller do it's thing.
A controller carries out presentation logic (it acts as a viewmodel in Angular Model-View-Whatever pattern). Services do business logic (model). It is battle-proven separation of concerns and inherent part of OOP good practices.
Thin controllers and fat services guarantee that app units stay reusable, testable and maintainable.
There's no benefit in replacing $http with RestService if they are the same thing. The proper separation of business and presentation logic is expected to be something like this
$scope.getCurrentUser = function() {
return UserService.getCurrent()
.then(function(user) {
$scope.currentUser = user.id;
})
.catch(function(err) {
alert("Browser failed to get current user.");
throw err;
});
});
It takes care of result conditioning and returns a promise. getCurrentUser passes a promise, so it could be chained if needed (by other controller method or test).
It would make sense to have your service look like this:
app.factory('AccountService', function($http) {
return {
getCurrentUser: function(param1, param2) {
var postOptions = {}; // build the postOptions based on params here
return $http.post("/Account/CurrentUser", {}, postOptions)
.then(function(response) {
// do some common processing here
});
}
};
});
Then calling this method would look this way:
$scope.getCurrentUser = function() {
AccountService.getCurrentUser(param1, param2)
.then(function(currentUser){
// do your stuff here
});
};
Which looks much nicer and lets you avoid the repetition of the backend service url and postOptions variable construction in multiple controllers.
Simple. Write every function as a service so that you can reuse it. As this is an asynchronous call use angular promise to send the data back to controller by wrapping it up within a promise.

Angularjs, do something when document loads

I am developing an angular js application, and i need to run a function, via service provider, before everything else. The function is a get request, which returns two variables in JSON, which returns either true orfalse.
If the variable is true, then load another state (i am using ui router). How can i do that?
In angular the document loaded is not really has a good usage, because it is a framework and the we need to count on angular loaded/ready instead. Use angular.module('yourApp').run() like below:
var app = angular.module('yourApp');
app.run(['$rootScope', '$http', function($rootScope, $http){
$http('http://localhost/status/').then(function(res){
$rootScope.somethingStatus = res; // assuming that your response is either true of false.
});
$rootScope.$watch('somethingStatus', function(nv){
// here is where you know your request has been done...
if(nv === true) {
// ...
}
else if(nv === false) {
// ...
}
});
}]);
NOTE: please aware what run in app.run() will be fired before any controller initialing or view rendering. So later on when you want to access the value of what you been got. Inject $rootScope to the controller (or any place) when you needed it.
EDIT: fix some typos. Updated answer again, credit to #georgeawg and #Pierre Emmanuel Lallemant. Thanks you guys for the correction
You should write a controller for the page where you do the request (in order to display something to the user), with a default html view, and when you receive the response, then you change the state to use the desired controller.

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

Where to store remote data used by directive inside ngView? (AngularJS)

Just for example:
I have directive inside ngView with this structure:
.directive('features', function() {
templateUrl: '.../',
link: function(scope) {
// HTTP req, getting remote data.
// Store the remote data to the scope
}
})
When I change the route and return it back, the directive link option is executed again, the scope is empty. It need to wait some time for data-response from the remote server and then showing the data. I trying to avoid the layout stretching.
I am new to angular, I was read the documentation.
My question is: In this case, where the data is good to be saved? Do I need to make a controller? Or I can just cache it?
Note: This directive repeating an array (remote data) and making "features" HTML layout. This directive will be used in another routes with another behaviors.
I do not need code explanations, I can read docs. I accept terminology.
You could store the data in a service, basically to act as a cache as you mentioned. If you need to have that data available on page reloads (hard refresh, your app is reloaded again) you can store it in a cookie/local storage.
app.service("featuresService",function($http){
var cachedFeatures=null;
return{
getFeatures:function(callback){
if(!cachedFeatures){
$http.get("...").success(function(data){
cachedFeatures=data;
callback(data);
})
}else{
callback(cachedFeatures);
}
}
}
})

Redirect requests via routes sails.js

I would like to have a sub part in my application:
For instance, all requests send to www.example.com/backoffice/user should be redirected in my BackofficeUserController.js.
I ue sails.js, I know I have to do that with the config/routes.js, I just don't know how.
I tried this:
'/backoffice/:controller/:action?': {
controller : 'backoffice' + ':controller',
action : ':action'
}
But it doesn't works. Any idea?
The doc doesn't explains too much about dynamic routes. http://sailsjs.org/#!documentation/routes
This is actually a decent use-case for nested controllers. If you do sails generate controller backoffice/user, you'll end up with a controllers/backoffice/userController.js file corresponding to a controller class called Backoffice/UserController. All requests to /backoffice/user/:action will then be automatically routed to that controller.
All captured params get passed to the controller's action method on your request object. Maybe you should be more explicit when defining your routes or use your UserController as a proxy.
You could have backoffice users?
'/user/backoffice': 'UserController.backoffice'
or having a backoffice controller handle user requests
'/backoffice/user/:id': 'BackofficeController.user'
or (i'm not sure if controllers are global but you could require the controller from another controller and use its methods inside UserController)
module.exports = {
'/backoffice/user/:id': 'UserController.backoffice'
};
and then in your UserController
var BackofficeController = require('./BackofficeController');
module.exports = {
user: function(req, res) {
// Do something using BackOffice methods conditionally?
}
};
Many ways to achieve the same result. Not sure what the best approach is since I haven't run into this personally. But I would suggest sticking with Sailsjs conventions.

Categories

Resources