How to handle 404 Not Found in Ember REST Adapter - ruby-on-rails-3

I am using Ember 1.0.0 pre and use the REST Adapter to fetch an object from a Rails API.
I render a template if the object is found via the REST API and set the found model object as the model of the view. In order to do that, I use the model hook in the Route and everything seems to work fine.
Naturally, I want to render some special 404 template/view if the model object is not found.
The problem is that the model hook stops the processing if the REST adapter returns an error.
I saw in the ember-data roadmap that error handling is not yet supported.
What I don't understand is why does Ember not call the redirect hook in the Route if an error occurs. (And how can I handle such errors?)
Here is the Route:
App.MyRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('mymodel')
},
model: function(params) {
return App.MyModel.find(params.id);
},
redirect: function() {
// I want to redirect to another route
// if the model is not found via the find method.
// However, this hook is not called if the previous one
// does not return an object
}
})
Is there any other solution? I know, I can check in the template if the model of the view is null and then render a partial template, but the problem is that the view is not rendered at all (I assume exactly because of the same problem).

What I would do is create an abstract, and then have your routes extend that route. I've created a working JSFiddle for you, but please let me explain.
Our App.IndexRoute can contain all of the logic in App.MyRedirectRoute, but because other routers might require the same functionality, it's better to create an abstract so if/when you need this functionality again, you can just extend App.MyRedirectRoute again to prevent re-writing code.
Unfortunately Ember.JS doesn't have the logic to detect if the model is empty and to render a different page. Maybe in the future this will be a reality! (It would be lovely!), but we can do this ourselves.
Our App.IndexRoute is nice and simple:
App.IndexRoute = App.MyRedirectRoute.extend({
defaultRender: 'home',
errorRedirectTo: '404',
model: function(params) {
// We've found a model!
return Ember.Object.create({ params: params });
// We've not found a model!
return null;
}
});
If the model is valid then we can render the home route, otherwise we'll render the 404 route. Nothing changes in returning the model from the route.
It's the renderTemplate that contains our logic for rendering the appropriate view. So we therefore overload the renderTemplate method in our abstract and do the logic stuff:
If the model is considered empty by Ember.JS, then we'll render the view as specified by errorRedirectTo;
If the model is considered valid, then we'll render the default, as specified by defaultRender.
If the model is empty, then we'll simply specify that we wish to render the 404 route (errorRedirectTo), but by default we'll want to render the default route (defaultRender).
To see it in action, take a look at the aforementioned JSFiddle. Comment out line #27 to see the 404 page rendered, because the object is null.
I hope this helps!

Related

Custom PageLayoutComponent

To have specific layout for some pages at our project we create few custom PageLayoutComponent's. Some contfiguration example:
{
// #ts-ignore
path: null,
canActivate: [CmsPageGuard],
component: CartPageLayoutComponent,
data: {
cxRoute: 'cart',
cxContext: {
[ORDER_ENTRIES_CONTEXT]: ActiveCartOrderEntriesContextToken,
},
},
},
All work fine with storefront until you will not try to select specific page at smartedit. As result it not use our custom CartPageLayoutComponent, but will use PageLayoutComponent for rendering.
Probably this is because it's not a normal route navigation. Can somebody from spartacus team suggest how this bug can be fixed?
Probably this is because it's not a normal route navigation
I believe your Route should be recognized normally, there is nothing special in adding a custom Angular Route.
So I guess there is something special about the page or URL of Spartacus Storefront that runs in your SmartEdit.
It's hard to tell the reason of your problem without more debugging.
You said your app works as expected when run differently (locally?), but when used in SmartEdit, then there is a problem. Please identify factors that makes the run SmartEdit different from your (local?) run. And try to isolate them. Guesses from top of my head:
production vs dev mode of the build?
exact URL of the cart page?
any difference in configuration between a local version and deployed one to be used in SmartEdit?
I would also add some debugging code to better know which routes configs are available and which one is used for the current route. For debugging purposes please add the following constructor logic in your AppModule:
export class AppModule {
// on every page change, log:
// - active url
// - the Route object that was matched with the active URL
// - all the Route objects provided to Angular Router
constructor(router: Router) {
router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
console.log({
activeUrl: router.url,
activeRouteConfig:
router.routerState.snapshot.root.firstChild.routeConfig,
allRoutesConfigs: router.config,
});
}
});
}
}
The pages opened in SmartEdit have the same route of cx-preview (e.g. to open faq page in smartedit, request is https://localhost:4200/electronics-spa/en/USD/cx-preview?cmsTicketId=xxxxx. backend can get page id from cmsTicketId). If you want to change the page layout, you can consider use PageLayoutHandler. Spartacus has some PageLayoutHandlers, e.g.
{
provide: PAGE_LAYOUT_HANDLER,
useExisting: CartPageLayoutHandler,
multi: true,
},

Vue Lifecycle — which happens before page renders?

Using Vue 2, I want to run a check before any page elements are rendered, to determine whether the user is signed in and if so, redirect them to another page.
Looking at the Vue Lifecycle, it's my understanding that beforeMount is first in this cycle. However, the page still appears for half a second before redirecting (in my case, to Dashboard)
beforeMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.$router.push({ name: 'Dashboard'})
}
});
}
I've tried other Lifecycle options and none of the others work either. What am I doing wrong?
Looking at the Vue's lifecycle diagram:
beforeCreate and created hooks are earlier than beforeMount. You should use either one of them.
You have to use Guard for this. you have to check auth before going to route on router.js file. you can use beforeEnter into your route path.
read more about that here: https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard

Pass an object as param to router.push (vue-router#4.05)

Problem
router.push({name:"Order", params: {obj: {}})
fails to push obj: {}, instead the route receives obj: '[object Object]'
Situation
I have a route setup like this
{
path: '/Order',
name: 'Order',
component: () => import("../views/Order.vue"),
props: route => {
return route.params.obj // '[object Object]'
},
}
this results in props not being defined in Order.vue
Expected Result
{
...
props: route => {
return route.params.obj // '{}'
},
}
Based on this answer objects work in older versions
What I've tested
I've used jest to inspect the arguments passed to router.push and they appear as they should: {name:"Order", params: {obj: {}}
Any Ideas?
Passing objects to params was never supported in Vue-router
It sort of worked in Router v3 with $router.push - the target component received the object. BUT as soon as user started using browser navigation (Back button) OR copy/pasting URL's, this solution was broken (you can try it here - just click the button and then use back and forward controls of the frame)
As a rule of thumb - if you want to pass anything to the target route/component, such data must be defined as parameters in the route definition so it can be included directly in the URL. Alternatives are passing data in a store, query params (objects needs to be serialized with JSON.stringify), or using the history state.
This was true for Vue-router v3 and is still for Vue-router v4
Note
Just to explain alternatives. By "store" I do not mean just Vuex. I understand Vuex can be overkill for some small applications. I personally prefer Pinia over existing Vuex. And you can create global state solution yourself with composition API very easily...

State is always undefined when using the #connectTo decorator in a config pipeline step in Aurelia Store

I have an Aurelia app using Aurelia Store. I'm having some trouble when using the #connectTo decorator in an Aurelia pipeline step.
I have added the following step to my config pipeline:
config.addPipelineStep('authorize', AuthorizeStep);
And this step looks like:
#connectTo()
export class AuthorizeStep {
state: State;
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().find(x => x.config.isAdmin))
{
if (!this.state.user.isAdmin) {
return next.cancel();
}
}
return next();
}
}
However, my state is always undefined. Looking at other parts of my project, I can see the state and user are being populated, but it seems like in this AuthorizeStep it doesn't seem to work.
I think this issue may be due to the fact that my AuthorizeStep doesn't have a bind lifecycle method, but if so, what can I do about this?
The maintainers of Aurelia responded (only after I raised an issue on their GitHub) here.
Basically, as the bind lifecycle does not exist within this class, the #connectTo decorator won't work. Instead, I will need to manually inject the Store and subscribing to the state.

How to defer routes definition in Angular.js?

I have configured some basic routes that are available for all users before they log in:
App.config(function ($routeProvider) {
$routeProvider.
when('/login', { templateUrl: 'views/login.html', controller: PageStartCtrl.Controller }).
otherwise({ redirectTo: '/login' });
});
So the only thing user can do is to log in. After the user logs in, I would like to register additional routes like this:
$http
.post('api/Users/Login', { User: userName, Password: userPassword })
.success(function (response : any) {
App.config(function ($routeProvider) {
$routeProvider
.when('/dashboard',
{ templateUrl: 'part/dashboard.html',
controller: DashboardCtrl.Controller });
});
However, I suppose I should call .config method only once, because the $routeProvider is brand new instance that knows nothing about /login route. Further debugging showed me that the first instance of $resourceProvider is used when resolving view change.
Q: Is there a way how to register routes later?
Solution from Add routes and templates dynamically to $routeProvider might work, but is quite ugly (involved global variable nastyGlobalReferenceToRouteProvider).
Since routes are defined on a provider level, normally new routes can only be defined in the configuration block. The trouble is that in the configuration block all the vital services are still undefined (most notably $http). So, on the surface it looks like w can't define routes dynamically.
Now, it turns out that in practice it is quite easy to add / remove routes at any point of the application life-cycle! Looking at the $route source code we can see that all the routes definition are simply kept in the $route.routes hash which can be modified at any point in time like so (simplified example):
myApp.controller('MyCtrl', function($scope, $route) {
$scope.defineRoute = function() {
$route.routes['/dynamic'] = {templateUrl: 'dynamic.tpl.html'};
};
});
Here is the jsFiddle that demonstrates this in action: http://jsfiddle.net/4zwdf/6/
In reality, if we want to be close to what AngularJS is doing the route definition logic should be a bit more complex as AngularJS is also defining a redirect route to correctly handle routes with / at the end (make it effectively optional).
So, while the above technique will work, we need to note the following:
This technique depends on the internal implementation and might break if the AngularJS team decides to change the way routes are defined / matched.
It is also possible to define the otherwise route using the $route.routes as the default route is stored in the same hash under the null key
I found that the answer by #pkozlowski.opensource works only in angularjs 1.0.1. However, after angular-route.js becomes an independent file in the later version, directly set the $route doesn't work.
After reviewing the code, I find the key of $route.routes is no longer used to match location but $route.route[key].RegExp is used instead. After I copy the origin when and pathRegExp function, route works. See jsfiddle:
http://jsfiddle.net/5FUQa/1/
function addRoute(path, route) {
//slightly modified 'when' function in angular-route.js
}
addRoute('/dynamic', {
templateUrl: 'dynamic.tpl.html'
});