How to redirect from pages without trailing slashes to pages with trailing slashes - sammy.js

I have sammy.js running in a knockout.js app. I'm currently trying to redirect routes that are missing the trailing slash (for example /#/stop/1/100032) I would like to redirect all such pages missing the trailing slash, to the page with the trailing slash.
To complicate things, I would also like to have an error page in the case that there is no such route.
function RoutesViewModel () {
var self = this;
self.router = Sammy(function () {
this.get('/#/stop/:agency_id/:stop_id/', function () {
app.page.state('bus');
app.stop.setCurrentById(this.params['agency_id'], this.params['stop_id']);
mixpanel.track('stop page load', {
'route': '/#/stop/' + this.params['agency_id'] + '/' + this.params['stop_id'] + '/',
});
});
this.get('/(.*[^\/])', function () {
this.redirect('/',this.params['splat'],'/');
});
});
self.router.error = function (message, error) {
app.page.header("Unable to find your page");
app.page.message("The page you've requested could not be found.<br />Click here to return to the main page.");
}
self.run = function () {
self.router.run();
}
}
Above is a selection of the routes I have so far. Unfortunately, when I go to the example url above, the page loads the error, instead of the correct /#/stop/1/100032/.
Any help would be greatly appreciated.

I know this is an old question, but I encountered this issue as well.
According to http://sammyjs.org/docs/routes routes are regular expressions. So this worked for me:
this.get('#/foo/:id/?', function() {

I had this same problem, and decided to fix it with a catch-all at the end of my Sammy set-up. My solution removes the trailing slash if there is one, but you could easily do the reverse, adding a slash on instead:
Sammy(function () {
this.get('#', function () {
// ...
});
this.notFound = function (method, path) {
if (path[path.length - 1] === '/') {
// remove trailing slash
window.location = path.substring(0, path.length - 1);
} else {
// redirect to not found
}
}
});

Related

laravel + vue js API store function doesn't work on shared hosting but works well on localhost

All cruds work well both on shared hosting and localhost except create (store).
here is my addtoqueue controller:
public function store(Request $request)
{
$last = antri::whereDate('created_at', Carbon::today())->latest()->first();
if ( $last ) {
$tambah = $last->number + 1;
}
else {
$tambah = 1;
}
$newantri = new antri;
$newantri->user_id = $request->user_id;
$newantri->number = $tambah;
$newantri->called = 0;
$newantri->save();
return $newantri;
}
model:
class antri extends Model
{
use HasFactory;
}
api route :
Route::post('queue', [addtoqueue::class, 'store']);
the trigger:
<v-btn elevation="2" v-on:click="increase" >Ambil Nomor Antrian</v-btn>
the script:
methods: {
increase: function() {
axios.post('/api/queue/', {
user_id: this.user.id
})
. then( response=> {
if( response.status == 200 ) {
this.$emit('itemchanged');
}
})
. catch( error => {
console.log(error);
})
window.location.reload();
}
tested using postman worked well too on both local and share hosting database.
can anyone figure out what seems to be the problem?
I found the problem. I got Status Code 301 moved permanently every time post method is requested. this caused the url structure changed from domain/api/target to domain/public/api/targetand of course it won't hit the store function in the controller.
I edited .htaccess file in public folder from:
RewriteRule ^ %1 [L,R=301]
to
RewriteRule ^ %1 [L,R=307]
which means from permanently moved to temporarily redirected.
and this solved my problem.

How to refresh content when using CrossroadJS and HasherJS with KnockoutJS

I was following Lazy Blogger for getting started with routing in knockoutJS using crossroads and hasher and it worked correctly.
Now I needed to refresh the content using ajax for Home and Settings page every time they are clicked. So I googled but could not find some useful resources. Only these two links
Stack Overflow Here I could not understand where to place the ignoreState property and tried these. But could not make it work.
define(["jquery", "knockout", "crossroads", "hasher"], function ($, ko, crossroads, hasher) {
return new Router({
routes:
[
{ url: '', params: { page: 'product' } },
{ url: 'log', params: { page: 'log' } }
]
});
function Router(config) {
var currentRoute = this.currentRoute = ko.observable({});
ko.utils.arrayForEach(config.routes, function (route) {
crossroads.addRoute(route.url, function (requestParams) {
currentRoute(ko.utils.extend(requestParams, route.params));
});
});
activateCrossroads();
}
function activateCrossroads() {
function parseHash(newHash, oldHash) {
//crossroads.ignoreState = true; First try
crossroads.parse(newHash);
}
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();
$('a').on('click', function (e) {
crossroads.ignoreState = true; //Second try
});
}
});
Crossroads Official Page Here too I could not find where this property need to be set.
If you know then please point me to some url where I can get more details about this.

casperjs evaluate function not working

I am working on casperjs. I write following program to get output:
var casper = require('casper').create();
var cookie;
casper.start('http://wordpress.org/');
casper.then(function() {
this.evaluate(function() {
cookie=document.cookie;
})
})
casper.then(function() {
console.log("Page cookie");
console.log(cookie);
})
casper.run(function() {
this.echo('Done.').exit();
})
Output for above is:
Page cookie
undefined
Done.
why it give me undefined? Help me into this.
Concept behind evaluate is, you will pass your code to browser's console and execute your code there. If you define any variable inside evaluate method that variable will be local to that method. That scope is local. When you are dealing with Casper you should consider the scope of the variable.
So when you try to print out "cookie" in the main function it will say it is undefined.Which is expected.
note that you cant use echo (),console.log () inside evaluate method.
cookie = this.evaluate(function() {
var cookieLocal=document.cookie;
return cookieLocal;
})
Here "cookieLocal" is a local variable.
This will return value to Gloabal variable "cookie". So when you try to print the value in the main function it will work as expected. I hope this will make you to consider scope when declaring variable. You can directly return do the return . No Need of using local variable.
cookie = this.evaluate(function() {
return document.cookie;
})
Another important thing i recommend when you using an evaluate method. try to use Try catch method while developing the code. It wont be needed in production as per you requirement. We cannot print anything inside console. so use try catch for debugging purpose.
casper.then(function() {
cookie = this.evaluate(function() {
try {
return document.cookie;
} catch (e) {
return e;
}
})
this.echo (JSON.stringify ('cookie :'+cookie));
})
Note that this.echo () should be outside evaluate method.
Hope this will be an helpful one.
remove var cookie
cookie = casper.evaluate(function() {
return document.cookie;
})
casper.then(function() {
console.log("Page cookie");
console.log(cookie);
})
The above code works fine for me.

How to set mandatory route parameters

I want to make a route with has a mandatory parameter. If not, it should fall into
$urlRouterProvider.otherwise("/home");
Current route:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
Currently both the routes below are valid:
http://myapp/settings //Should be invalid route
http://myapp/settings/123
Any ideas?
Use a state change start listener to check if params were passed:
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState, fromParams) {
if(toState.name==="settings")
{
event.preventDefault(); //stop state change
if (toParams.id===undefined)
$state.go("home");
else
$state.go(toState, toParams);
}
});
The following solution is valid for ui-router 1.0.0:
.config(($stateProvider, $transitionsProvider) => {
//Define state
$stateProvider.state('verifyEmail', {
parent: 'portal',
url: '/email/verify/:token/:optional',
component: 'verifyEmail',
params: {
token: {
type: 'string',
},
optional: {
value: null,
squash: true,
},
},
});
//Transition hooks
$transitionsProvider.onBefore({
to: 'verifyEmail',
}, transition => {
//Get params
const params = transition.params();
//Must have token param
if (!params.token) {
return transition.router.stateService.target('error', {
type: 'page-not-found',
});
}
});
})
The above will make the :token parameter mandatory and the :optional parameter optional. If you try to browse to the page without the token parameter it will fail the transition and redirect to your error page. If you omit the :optional parameter however, it will use the default value (null).
Remember to use squash: true on the trailing optional parameters, because otherwise you'll also get a 404 if you omit the trailing / in the URL.
Note: the hook is required, because if you browse to email/verify/ with a trailing slash, ui-router will think the token parameter is an empty string. So you need the additional handling in the transition hook to capture those cases.
In my app I had to make required parameters for a lot of routes. So I needed a reusable and DRY way to do it.
I define a constants area in my app to access global code. I use for other things as well.
I run this notFoundHandler at app config time. This is setting up a router state for handling errors. It is setting the otherwise route to this error route. You could define a different route for when a required parameter is missing, but for us this was defined as being the same as a 404 experience.
Now at app run time I also define a stateChangeErrorHandler which will look for a rejected route resolve with the 'required-param' string.
angular.module('app')
.constant('constants', constants)
.config(notFoundHandler)
.run(stateChangeErrorHandler);
// use for a route resolve when a param is required
function requiredParam(paramName) {
return ['$stateParams', '$q', function($stateParams, $q) {
// note this is just a truthy check. if you have a required param that could be 0 or false then additional logic would be necessary here
if (!$stateParams[paramName]) {
// $q.reject will trigger the $stateChangeError
return $q.reject('required-param');
}
}];
}
var constants = {
requiredParam: requiredParam,
// define other constants or globals here that are used by your app
};
// define an error state, and redirect to it if no other route matches
notFoundHandler.$inject = ['$stateProvider', '$urlRouterProvider'];
function notFoundHandler($stateProvider, $urlRouterProvider) {
$stateProvider
//abstract state so that we can hold all our ingredient stuff here
.state('404', {
url: '/page-not-found',
views: {
'': {
templateUrl: "/app/error/error.tpl.html",
}
},
resolve: {
$title: function () { return 'Page Not Found'; }
}
});
// redirect to 404 if no route found
$urlRouterProvider.otherwise('/page-not-found');
}
// if an error happens in changing state go to the 404 page
stateChangeErrorHandler.$inject = ['$rootScope', '$state'];
function stateChangeErrorHandler($rootScope, $state) {
$rootScope.$on('$stateChangeError', function(evt, toState, toParams, fromState, fromParams, error) {
if (error && error === 'required-param') {
// need location: 'replace' here or back button won't work on error page
$state.go('404', null, {
location: 'replace'
});
}
});
}
Now, elsewhere in the app, when I have a route defined, I can make it have a required parameter with this route resolve:
angular.module('app')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', 'constants'];
function routeConfig($stateProvider, constants) {
$stateProvider.state('app.myobject.edit', {
url: "/:id/edit",
views: {
'': {
template: 'sometemplate.html',
controller: 'SomeController',
controllerAs: 'vm',
}
},
resolve: {
$title: function() { return 'Edit MyObject'; },
// this makes the id param required
requiredParam: constants.requiredParam('id')
}
});
}
I'd like to point out that there shouldn't be any problem with accessing the /settings path, since it doesn't correspond to any state, unless you've used inherited states (see below).
The actual issue should happen when accessing the /settings/ path, because it will assign the empty string ("") to the id parameter.
If you didn't use inherited states
Here's a solution in plunker for the following problem:
accessing the /state_name/ path, when there's a state with url /state_name/:id
Solution explanation
It works through the onBefore hook (UI router 1.x or above) of the Transition service, which prevents transitioning to states with missing required parameters.
In order to declare which parameters are required for a state, I use the data hash like this:
.state('settings', {
url: '/settings/:id',
data: {
requiredParams: ['id']
}
});
Then in app.run I add the onBefore hook:
transitionService.onBefore({}, function(transition) {
var toState = transition.to();
var params = transition.params();
var requiredParams = (toState.data||{}).requiredParams || [];
var $state = transition.router.stateService;
var missingParams = requiredParams.filter(function(paramName) {
return !params[paramName];
});
if (missingParams.length) {
/* returning a target state from a hook
issues a transition redirect to that state */
return $state.target("home", {alert: "Missing params: " + missingParams});
}
});
If you used inherited states
You could implement the same logic via inherited states:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings'
})
.state('settings.show", {
url: '/:id'
});
}
then you'd need to add the abstract property to the parent declaration, in order to make /settings path inaccessible.
Solution explanation
Here's what the documentation says about the abstract states:
An abstract state can never be directly activated. Use an abstract state to provide inherited properties (url, resolve, data, etc) to children states.
The solution:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings',
abstract: true
})
.state('settings.show", {
url: '/:id'
});
}
Note: that this only solves the issue with /settings path and you still need to use the onBefore hook solution in order to also limit the access to /settings/.
it is not very well documented, but you can have required and optional parameters, and also parameters with default values.
Here is how you can set required params:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
params: {
id: {}
},
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
I never used params with curly brackets, just with the semicolon, like this url: "^/settings/:id", but from what I read, those are equivalent.
For other types of parameters, please see the other half of my answer here: AngularJS UI Router - change url without reloading state
Please note that when I added that answer, I had to build ui-router from source, but I read that functionality has been added to the official release by now.

Application routes not working

I've just written some routes (app\routes.php) based on Laravel framework as following,
Route::model('cat', 'Cat');
Route::get('/', function()
{
return "All cats";
});
Route::get('/cats', function()
{
$cats = Cat::all();
return View::make('cats.index')->with('cats', $cats);
});
Route::get('/cats/breeds/{name}', function($name)
{
$breed = Breed::whereName($name)->with('cats')->first();
return View::make('cats.index')->with('breed', $breed)->with('cats', $breed->cats);
});
Route::get('/cats/{cat}', function(Cat $cat)
{
return View::make('cats.single')->with('cat', $cat);
});
Route::get('/cats/create', function()
{
return "Cat created.";
});
All routes are okay, except the one /cats/create.
I've tried to create other two dummies routes /dogs and /dogs/xxx, and the second one (/dogs/xxx) is not working.
It sounds weird but it actually happens. Has anyone face this problem before? Or you can provide me some hints to workout.
Maybe you need to put Route::get('/cats/create' before Route::get('/cats/{cat}. Right now system considers your create a {cat}.

Resources