Juan Carlos Picado Herrera avatar Juan Carlos Picado Herrera committed adfa063

javascript error handler with dialogs, mobile template prototype with dojo, removed xhtml unused files

Comments (0)

Files changed (10)

encuestame-core/src/main/java/org/encuestame/core/security/CustomAuthenticationEntryPoint.java

+/*
+ ************************************************************************************
+ * Copyright (C) 2001-2011 encuestame: system online surveys Copyright (C) 2009
+ * encuestame Development Team.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+package org.encuestame.core.security;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.web.DefaultRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+
+/**
+ * Description Class.
+ *
+ * @author Picado, Juan juanATencuestame.org
+ * @since Jan 30, 2011 1:11:52 PM
+ * @version Id:
+ */
+public class CustomAuthenticationEntryPoint extends
+        LoginUrlAuthenticationEntryPoint {
+
+    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+
+    private boolean forceHttps = false;
+
+    private boolean useForward = false;
+
+    private static final Log logger = LogFactory.getLog(CustomAuthenticationEntryPoint.class);
+
+    /**
+     *
+     */
+    @Override
+    public void commence(
+            HttpServletRequest request,
+            HttpServletResponse response,
+            org.springframework.security.core.AuthenticationException authException)
+            throws IOException, ServletException {
+
+        if (authException != null) {
+            // you can check for the spefic exception here and redirect like
+            // this
+            logger.debug("respnse"+response.toString());
+            logger.debug("respnse"+response.getContentType());
+            logger.debug("request"+request.toString());
+            logger.debug("request"+request.getContentType());
+            //response.sendRedirect("403.html");
+        }
+
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+        String redirectUrl = null;
+
+        if (useForward) {
+
+            if (forceHttps && "http".equals(request.getScheme())) {
+                // First redirect the current request to HTTPS.
+                // When that request is received, the forward to the login page
+                // will be used.
+                redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest);
+            }
+
+            if (redirectUrl == null) {
+                String loginForm = determineUrlToUseForThisRequest(httpRequest,
+                        httpResponse, authException);
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Server side forward to: " + loginForm);
+                }
+
+                RequestDispatcher dispatcher = httpRequest
+                        .getRequestDispatcher(loginForm);
+
+                dispatcher.forward(request, response);
+
+                return;
+            }
+        } else {
+            // redirect to login page. Use https if forceHttps true
+
+            redirectUrl = buildRedirectUrlToLoginPage(httpRequest,
+                    httpResponse, authException);
+
+        }
+
+        redirectStrategy.sendRedirect(httpRequest, httpResponse, redirectUrl);
+    }
+
+    /**
+     * @return the redirectStrategy
+     */
+    public RedirectStrategy getRedirectStrategy() {
+        return redirectStrategy;
+    }
+
+    /**
+     * @param redirectStrategy the redirectStrategy to set
+     */
+    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
+        this.redirectStrategy = redirectStrategy;
+    }
+
+    /**
+     * @return the forceHttps
+     */
+    public boolean isForceHttps() {
+        return forceHttps;
+    }
+
+    /**
+     * @param forceHttps the forceHttps to set
+     */
+    public void setForceHttps(boolean forceHttps) {
+        this.forceHttps = forceHttps;
+    }
+
+    /**
+     * @return the useForward
+     */
+    public boolean isUseForward() {
+        return useForward;
+    }
+
+    /**
+     * @param useForward the useForward to set
+     */
+    public void setUseForward(boolean useForward) {
+        this.useForward = useForward;
+    }
+}

encuestame-mvc/src/main/java/org/encuestame/mvc/controller/AbstractJsonController.java

 import org.encuestame.persistence.exception.EnMeDomainNotFoundException;
 import org.encuestame.utils.web.UnitUserBean;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.ui.ModelMap;
 import org.springframework.util.Assert;
 import org.springframework.web.bind.annotation.ExceptionHandler;
      * @return {@link ModelAndView}.
      */
     @ExceptionHandler(AccessDeniedException.class)
-    public ModelAndView handleException (AccessDeniedException ex) {
+    public ModelAndView handleException (final AccessDeniedException ex, HttpServletResponse httpResponse) {
+      log.error("handleException "+ ex.getMessage());
+      httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
       ModelAndView mav = new ModelAndView();
       mav.setViewName("MappingJacksonJsonView");
-      Map<String, Object> response = new HashMap<String, Object>();
+      final Map<String, Object> response = new HashMap<String, Object>();
       response.put("message", ex.getMessage());
-      mav.addObject("error", response);
+      response.put("description", "Application does not have permission for this action");
+      response.put("status", httpResponse.SC_FORBIDDEN);
+      response.put("session", this.checkIsSessionIsExpired());
+      response.put("anonymousUser", this.checkIsSessionIsAnonymousUser());
+      mav.addObject("error",  response);
       return mav;
     }
 
     /**
+     * Check is Session is Expired.
+     * @return
+     */
+    public boolean checkIsSessionIsExpired(){
+        boolean session = false;
+        if(getSecCtx().getAuthentication() != null){
+            session = getSecCtx().getAuthentication().isAuthenticated();
+            log.debug("checkIsSessionIsExpired "+getSecCtx().getAuthentication().getName());
+            log.debug("checkIsSessionIsExpired "+getSecCtx().getAuthentication().getCredentials());
+            log.debug("checkIsSessionIsExpired "+getSecCtx().getAuthentication().getDetails());
+        }
+        log.debug("checkIsSessionIsExpired->"+session);
+        return session;
+    }
+
+    /**
+     * Check is Session is Expired.
+     * @return
+     */
+    public boolean checkIsSessionIsAnonymousUser(){
+        boolean anonymous = false;
+        if("anonymousUser".equals(getSecCtx().getAuthentication().getName())){
+            anonymous = true;
+        }
+        log.debug("checkIsSessionIsExpired->"+anonymous);
+        return anonymous;
+    }
+
+    /**
      * Convert Notification Message.
      * @param notificationEnum
      * @return

encuestame-war/src/main/webapp/WEB-INF/jsp/includes/footer.jsp

-<div id="sessionHandler" dojoType="encuestame.org.core.commons.error.ErrorSessionHandler"></div>
-<div id="errorHandler" dojoType="encuestame.org.core.commons.error.ErrorHandler"></div>
-<div id="errorConexionHandler" dojoType="encuestame.org.core.commons.error.ErrorConexionHandler"></div>
+<div id="sessionHandler"></div>
+<div id="errorHandler"></div>
+<div id="errorConexionHandler"></div>
 </body>
 </html>

encuestame-war/src/main/webapp/WEB-INF/jsp/mobile/home.jsp

+<%@ include file="/WEB-INF/jsp/includes/taglibs.jsp" %>
+<%@ include file="/WEB-INF/jsp/includes/initPage.jsp" %>
+  <style type="text/css">
+      @import "<%=request.getContextPath()%>/resource/js/dojox/mobile/themes/iphone/iphone.css";
+  </style>
+  <div>
+    <script>
+        // Load the basic mobile widgetry and support code.
+        dojo.require("dojox.mobile");
+
+        // Load the lightweight parser.  dojo.parser can also be used, but it requires much more code to be loaded.
+        dojo.require("dojox.mobile.parser");
+
+        // Load the compat layer if the incoming browser isn't webkit based
+        dojo.requireIf(!dojo.isWebKit, "dojox.mobile.compat");
+    </script>
+
+    <div id="main" dojoType="dojox.mobile.View" selected="true">
+        <h1 dojoType="dojox.mobile.Heading">
+            Settings
+        </h1>
+        <ul dojoType="dojox.mobile.EdgeToEdgeList">
+            <li dojoType="dojox.mobile.ListItem" icon="/moin_static185/js/dojo/trunk/dojo/../dojox/mobile/tests/images/i-icon-1.png">
+                Coolness Mode
+                <div class="mblItemSwitch" dojoType="dojox.mobile.Switch">
+                </div>
+            </li>
+            <li dojoType="dojox.mobile.ListItem" icon="/moin_static185/js/dojo/trunk/dojo/../dojox/mobile/tests/images/i-icon-2.png"
+            rightText="mac" moveTo="disco">
+                Disco Room
+            </li>
+            <li dojoType="dojox.mobile.ListItem" icon="/moin_static185/js/dojo/trunk/dojo/../dojox/mobile/tests/images/i-icon-3.png"
+            rightText="AcmePhone" moveTo="disco">
+                Carrier
+            </li>
+        </ul>
+    </div>
+    <div id="disco" dojoType="dojox.mobile.View">
+        <h1 dojoType="dojox.mobile.Heading">
+            Hello
+        </h1>
+        <ul dojoType="dojox.mobile.EdgeToEdgeList">
+            <ul dojoType="dojox.mobile.EdgeToEdgeList">
+                <li dojoType="dojox.mobile.ListItem" moveTo="main">
+                    I'm a square, man.
+                </li>
+                <li dojoType="dojox.mobile.ListItem" moveTo="main">
+                    Leave Disco Room
+                </li>
+            </ul>
+    </div>
+
+  </div>
+<%@ include file="/WEB-INF/jsp/includes/endBody.jsp" %>

encuestame-war/src/main/webapp/WEB-INF/layouts/admonTemplate.xhtml

-<ui:composition template="home.xhtml" xmlns="http://www.w3.org/1999/xhtml"
-  xmlns:ui="http://java.sun.com/jsf/facelets"
-  xmlns:h="http://java.sun.com/jsf/html"
-  xmlns:f="http://java.sun.com/jsf/core"
-  xmlns:a4j="http://richfaces.org/a4j"
-  xmlns:rich="http://richfaces.org/rich"
-  xmlns:encuestame="http://encuesta.me/encuestame">
-  <ui:define name="css">
-      <a4j:loadStyle src="/resource/css/project.css" type="text/css"></a4j:loadStyle>
-  </ui:define>
-  <ui:define name="main">
-    <div id="admonWrapper">
-            <div class="panelAdmon">
-                <div>
-                    <div style="position: relative; overflow: visible; width: 235px;">
-                        <ui:insert name="panel">
-                            <div id="livePanel" class="livePanel">
-
-                            </div>
-                        </ui:insert>
-                    </div>
-                </div>
-            </div>
-            <div class="bodyAdmon">
-                <ui:insert name="bodyOptions">
-                    <div id="projectContent">Content should be here.</div>
-                </ui:insert>
-            </div>
-    </div>
-  </ui:define>
-</ui:composition>

encuestame-war/src/main/webapp/resource/js/encuestame/org/core/commons.js

 dojo.provide("encuestame.org.core.commons");
 
+dojo.require("encuestame.org.core.commons.error.ErrorConexionHandler");
+dojo.require("encuestame.org.core.commons.error.ErrorHandler");
+dojo.require("encuestame.org.core.commons.dashboard.Dashboard");
+dojo.require("dijit.Dialog");
+
 encuestame.service = {};
 encuestame.service.timeout = 20000;
 encuestame.contextDefault = "/encuestame";
+encuestame.signin = encuestame.contextDefault+"/signin.jspx";
+
 /**
  * Json Get Call.
  */
-encuestame.service.xhrGet = function(url, params, load, error){
+encuestame.service.xhrGet = function(url, params, load, error, logginHandler){
+    if (logginHandler == null) {
+        logginHandler = true;
+    }
     var defaultError = function(error, ioargs){
         console.debug("default error ", error);
     };
     if(error == null){
       error = defaultError;
-      console.debug("default error");
+      console.error("default error");
     }
-    if(load == null || url == null || params == null){
+    if (load == null || url == null || params == null) {
         console.error("error params required.")
     } else {
         dojo.xhrGet({
             content: params,
             load: load,
             preventCache: true,
-            error: error,
-            handle: function(error, ioargs) {
+            error: function(error, ioargs) {
+                console.info("error function", ioargs);
+                var message = "";
+                console.info(ioargs.xhr.status, error);
+                //if dialog is missing or is hide.
+                if (encuestame.error.dialog == null || !encuestame.error.dialog.open) {
+                    switch (ioargs.xhr.status) {
+                    case 403:
+                        var jsonError = dojo.fromJson(ioargs.xhr.responseText);
+                        console.info("queryObject", jsonError);
+                        message = "Application does not have permission for this action";
+                        if(!logginHandler){
+                            encuestame.error.denied(message);
+                        } else {
+                            if (!jsonError.session || jsonERror.anonymousUser) {
+                                console.info("session is expired");
+                                encuestame.error.session(encuestame.error.messages.session);
+                            }
+                        }
+                        break;
+                    case 0:
+                        message = "A network error occurred. Check that you are connected to the internet.";
+                        encuestame.error.conexion(message);
+                        break;
+                    default:
+                        message = "An unknown error occurred";
+                        encuestame.error.unknown(message, ioargs.xhr.status);
+                    }
+                }
+              },
+            handle: function(response, ioargs) {
+                encuestame.filter.response(response);
                 var message = "";
+                console.info(ioargs.xhr.status, error);
                 switch (ioargs.xhr.status) {
                 case 200:
                     message = "Good request.";
+                    if (encuestame.error.dialog != null) {
+                        encuestame.error.clear();
+                    }
                     break;
                 case 404:
-                    message = "The requested page was not found";
-                    console.debug(message);
+                    message = "The page you requested was not found.";
+                    encuestame.error.createDialog(message, message);
+                    break;
+                case 400:
+                    message = "Bad Request";
+                    encuestame.error.createDialog(message, message);
+                    break;
                 case 500:
                     break;
-                    message = "The server reported an error.";
-                    console.debug(message);
+                    message = "Service temporarily unavailable.";
+                    encuestame.error.createDialog(message, message);
                     break;
                 case 407:
                     message = "You need to authenticate with a proxy.";
-                    console.debug(message);
+                    encuestame.error.createDialog(message, message);
+                    break;
+                case 0:
+                    message = "A network error occurred. Check that you are connected to the internet.";
+                    encuestame.error.conexion(message);
                     break;
                 default:
-                    console.debug("error", dijit.byId("errorConexionHandler"));
-                    dijit.byId("errorConexionHandler").show();
-                    message = "Unknown error.";
-                    console.debug(message);
+                    message = "An unknown error occurred";
+                    encuestame.error.unknown(message, ioargs.xhr.status);
                 }
               }
           });
     }
 };
 
+encuestame.error = {};
+encuestame.error.debug = true;
+encuestame.error.dialog = null;
+
+/*
+ * clear dialog.
+ */
+encuestame.error.clear = function(){
+    console.info("clean", encuestame.error.dialog);
+    if (encuestame.error.dialog != null){
+        console.info("hidding dialog");
+        encuestame.error.dialog.hide();
+    }
+};
+
+/*
+ * denied dialog.
+ */
+encuestame.error.denied = function(error){
+    var div = dojo.doc.createElement('div');
+    var h3 = dojo.doc.createElement('h3');
+        h3.innerHTML = error;
+        var p = dojo.doc.createElement('p');
+        p.innerHTML = status;
+        div.appendChild(h3);
+        div.appendChild(p);
+    encuestame.error.createDialog("Opps, What's happening?", div);
+};
+
+/*
+ * unknow error dialog.
+ */
+encuestame.error.unknown = function(error, status){
+    var div = dojo.doc.createElement('div');
+    var h3 = dojo.doc.createElement('h3');
+        h3.innerHTML = error;
+        var p = dojo.doc.createElement('p');
+        p.innerHTML = status;
+        div.appendChild(h3);
+        div.appendChild(p);
+    encuestame.error.createDialog("Opps, What's happening?", div, true);
+};
+
+/*
+ * conexion error.
+ */
+encuestame.error.conexion = function(message){
+    var div = dojo.doc.createElement('div');
+    var h3 = dojo.doc.createElement('h3');
+    h3.innerHTML = message;
+    div.appendChild(h3);
+    encuestame.error.createDialog(message, div, true);
+};
+
+/*
+ * expired sesion dialog.
+ */
+encuestame.error.session = function(message){
+    var div = dojo.doc.createElement('div');
+    var h3 = dojo.doc.createElement('h3');
+    h3.innerHTML = message;
+    var widgetButton = new dijit.form.Button({
+        label: "Sign In",
+        onClick: dojo.hitch(this, function(event) {
+            dojo.stopEvent(event);
+            document.location.href = encuestame.signin;
+        })
+    });
+    div.appendChild(h3);
+    div.appendChild(widgetButton.domNode);
+    encuestame.error.createDialog("Not logged in", div, true);
+};
+
+/*
+ * Create New Error Dialog.
+ */
+encuestame.error.createDialog = function(title, content, addcloseButton){
+    var node = dojo.byId("errorHandler");
+    console.debug("node", node);
+    if(node != null){
+        if (encuestame.error.dialog != null){
+             encuestame.error.dialog.open ? encuestame.error.dialog.hide() : "";
+        } else {
+        }
+        dojo.empty(node);
+        //close button validation
+        addcloseButton = addcloseButton == null ? false : addcloseButton;
+        encuestame.error.dialog = new dijit.Dialog({
+              title: title,
+              content: content,
+              style: "width: 480px; height: 100px;"
+          });
+        if(addcloseButton){
+            var widgetButton = new dijit.form.Button({
+                label: "Close",
+                onClick: dojo.hitch(this, function(event) {
+                    dojo.stopEvent(event);
+                    encuestame.error.dialog.hide();
+                })
+            });
+            var content = encuestame.error.dialog.content;
+            content.appendChild(widgetButton.domNode);
+        }
+        console.debug("dialog", encuestame.error.dialog);
+        node.appendChild(encuestame.error.dialog.domNode);
+        encuestame.error.dialog.show();
+    } else {
+        console.error("no error handler dialog found");
+    }
+};
+
+encuestame.error.messages = {};
+encuestame.error.messages.denied = "Application does not have permission for this action";
+encuestame.error.messages.session = "Your session has expired, please sign in to contiue.";
+
 encuestame.filter = {};
 
-encuestame.filter.response = function(load){
+/**
+ * {"error":{"message":"Access is denied"}}
+ */
+encuestame.filter.response = function(response){
+    console.info("filter", response);
+    //no permissions or session
+    if(response == undefined){
+        //no response
+
+    }else if (response.error.message != undefined && response.success == undefined ) {
+        encuestame.session.getSession();
+    } else if(response.success == undefined ) {
+        console.info("sucess response no existe");
+    }
+};
 
-}
+encuestame.session = {};
+encuestame.session.getSession = function(){
+    //JSESSIONID=dh3u2xvj7fwd1llbddl33dhcq; path=/encuestame; domain=demo2.encuestame.org
+    var sessionCookie = dojo.cookie("JSESSIONID");
+    if(sessionCookie == undefined){
+        encuestame.error.session(encuestame.error.messages.denied);
+    } else {
+        console.info("session is valid");
+    }
+};
 
 /**
  * Json Get Call.
                 load: load,
                 preventCache: true,
                 error: error
-            }
+            };
         var deferred = dojo.xhrPost(xhrArgs);
     }
 };
 encuestame.service.list = {};
 encuestame.service.list.userList = encuestame.contextWidget()+"/api/admon/users.json";
 encuestame.service.list.getNotifications = encuestame.contextWidget()+"/api/notifications.json";
-encuestame.service.list.getStatusNotifications = encuestame.contextWidget()+"/api/status-notifications.json"
-encuestame.service.list.changeStatusNotification = encuestame.contextWidget()+"/api/change-status-notifications.json"
-encuestame.service.list.removeNotification = encuestame.contextWidget()+"/api/remove-notification.json"
+encuestame.service.list.getStatusNotifications = encuestame.contextWidget()+"/api/status-notifications.json";
+encuestame.service.list.changeStatusNotification = encuestame.contextWidget()+"/api/change-status-notifications.json";
+encuestame.service.list.removeNotification = encuestame.contextWidget()+"/api/remove-notification.json";
 encuestame.service.list.userInfo = encuestame.contextWidget()+"/api/admon/user-info.json";
 encuestame.service.list.createUser = encuestame.contextWidget()+"/api/admon/create-user.json";
 encuestame.service.list.upgradeProfile = encuestame.contextWidget()+"/api/user/profile/upgrade.json";

encuestame-war/src/main/webapp/resource/js/encuestame/org/core/commons/error/AbstractErrorHandler.js

 
         type : "",
 
-        postMixInProperties: function(){
+        dialog : null,
 
-        },
 
         postCreate: function() {
+            console.debug("postCreate Abstract Error ");
+            /*this.dialog = new dojox.widget.Dialog();
+            console.debug("dialog 1", this.dialog);
+            this.dialog.set("dimensions", [400, 200]); // [width, height]
+            this.dialog.layout(); //starts the resize
+            console.debug("dialog 2", this.dialog);
+            this._errorBox.appenChild(this.dialog.domNode);*/
+        },
 
+        show : function(){
+             console.debug("show");
+            if(this.dialog != null){
+                this.dialog.show();
+            } else {
+                console.info("no error duialog");
+            }
         }
     }
 );

encuestame-war/src/main/webapp/resource/js/encuestame/org/core/commons/error/ErrorHandler.js

 
 dojo.require("dojox.widget.Dialog");
 dojo.require("encuestame.org.core.commons.error.AbstractErrorHandler");
+dojo.require("dijit._Templated");
+dojo.require("dijit._Widget");
 
 dojo.declare(
     "encuestame.org.core.commons.error.ErrorHandler",
-    [encuestame.org.core.commons.error.AbstractErrorHandler],{
+    [dijit._Widget, dijit._Templated],{
 
         widgetsInTemplate: true,
 
-        type : "Error",
-
-        postMixInProperties: function(){
+        templatePath: dojo.moduleUrl("encuestame.org.core.commons.error", "template/error.inc"),
 
-        },
+        type : "Error",
 
         postCreate: function() {
-
+            console.debug("postCreate");
+            //this.inherited(arguments);
         }
     }
 );

encuestame-war/src/main/webapp/resource/js/encuestame/org/core/commons/error/template/error.inc

 <div>
-    <div dojoType="dojox.widget.Dialog" id="errorDialog_${id}" dimensions="[1030,310]">
-         <div dojoType="dijit.layout.ContentPane" title="Error ${type}" selected="true">
-            ERROR
-         </div>
+    <div dojoAttachPoint="_errorBox">
     </div>
 </div>

encuestame-war/src/main/webapp/resource/js/encuestame/org/core/shared/utils/Table.js

                 this.cleanTable();
                 this.iterateResponseItems(data);
             });
-            var error = function(error) {
-                console.debug("error", error);
-                this.errorResponse(error);
-            };
+            var error = dojo.hitch(this, function(error) {
+                console.debug("error table", error);
+            });
             encuestame.service.xhrGet(this.jsonServiceUrl, {limit: this.limit, start: this.start}, load, error);
         },
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.