I need to add into my MVC js project a middleware filter which will transfer control to the controller which will do some actions if the filter condition is false and render a special page if a condition is true (but not giving the controll to the controller). It must be a middleware which executes before the controller logic and it SHOULD NOT BE a function in the controller.
Here i have a route which now just executes a download method in the controller.
if (config.common.zoneName === 'main') {
router.get('/book/:itemid', new
DownloadController(ResourceRepo).download);
}
I need to add there some logic, for example
if (itemid > 10){
//render some special page
}
//execute download method on the same controller
}
In the real task condition is preety complex (such as getting the request IP and checking a field in a datadase with the same IP). So the condition is not an inline function but some complex method.
How can i do that using expressjs middleware?
Many thanks :3
I suppose you want to do something like this:
if (config.common.zoneName === 'main') {
router.get('/book/:itemid',
(req, res, next) => {
if (req.params.itemid > 10){
//render some special page
}
//execute download method on the same controller
},
new DownloadController(ResourceRepo).download);
}
Related
I have a very simple 'named' Nuxt middleware set up (taken from the docs) which checks in the store to see whether a user is authenticated before they can navigate to certain routes. If the user is not authenticated, they are directed to a straightforward form in which they have to provide an email address to gain access (at http://example.com/access). All of that works fine, after they fulfil the middleware's store.state.authenticated check they can navigate around no problem.
export default function ({ store, redirect }) {
if (!store.state.authenticated) {
return redirect('/access')
}
}
My question is, once the user has entered their email address, I have no way of knowing what route they were initially trying to access. I've looked at other questions here about passing data between routes but because of the way the middleware works these solutions don't seem to be feasible.
I really would rather not set the slug in the vuex state as this will lead to a whole other set of complications – I also don't mind setting the intended slug as a query or a param on the /access url. I have read through the docs for the Nuxt redirect function and realise you can pass a route.query as an argument. So it seems that you could potentially do something like this:
return redirect('/access', intendedSlug)
...or, if using params(?):
return redirect(`/access/${intendedSlug}`)
But I don't know how to pass that intendedSlug value to the middleware in the first place as it's not exposed on the context passed to the function or anywhere else. It seems like this would be a common problem, but I can't find any simple solutions – any help would be really appreciated!
To help #Bodger I'm posting how I resolved this, it may not be perfect and it's working on a slightly older version of Nuxt (I know 😵!) but this is how I resolved the issue.
.../middleware/authenticated.js
export default function (context) {
const path =
context.route.path.length && context.route.path[0] === '/'
? context.route.path.slice(1)
: context.route.path
const pathArray = path.split('/')
if (process.server && !context.store.state.authenticated) {
return context.redirect('/access', pathArray)
} else if (!context.store.state.authenticated) {
return context.redirect('/access', pathArray)
}
}
The pathArray is then accessible in my /access page.
.../pages/access.js
data() {
return {
attemptedRoutePathArray: Object.values(this.$route.query)
...
}
},
...
computed: {
attemptedRouteURL() {
return new URL(
this.attemptedRoutePathArray.join('/'),
process.env.baseUrl
)
},
...
}
I would like to ask if can I implement this on vuejs, so basically the code will load a page/template base on the param url. I've been searching for a while and can't get the results I need or maybe I'm just searching a wrong keyword.
My url is like this, so I just can't manually declare the url in my route because it is dynamic, fetch from the database.
path: '/user/page_type
Thank you very much!
export default {
mounted () {
if(this.$routes.params.page_type == "home"){
// Load Homepage Here
// ../../../page/HomePage.vue
}
else if(this.$routes.params.page_type == "speaker"){
// Load Speakerpage Here
// ../../../page/HomePage.vue
}
else if(this.$routes.params.page_type == 'html'){
// Load HTML Page Here
// ../../../page/HtmlPage.vue
}
}
}
This is available out of the box within official addon vue-router.
Docs for your case: link
I have 3 routes: items/one, items/two, and items/three and they're all pointing to 'items' vm/view.
in the items.js activate function, I'm checking the url, and based on that, I'm changing a filter:
function activate(r) {
switch (r.routeInfo.url) {
case 'items/one': vm.filterType(1); break;
case 'items/two': vm.filterType(2); break;
case 'items/three': vm.filterType(3); break;
}
return init(); //returns a promise
}
The items view has a menu with buttons for one, two, and three.
Each button is linked to an action like this:
function clickOne() {
router.navigateTo('#/items/one');
}
function clickTwo() {
router.navigateTo('#/items/two');
}
function clickThree() {
router.navigateTo('#/items/three');
}
this all works and I get the right filter on the view. However, I've noticed that if I'm on 'one', and then go to 'two', the ko-bound variables update in 'real-time', that is, as they're changing, and before the activate promise resolves, which causes the transition to happen twice (as the data is being grabbed, and after the activate function returns).
This only happens in this scenario, where the view and viewmodel are the same as the previous one. I'm aware that this is a special case, and the router is probably handling the loading of the new route with areSameItem = true. I could split the VMs/Views into three and try to inherit from a base model, but I was hoping for a simpler solution.
I was able to solve the issue by simply removing the ko bindings before navigation using ko.cleanNode() on the items containing div.
Assuming that in your parent view you've a reference to router.activeItem with a transition e.g.
<!--ko compose: {model: router.activeItem,
afterCompose: router.afterCompose,
transition: 'entrance'} -->
<!--/ko-->
then the entrance transition happens on every route you've setup to filter the current view.
But this transition should probably only happen on first time visit and from that point on only the view should be updated with the filtered data. One way to accomplish that would be to setup an observable filterType and use filterType.subscribe to call router.navigateTowith the skip parameter.
Something along the line:
var filterType = ko.observable();
filterType.subscribe(function (val) {
// Create an entry in the history but don't activate the new route to prevent transition
// router plugin expects this without leading '/' dash.
router.navigateTo(location.pathname.substring(1) + '#items/' + filterType(), 'skip');
activate();
});
Please note that the router plugin expects skipRouteUrl without leading / slash to compare the context.path. https://github.com/BlueSpire/Durandal/blob/master/App/durandal/plugins/router.js#L402
Your experience might be different.
Last in order to support deep linking in activate:
function activate(routerdata) {
// deep linking
if (routerdata && routerdata.filterType && (routerdata.filterType !== filterType() ) ) {
filterType(routerdata.filterType);
}
return promise;
};
I have an mvc grid.
Next to each record is a delete button.
This delete button corresponds to an action DeleteRecord. The action deletes this record and performs a RedirectToAction("MyGrid");.
This works pretty well but what is annoying is the fact that my redirectaction causes the entire page to reload. This is something I am trying to stay away from, but can't seem to figure out a way around this.
I start out like this.
/MyApp/MyGrid => click delete
/MyApp/DeleteRecord => redirect
/MyApp/MyGrid => full reload of page
Maybe this isn't possible but I tried doing this by using overriden actions and actions with different action names, but this didn't solve my full reload. I am new to MVC so maybe this isn't even possible. I was thinking maybe if I just did an ajax.post on the clientside that I could get away from this but the more I think about it the more likely that it just will end up in the same action performing the same redirect.
Any ideas on how to get around this situation?
I actually ended up just tying into the callback function of the grid and the inline delete of the grid function and it worked. Although, extremely hackish, it stops the full post back by hoping into the inline grid delete code. Although, you can't count on the callback javascript routines to run in order as that is just how javascript works right. ; o
s.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "DeleteRecordGrid" };
s.ClientSideEvents.EndCallback = "function(s,e) { OnEndCallback(s, e, 'delete') }";
<script type="text/javascript">
function deleteWithoutPostback(eVisibleIndex) {
var okToDelete = confirm("Do you really want to delete this record?");
if (okToDelete == true) {
myGrid.DeleteRow(eVisibleIndex);
myGrid.ClearFilter();
} else {
return;
}
}
function OnEndCallback(s, e, callType) {
if (typeof callType == 'undefined') {
if (callType == 'delete') {
myGrid.Refresh();
}
}
}
</script>
Im creating a site who works with ajaxRequest, when I click a link, it will load using ajaxRequest. When I load for example user/login UserController actionLogin, I renderPartial the view with processOUtput to true so the js needed inside that view will be generated, however if I have clientScriptRegister inside that view with events, how can I avoid to generate the scriptRegistered twice or multiple depending on the ajaxRequest? I have tried Yii::app()->clientScript->isSCriptRegistered('scriptId') to check if the script is already registered but it seems that if you used ajaxRequest, the result is always false because it will only be true after the render is finished.
Controller code
if (Yii::app()->request->isAjaxRequest)
{
$this->renderPartial('view',array('model'=>$model),false,true);
}
View Code
if (!Yii::app()->clientScript->isScriptregistered("view-script"))
Yii::app()->clientScript->registerScript("view-script","
$('.link').live('click',function(){
alert('test');
})
");
If I request for the controller for the first time, it works perfectly (alert 1 time) but if I request again for that same controller without refreshing my page and just using ajaxRequest, the alert will output twice if you click it (because it keeps on generating eventhough you already registered it once)
This is the same if you have CActiveForm inside the view with jquery functionality.. the corescript yiiactiveform will be called everytime you renderPartial.
To avoid including core scripts twice
If your scripts have already been included through an earlier request, use this to avoid including them again:
// For jQuery core, Yii switches between the human-readable and minified
// versions based on DEBUG status; so make sure to catch both of them
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery.min.js'] = false;
If you have views that are being rendered both independently and as HTML fragments to be included with AJAX, you can wrap this inside if (Yii::app()->request->isAjaxRequest) to cover all bases.
To avoid including jQuery scripts twice (JS solution)
There's also the possibility of preventing scripts from being included twice on the client side. This is not directly supported and slightly more cumbersome, but in practice it works fine and it does not require you to know on the server side what's going on at the client side (i.e. which scripts have been already included).
The idea is to get the HTML from the server and simply strip out the <script> tags with regular expression replace. The important point is you can detect if jQuery core scripts and plugins have already been loaded (because they create $ or properties on it) and do this conditionally:
function stripExistingScripts(html) {
var map = {
"jquery.js": "$",
"jquery.min.js": "$",
"jquery-ui.min.js": "$.ui",
"jquery.yiiactiveform.js": "$.fn.yiiactiveform",
"jquery.yiigridview.js": "$.fn.yiiGridView",
"jquery.ba-bbq.js": "$.bbq"
};
for (var scriptName in map) {
var target = map[scriptName];
if (isDefined(target)) {
var regexp = new RegExp('<script.*src=".*' +
scriptName.replace('.', '\\.') +
'".*</script>', 'i');
html = html.replace(regexp, '');
}
}
return html;
}
There's a map of filenames and objects that will have been defined if the corresponding script has already been included; pass your incoming HTML through this function and it will check for and remove <script> tags that correspond to previously loaded scripts.
The helper function isDefined is this:
function isDefined(path) {
var target = window;
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return false;
}
target = target[branch];
}
return true;
}
To avoid attaching event handlers twice
You can simply use a Javascript object to remember if you have already attached the handler; if yes, do not attach it again. For example (view code):
Yii::app()->clientScript->registerScript("view-script","
window.myCustomState = window.myCustomState || {}; // initialize if not exists
if (!window.myCustomState.liveClickHandlerAttached) {
window.myCustomState.liveClickHandlerAttached = true;
$('.link').live('click',function(){
alert('test');
})
}
");
The cleanest way is to override beforeAction(), to avoid any duplicated core script:
class Controller extends CController {
protected function beforeAction($action) {
if( Yii::app()->request->isAjaxRequest ) {
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery-2.0.0.js'] = false;
Yii::app()->clientScript->scriptMap['anything.js'] = false;
}
return parent::beforeAction($action);
}
}
Note that you have to put the exact js file name, without the path.
To avoid including script files twice, try this extension: http://www.yiiframework.com/extension/nlsclientscript/
To avoid attaching event handlers twice, see Jons answer: https://stackoverflow.com/a/10188538/729324