diff --git a/common/src/main/java/com/jeesite/common/web/http/ServletUtils.java b/common/src/main/java/com/jeesite/common/web/http/ServletUtils.java index 1d7fd615..0750d40e 100644 --- a/common/src/main/java/com/jeesite/common/web/http/ServletUtils.java +++ b/common/src/main/java/com/jeesite/common/web/http/ServletUtils.java @@ -8,7 +8,6 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.TreeMap; @@ -25,7 +24,6 @@ import com.fasterxml.jackson.databind.util.JSONPObject; import com.jeesite.common.collect.MapUtils; import com.jeesite.common.io.PropertiesUtils; import com.jeesite.common.lang.ExceptionUtils; -import com.jeesite.common.lang.ObjectUtils; import com.jeesite.common.lang.StringUtils; import com.jeesite.common.mapper.JsonMapper; import com.jeesite.common.mapper.XmlMapper; @@ -40,13 +38,26 @@ public class ServletUtils { public static final String EXT_PARAMS_PREFIX = "param_"; // 扩展参数前缀 // 定义静态文件后缀;静态文件排除URI地址 - private static String[] staticFiles; - private static String[] staticFileExcludeUri; - private static Boolean favorPathExtension; - private static Boolean favorParameter; - private static Boolean favorHeader; - private static Boolean jsonp; + private static final PropertiesUtils PROPS = PropertiesUtils.getInstance(); + private static final String[] STATIC_FILE = StringUtils.split(PROPS.getProperty("web.staticFile"), ","); + private static final String[] STATIC_FILE_EXCLUDE_URI = StringUtils.split(PROPS.getProperty("web.staticFileExcludeUri"), ","); + // AJAX 请求参数和请求头名 + public static final String AJAX_PARAM_NAME = PROPS.getProperty("web.ajaxParamName", "__ajax"); + public static final String AJAX_HEADER_NAME = PROPS.getProperty("web.ajaxHeaderName", "x-ajax"); + + // MVC 偏好设置,根据后缀、参数、Header 返回特定格式数据 + public static final Boolean FAVOR_PATH_EXTENSION = PROPS.getPropertyToBoolean("web.view.favorPathExtension", "false"); + public static final Boolean FAVOR_PARAMETER = PROPS.getPropertyToBoolean("web.view.favorParameter", "true"); + public static final Boolean FAVOR_HEADER = PROPS.getPropertyToBoolean("web.view.favorHeader", "true"); + + // JSONP 支持(为兼用旧版保留,建议使用 CORS) + public static final Boolean JSONP_ENABLED = PROPS.getPropertyToBoolean("web.jsonp.enabled", "false"); + public static final String JSONP_CALLBACK = PROPS.getProperty("web.jsonp.callback", "__callback"); + + // 是否打印错误信息参数到视图页面(生产环境关闭) + private static final Boolean PRINT_ERROR_INFO = PROPS.getPropertyToBoolean("error.page.printErrorInfo", "true"); + /** * 获取当前请求对象 * web.xml: @@ -105,51 +116,28 @@ public class ServletUtils { * @throws Exception */ public static boolean isStaticFile(String uri){ - if (staticFiles == null){ - PropertiesUtils pl = PropertiesUtils.getInstance(); - try{ - staticFiles = StringUtils.split(pl.getProperty("web.staticFile"), ","); - staticFileExcludeUri = StringUtils.split(pl.getProperty("web.staticFileExcludeUri"), ","); - }catch(NoSuchElementException nsee){ - ; // 什么也不做 - } - if (staticFiles == null){ - try { - throw new Exception("检测到“jeesite.yml”中没有配置“web.staticFile”属性。" - + "配置示例:\n#静态文件后缀\nweb.staticFile=.css,.js,.png,.jpg,.gif," - + ".jpeg,.bmp,.ico,.swf,.psd,.htc,.crx,.xpi,.exe,.ipa,.apk"); - } catch (Exception e) { - e.printStackTrace(); - } + if (STATIC_FILE == null){ + try { + throw new Exception("检测到“jeesite.yml”中没有配置“web.staticFile”属性。" + + "配置示例:\n#静态文件后缀\nweb.staticFile=.css,.js,.png,.jpg,.gif," + + ".jpeg,.bmp,.ico,.swf,.psd,.htc,.crx,.xpi,.exe,.ipa,.apk"); + } catch (Exception e) { + e.printStackTrace(); } } - if (staticFileExcludeUri != null){ - for (String s : staticFileExcludeUri){ + if (STATIC_FILE_EXCLUDE_URI != null){ + for (String s : STATIC_FILE_EXCLUDE_URI){ if (StringUtils.contains(uri, s)){ return false; } } } - if (StringUtils.endsWithAny(uri, staticFiles)){ + if (StringUtils.endsWithAny(uri, STATIC_FILE)){ return true; } return false; } - /** - * 初始化一些个性化配置 - * @author ThinkGem - */ - private static void initWebViewConfig() { - if (favorPathExtension == null || favorParameter == null || favorHeader == null || jsonp == null) { - PropertiesUtils props = PropertiesUtils.getInstance(); - favorPathExtension = ObjectUtils.toBoolean(props.getProperty("web.view.favorPathExtension", "false")); - favorParameter = ObjectUtils.toBoolean(props.getProperty("web.view.favorParameter", "true")); - favorHeader = ObjectUtils.toBoolean(props.getProperty("web.view.favorHeader", "true")); - jsonp = ObjectUtils.toBoolean(props.getProperty("web.jsonp.enabled", "false")); - } - } - /** * 是否是Ajax异步请求 * @param request @@ -166,9 +154,7 @@ public class ServletUtils { return true; } - initWebViewConfig(); - - if (favorPathExtension) { + if (FAVOR_PATH_EXTENSION) { String uri = request.getRequestURI(); if (StringUtils.endsWithIgnoreCase(uri, ".json") || StringUtils.endsWithIgnoreCase(uri, ".xml")){ @@ -176,15 +162,15 @@ public class ServletUtils { } } - if (favorParameter) { - String ajaxParameter = request.getParameter("__ajax"); + if (FAVOR_PARAMETER) { + String ajaxParameter = request.getParameter(AJAX_PARAM_NAME); if (StringUtils.inStringIgnoreCase(ajaxParameter, "json", "xml")){ return true; } } - if (favorHeader) { - String ajaxHeader = request.getHeader("__ajax"); + if (FAVOR_HEADER) { + String ajaxHeader = request.getHeader(AJAX_HEADER_NAME); if (StringUtils.inStringIgnoreCase(ajaxHeader, "json", "xml")){ return true; } @@ -234,8 +220,7 @@ public class ServletUtils { String exMsg = ExceptionUtils.getExceptionMessage(ex); if (StringUtils.isNotBlank(exMsg)){ resultMap.put("message", message + "," + exMsg); - }else if (ObjectUtils.toBoolean(PropertiesUtils.getInstance() - .getProperty("error.page.printErrorInfo", "true"))){ + }else if (PRINT_ERROR_INFO){ resultMap.put("message", message + "," + ex.getMessage()); } }else if (data instanceof Map){ @@ -248,9 +233,9 @@ public class ServletUtils { HttpServletResponse response = getResponse(); HttpServletRequest request = getRequest(); if (request != null){ - String uri = request.getRequestURI(); initWebViewConfig(); - if ((favorPathExtension && StringUtils.endsWithIgnoreCase(uri, ".xml")) - || (favorParameter && StringUtils.equalsIgnoreCase(request.getParameter("__ajax"), "xml"))){ + String uri = request.getRequestURI(); + if ((FAVOR_PATH_EXTENSION && StringUtils.endsWithIgnoreCase(uri, ".xml")) + || (FAVOR_PARAMETER && StringUtils.equalsIgnoreCase(request.getParameter(AJAX_PARAM_NAME), "xml"))){ if (response != null){ response.setContentType(MediaType.APPLICATION_XML_VALUE); } @@ -260,8 +245,8 @@ public class ServletUtils { return XmlMapper.toXml(resultMap); } } - if (jsonp) { - String functionName = request.getParameter("__callback"); + if (JSONP_ENABLED) { + String functionName = request.getParameter(JSONP_CALLBACK); if (StringUtils.isNotBlank(functionName)){ object = new JSONPObject(functionName, resultMap); } @@ -337,13 +322,13 @@ public class ServletUtils { */ public static String renderObject(HttpServletResponse response, Object object, Class jsonView) { HttpServletRequest request = getRequest(); - String uri = request.getRequestURI(); initWebViewConfig(); - if ((favorPathExtension && StringUtils.endsWithIgnoreCase(uri, ".xml")) - || (favorParameter && StringUtils.equalsIgnoreCase(request.getParameter("__ajax"), "xml"))){ + String uri = request.getRequestURI(); + if ((FAVOR_PATH_EXTENSION && StringUtils.endsWithIgnoreCase(uri, ".xml")) + || (FAVOR_PARAMETER && StringUtils.equalsIgnoreCase(request.getParameter(AJAX_PARAM_NAME), "xml"))){ return renderString(response, XmlMapper.toXml(object)); } - if (jsonp) { - String functionName = request.getParameter("__callback"); + if (JSONP_ENABLED) { + String functionName = request.getParameter(JSONP_CALLBACK); if (StringUtils.isNotBlank(functionName)){ object = new JSONPObject(functionName, object); } diff --git a/modules/core/src/main/resources/config/jeesite-core.yml b/modules/core/src/main/resources/config/jeesite-core.yml index 38abb130..48b2fad9 100644 --- a/modules/core/src/main/resources/config/jeesite-core.yml +++ b/modules/core/src/main/resources/config/jeesite-core.yml @@ -309,12 +309,12 @@ gen: # 系统监控 state: enabled: true - + #======================================# #========= Framework settings =========# #======================================# -# Shiro 相关配置 +# Shiro 相关 shiro: #索引页路径 @@ -354,9 +354,9 @@ shiro: # 指定获取客户端IP的Header名称,防止IP伪造。指定为空,则使用原生方法获取IP。 remoteAddrHeaderName: X-Forwarded-For - - # 允许的请求方法设定,解决安全审计问题 - allowRequestMethods: GET,POST,OPTIONS,PUT,DELETE + + # 允许的请求方法设定,解决安全审计问题(BPM设计器用到了PUT或DELETE方法) + allowRequestMethods: GET, POST, OPTIONS, PUT, DELETE # 是否允许账号多地登录,如果设置为false,同一个设备类型的其它地点登录的相同账号被踢下线 isAllowMultiAddrLogin: true @@ -370,21 +370,22 @@ shiro: # 是否允许嵌入到外部网站iframe中(true:不限制,false:不允许) isAllowExternalSiteIframe: true - # 是否允许跨域访问 CORS,如果允许,设置允许的域名。当设置'*'号全部域名时,accessControlAllowCredentials应该设置为false。 - # v4.2.3 开始支持多个域名和模糊匹配,例如:http://*.jeesite.com,http://*.jeesite.net -# accessControlAllowOrigin: http://demo.jeesite.com -# accessControlAllowOrigin: '*' - - # 允许跨域访问时 CORS,可以使用的方法和标头 -# accessControlAllowMethods: GET, POST, OPTIONS -# accessControlAllowHeaders: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With - - # 是否允许接收跨域的Cookie凭证数据 CORS,当设置为true时,accessControlAllowOrigin不能设置为'*'。 -# accessControlAllowCredentials: false + # 设定允许获取的资源列表(v4.2.3) + #contentSecurityPolicy: "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src 'self' 'unsafe-inline' 'unsafe-eval' data:" + + # 是否允许跨域访问 CORS,如果允许,设置允许的域名。v4.2.3 开始支持多个域名和模糊匹配,例如:http://*.jeesite.com,http://*.jeesite.net + accessControlAllowOrigin: '*' + + # 允许跨域访问时 CORS,可以获取和返回的方法和请求头 + #accessControlAllowMethods: GET, POST, OPTIONS + #accessControlAllowHeaders: content-type, x-requested-with, x-ajax, x-token, x-remember + #accessControlExposeHeaders: x-remember + + # 是否允许接收跨域的Cookie凭证数据 CORS + #accessControlAllowCredentials: false # 允许的网站来源地址,不设置为全部地址(避免一些跨站点请求伪造 CSRF、防盗链) -# allowReferers: http://127.0.0.1,http://localhost -# allowReferers: ~ + #allowReferers: http://127.0.0.1,http://localhost # 是否在登录后生成新的Session(默认false) isGenerateNewSessionAfterLogin: false @@ -406,6 +407,8 @@ shiro: # ${adminPath}/sys/corpAdmin/treeData = anon # ${adminPath}/${spring.application.name}/swagger/** = anon # ${adminPath}/** = user + + # URI 权限过滤器定义(自定义添加参数时,请不要移除 ${adminPath}/** = user,否则会导致权限异常) filterChainDefinitions: | ${adminPath}/** = user @@ -460,8 +463,16 @@ session: #sessionIdCookieHttpOnly: true #sessionIdCookieSameSite: LAX - # 设置接收SessionId请求参数的名称 + # 设置接收 SessionId 请求参数和请求头的名称 sessionIdParamName: __sid + sessionIdHeaderName: x-token + + # 当直接通过 __sid 参数浏览器访问页面时,可将直接将 __sid 写入 Cookie 应用于后面的访问 + # 访问地址举例:http://host/js/a/index?__sid=123456&__cookie=true + writeCookieParamName: __cookie + + # 记住我的请求参数和请求头的名称(v4.2.3) + rememberMeHeaderName: x-remember # 系统缓存配置 j2cache: @@ -513,6 +524,10 @@ mybatis: # Web 相关 web: + # AJAX 接受参数名和请求头名(v4.3.0) + ajaxParamName: __ajax + ajaxHeaderName: x-ajax + # MVC 视图相关 view: @@ -528,7 +543,7 @@ web: favorPathExtension: false # 使用 __ajax=json、__ajax=xml 后缀匹配返回视图数据 favorParameter: true - # 使用 __ajax=json、__ajax=xml 请求头匹配返回视图数据 + # 使用 x-ajax=json、x-ajax=xml 请求头匹配返回视图数据 favorHeader: true # MVC 拦截器 @@ -578,6 +593,7 @@ web: # 默认不启用(为兼用旧版保留,建议使用 CORS) jsonp: enabled: false + callback: __callback # 核心模块的Web功能(仅作为微服务时设为false) core: @@ -600,7 +616,7 @@ file: enabled: true # 文件上传根路径,设置路径中不允许包含“userfiles”,在指定目录中系统会自动创建userfiles目录,如果不设置默认为contextPath路径 -# baseDir: D:/jeesite + #baseDir: D:/jeesite # 上传文件的相对路径(支持:yyyy, MM, dd, HH, mm, ss, E) uploadPath: '{yyyy}{MM}/' @@ -638,7 +654,7 @@ video: # 视频格式转换 ffmpeg.exe 所放的路径 ffmpegFile: d:/tools/video/ffmpeg-4.9/bin/ffmpeg.exe -# ffmpegFile: d:/tools/video/libav-10.6-win64/bin/avconv.exe + #ffmpegFile: d:/tools/video/libav-10.6-win64/bin/avconv.exe # 视频格式转换 mencoder.exe 所放的路径 mencoderFile: d:/tools/video/mencoder-4.9/mencoder.exe diff --git a/web/src/main/resources/config/application.yml b/web/src/main/resources/config/application.yml index 66630227..75a4ce17 100644 --- a/web/src/main/resources/config/application.yml +++ b/web/src/main/resources/config/application.yml @@ -427,7 +427,7 @@ logging: #========= Framework settings =========# #======================================# -# Shiro 相关配置 +# Shiro 相关 shiro: # #索引页路径 @@ -468,8 +468,8 @@ shiro: # # 指定获取客户端IP的Header名称,防止IP伪造。指定为空,则使用原生方法获取IP。 # remoteAddrHeaderName: X-Forwarded-For # -# # 允许的请求方法设定,解决安全审计问题 -# allowRequestMethods: GET,POST,OPTIONS,PUT,DELETE +# # 允许的请求方法设定,解决安全审计问题(BPM设计器用到了PUT或DELETE方法) +# allowRequestMethods: GET, POST, OPTIONS, PUT, DELETE # # # 是否允许账号多地登录,如果设置为false,同一个设备类型的其它地点登录的相同账号被踢下线 # isAllowMultiAddrLogin: true @@ -483,21 +483,22 @@ shiro: # # 是否允许嵌入到外部网站iframe中(true:不限制,false:不允许) # isAllowExternalSiteIframe: true # -# # 是否允许跨域访问 CORS,如果允许,设置允许的域名。当设置'*'号全部域名时,accessControlAllowCredentials应该设置为false。 -# # v4.2.3 开始支持多个域名和模糊匹配,例如:http://*.jeesite.com,http://*.jeesite.net -## accessControlAllowOrigin: http://demo.jeesite.com -## accessControlAllowOrigin: '*' -# -# # 允许跨域访问时 CORS,可以使用的方法和标头 -## accessControlAllowMethods: GET, POST, OPTIONS -## accessControlAllowHeaders: Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With -# -# # 是否允许接收跨域的Cookie凭证数据 CORS,当设置为true时,accessControlAllowOrigin不能设置为'*'。 -## accessControlAllowCredentials: false +# # 设定允许获取的资源列表(v4.2.3) +# #contentSecurityPolicy: "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src 'self' 'unsafe-inline' 'unsafe-eval' data:" +# +# # 是否允许跨域访问 CORS,如果允许,设置允许的域名。v4.2.3 开始支持多个域名和模糊匹配,例如:http://*.jeesite.com,http://*.jeesite.net +# #accessControlAllowOrigin: '*' +# +# # 允许跨域访问时 CORS,可以获取和返回的方法和请求头 +# #accessControlAllowMethods: GET, POST, OPTIONS +# #accessControlAllowHeaders: content-type, x-requested-with, x-ajax, x-token, x-remember +# #accessControlExposeHeaders: x-remember +# +# # 是否允许接收跨域的Cookie凭证数据 CORS +# #accessControlAllowCredentials: false # # # 允许的网站来源地址,不设置为全部地址(避免一些跨站点请求伪造 CSRF、防盗链) -## allowReferers: http://127.0.0.1,http://localhost -## allowReferers: ~ +# #allowReferers: http://127.0.0.1,http://localhost # # # 是否在登录后生成新的Session(默认false) # isGenerateNewSessionAfterLogin: false @@ -516,8 +517,11 @@ shiro: # # 注意:如果超时超过30m,你还需要同步修改当前配置文件的属性:j2cache.caffeine.region.sessionCache 超时时间,大于这个值。 # sessionTimeout: 1800000 # +# # PC设备会话超时参数设置,登录请求参数加 param_deviceType=pc 时有效 +# #pcSessionTimeout: 1800000 +# # # 手机APP设备会话超时参数设置,登录请求参数加 param_deviceType=mobileApp 时有效 -# mobileAppSessionTimeout: 43200000 +# #mobileAppSessionTimeout: 43200000 # # # 定时清理失效会话,清理用户直接关闭浏览器造成的孤立会话 # sessionTimeoutClean: 1200000 @@ -528,7 +532,23 @@ shiro: # # # 共享的SessionId的Cookie名称,保存到跟路径下,第三方应用获取。同一域名下多个项目时需设置共享Cookie的名称。 # #shareSessionIdCookieName: ${session.sessionIdCookieName} - +# +# # 其它 SimpleCookie 参数(v4.2.3) +# #sessionIdCookieSecure: false +# #sessionIdCookieHttpOnly: true +# #sessionIdCookieSameSite: LAX +# +# # 设置接收 SessionId 请求参数和请求头的名称 +# sessionIdParamName: __sid +# sessionIdHeaderName: x-token +# +# # 当直接通过 __sid 参数浏览器访问页面时,可将直接将 __sid 写入 Cookie 应用于后面的访问 +# # 访问地址举例:http://host/js/a/index?__sid=123456&__cookie=true +# writeCookieParamName: __cookie +# +# # 记住我的请求参数和请求头的名称(v4.2.3) +# rememberMeHeaderName: x-remember +# # 系统缓存配置 #j2cache: # @@ -565,6 +585,9 @@ shiro: # # TypeHandlers 扫描基础包,如果多个,用“,”分隔 # scanTypeHandlersPackage: ~ # +# # 是否开启 JDBC 管理事务,默认 Spring 管理事务 v4.2.3 +# jdbcTransaction: false +# # # Mapper文件刷新线程 # mapper: # refresh: @@ -575,6 +598,10 @@ shiro: # Web 相关 #web: +# +# # AJAX 接受参数名和请求头名(v4.3.0) +# ajaxParamName: __ajax +# ajaxHeaderName: x-ajax # # # MVC 视图相关 # view: @@ -583,6 +610,16 @@ shiro: # # 引入页面头部:'/themes/'+themeName+'/include/header.html' # # 引入页面尾部:'/themes/'+themeName+'/include/footer.html' # themeName: default +# +# # 使用智能参数接收器,同时支持 JSON 和 FormData 的参数接受 +# smartMethodArgumentResolver: true +# +# # 使用 .json、.xml 后缀匹配返回视图数据(Spring官方已不推荐使用) +# favorPathExtension: false +# # 使用 __ajax=json、__ajax=xml 后缀匹配返回视图数据 +# favorParameter: true +# # 使用 x-ajax=json、x-ajax=xml 请求头匹配返回视图数据 +# favorHeader: true # # # MVC 拦截器 # interceptor: @@ -628,6 +665,11 @@ shiro: # id: '[a-zA-Z0-9_\-/#\u4e00-\u9fa5]{0,64}' # user.loginCode: '[a-zA-Z0-9_\u4e00-\u9fa5]{4,20}' # +# # 默认不启用(为兼用旧版保留,建议使用 CORS) +# jsonp: +# enabled: false +# callback: __callback +# # # 核心模块的Web功能(仅作为微服务时设为false) # core: # enabled: true @@ -649,12 +691,12 @@ shiro: # enabled: true # # # 文件上传根路径,设置路径中不允许包含“userfiles”,在指定目录中系统会自动创建userfiles目录,如果不设置默认为contextPath路径 -## baseDir: D:/jeesite +# #baseDir: D:/jeesite # # # 上传文件的相对路径(支持:yyyy, MM, dd, HH, mm, ss, E) # uploadPath: '{yyyy}{MM}/' # -# # 上传单个文件最大字节(500M),在这之上还有 > Tomcat限制 > Nginx限制,等。 +# # 上传单个文件最大字节(500M),在这之上还有 > Tomcat限制 > Nginx限制,等,此设置会覆盖 spring.http.multipart.maxFileSize 设置 # maxFileSize: 500*1024*1024 # # # 设置允许上传的文件后缀(全局设置) @@ -687,7 +729,7 @@ shiro: # # # 视频格式转换 ffmpeg.exe 所放的路径 # ffmpegFile: d:/tools/video/ffmpeg-4.9/bin/ffmpeg.exe -## ffmpegFile: d:/tools/video/libav-10.6-win64/bin/avconv.exe +# #ffmpegFile: d:/tools/video/libav-10.6-win64/bin/avconv.exe # # # 视频格式转换 mencoder.exe 所放的路径 # mencoderFile: d:/tools/video/mencoder-4.9/mencoder.exe @@ -695,6 +737,10 @@ shiro: # # 将mp4视频的元数据信息转到视频第一帧 # qtFaststartFile: d:/tools/video/qt-faststart/qt-faststart.exe +# 文件管理是否启用租户模式 +#filemanager: +# useCorpModel: false + #======================================# #========== Message settings ==========# #======================================# @@ -707,6 +753,8 @@ shiro: # realtime: # # 是否开启 # enabled: true +# # 消息实时推送任务Bean名称 +# beanName: msgLocalPushTask # # 推送失败次数,如果推送次数超过了设定次数,仍不成功,则放弃并保存到历史 # pushFailNumber: 3 diff --git a/web/src/main/resources/views/modules/demo/demoFormTabPage.html b/web/src/main/resources/views/modules/demo/demoFormTabPage.html index 1308aaa1..91616bb9 100644 --- a/web/src/main/resources/views/modules/demo/demoFormTabPage.html +++ b/web/src/main/resources/views/modules/demo/demoFormTabPage.html @@ -56,7 +56,6 @@ headerHeight = $('.box-header').outerHeight() || 0, footerHeight = $('.box-footer').outerHeight() || 0, height = windowHeight - headerHeight - footerHeight - 13; - log(height) return height < 300 ? 300 : height; } });